vendredi 5 mars 2021

Designing a hierarchy of abstract classes and using it in EF Core

I am using .NET Core with Entity Framework Core to build a finance app and I want to know how to make my design better.

I have a 1 to Many relationship between two entities, BankAccount and Transaction. In a way that:

  • 1 Account can have many Transactions
  • 1 Transaction Belongs to 1 Account

However, I want to include bank accounts and transactions coming from different 3P sources. And while this relationship and the main fields are common across different sources, each source has a unique set of properties I want to keep.

To achieve this I decided to define these entities as abstract classes. This way, you can only instantiate concrete versions of these entities, each coming from a particular data source.

public abstract class Transaction : BaseEntity
{
    public DataSource Source { get; private set; }
    public decimal Amount { get; private set; }
    public DateTime Date { get; private set; }
    public string Name { get; private set; }
    public BankAccount BankAccount { get; private set; }
    public Guid BankAccountId { get; private set; }
    ...
}


public abstract class BankAccount : BaseEntity
{

    public DataSource Source { get; private set; }
    public string Name { get; private set; }
    public Balance Balance { get; private set; }
    public IEnumerable<Transaction> Transactions {get; private set;}
    ...
}

Here is a trimmed down example of the concrete implementations:

public class PlaidTransaction : Transaction
{
    public string PlaidId { get; private set; }

    private PlaidTransaction() : base() { }

    public PlaidTransaction(decimal amount, DateTime date, string name, Guid bankAccountId, string plaidId) : base( amount, date, name, bankAccountId)
    {
        PlaidId = plaidId;
    }
}

public class PlaidBankAccount : BankAccount
{
    public string PlaidId { get; private set; }
    ...
}

I am using .Net Core with Entity Framework Core to persist my data and I managed to store my concrete classes all in the same table (TPH approach)

This works great and now all my entities live under the same table. So I can either query all Transactions or those of a certain type using LINQ's OfType<T> extension.

DbSet<Transaction> entities = _context.Set<Transaction>();

IEnumerable<PlaidTransaction> plaidTransactions = entities.OfType<PlaidTransaction>();

However, when I access my BankAccount field from my concrete Transaction I don't get the concrete instance. So something like this doesn't work.

plaidTransactions.Where((t) => t.BankAccount.PlaidId)

Instead I have to cast it:

plaidTransactions.Where((t) => (t.BankAccount as PlaidBankAccount).PlaidId)

What can I do to avoid casting everywhere? I feel there's a missing piece in my design that would make all my code easier. I was thinking of overriding the getters on my concrete classes but I don't know if I can return a derived class to a base class method. Maybe I should move to generics but 1) I still want to keep the fixed relationship between these entities and 2) how would EF Core handle this?

Aucun commentaire:

Enregistrer un commentaire