Search code examples
c#entity-frameworkoopaccounting

In c# and entity framework how can I ensure that existing records cannot be changed and new records can only be added after passing checks?


I would like to create a simple general ledger app in c# 6 and entity framework 6 but I guess I am struggling with the proper design.

From my (simplified) point of view a general ledger is a collection of transactions which later can be used to compile relevant data/reports out of.

My requirements regarding the transactions are:

  • Once a transaction is added/posted to the general ledger, in order to ensure a complete audit trail, the transaction cannot be changed or removed.
  • A new transaction can only be added if the corresponding date is in a certain (fiscal) period. Other checks (e.g. if credit and debit amounts match) will also be applied.
    • Existing transactions (i.e. earlier posted transaction) should also be available but when loaded they must no longer pass the checks mentioned above (e.g. they might be from a earlier fiscal period).

Here is what I have come up with so far:

public class GeneralLedger
{
    public int GeneralLedgerId { get; set; }
    public DateTime FiscalPeriodStartDate { get; set; }
    public DateTime FiscalPeriodEndDate { get; set; }
    private ICollection<GeneralLedgerTransaction> _transactions;

    public ICollection<GeneralLedgerTransaction> Transactions {
        get
        {
            if (this._transactions != null) { 
                return this._transactions.ToList().AsReadOnly();
            }
            else
            {
                return null;
            }
        }
    }

    public void AddTransaction(GeneralLedgerTransaction trx)
    {
        if (trx.PostingDate < this.FiscalPeriodStartDate || trx.PostingDate > this.FiscalPeriodEndDate)
        {
            throw new ArgumentOutOfRangeException("invalid booking date");
        }
        else
        {
            this._transactions.Add(trx);
        }
    }

}

public class GeneralLedgerTransaction
{
    public int GeneralLedgerTransactionId { get; set; }
    public DateTime PostingDate { get; set; }
    public virtual ICollection<GeneralLedgerTransactionLine> GeneralLedgerTransactionLines { get; set; }
}

public class GeneralLedgerTransactionLine
{
    public int GeneralLedgerTransactionLineId { get; set; }
    public int FinancialAccountId { get; set; }
    public virtual FinancialAccount FinancialAccount { get; set; }
    public int GeneralLedgerTransactionTypeId { get; set; }
    public virtual GeneralLedgerTransactionType GeneralLedgerTransactionType { get; set; }
    public decimal Amount { get; set; }
}

public class FinancialAccount
{
    public int FinancialAccountId { get; set; }
    public string Name { get; set; }
}

public class GeneralLedgerTransactionType
{
    public int GeneralLedgerTransactionTypeId { get; set; }
    public string Name { get; set; }
}

public class MyDbContext : DbContext
{
    public DbSet<GeneralLedger> GeneralLedgers { get; set; }
    public DbSet<GeneralLedgerTransaction> GeneralLedgerTransactions { get; set; }
    public DbSet<GeneralLedgerTransactionLine> GeneralLedgerTransactionLines { get; set; }
    public DbSet<FinancialAccount> FinancialAccounts { get; set; }
    public DbSet<GeneralLedgerTransactionType> GeneralLedgerTransactionTypes { get; set; }
}

Now this works well in terms of no existing transaction can be removed or altered as the the Transaction property returns a ReadOnly-list and every new newly added transaction is checked against the date period rule. And I also can see that the records are saved to the database. So far so good, the only thing that I am currently missing is that existing records are not loaded to _transactions.

What can I do to make existing transactions available in _transactions without calling the entity framework from within the domain class? Or is there maybe a better way (i.e. a better design) to solve this problem?


Solution

  • After further investigation I learned that I can achieve what I was looking for as follows:

    • changing _transactions from private to protected virtual
    • adding modelBuilder.Configurations.Add(new GeneralLedger.GeneralLedgerTransactionsMapper()); to OnModelCreating
    • creating the following configuration-class:

    .

    public class GeneralLedgerTransactionsMapper : EntityTypeConfiguration<GeneralLedger>
        {
            public GeneralLedgerTransactionsMapper()
            {
                HasMany(s => s._transactions);
            }
        }