Thursday, August 29, 2013

Duck typing with EF entities

Ok, this might be old news for you, but for me it's a neat trick that I just figured out.

Any time I work on a personal project involving a database, I prefer to use Entity Framework Code First (EF from now). It just lets me do what I want (write code) instead of stuff I hate (dragging things around in a badly implemented "graphical editor"). One of the many things I absolutely love about it is that I can use POCOs - Plain Old C# Objects - as database entities. I don't have to inherit specific classes, not even specific interfaces.

I do have a few rules when writing these classes, though:

  • All entities have a primary key called Id.
  • The Id is normally an int (it will be automatically converted to an auto-incrementing field); in some special cases (e.g., for security purposes) the Id might be a Guid instead.
  • Any foreign key gets written as a <name>Id field for the foreign key itself and, most of the time, a virtual <name>(s) property for the actual record(s) - depending on whether the relationship is 1:1 or 1:many.

Here's an example from my Inventory project up on GitHub:

  public class SaleItem
  {
    public int Id { get; set; }
    public int SaleId { get; set; }
    public int ProductId { get; set; }
    public decimal Quantity { get; set; }
    public decimal Price { get; set; }

    public virtual Sale Sale { get; set; }
    public virtual Product Product { get; set; }
  }

You can see here the primary key Id and the two foreign keys SaleId and ProductId, with the matching Sale and Product properties for the lazy loading of the actual records. (EF wants them virtual so that it can intercept the accesses and make lazy loading possible.)

So far so good. So what is the trick? Well, there's a problem with not inheriting from a common root (as you can see from the SaleItem class above): it makes it difficult to write generic code. For example, I needed a method that could go through a list of either sale or acquisition items (two distinct entities and therefore distinct classes) and select the stocks with the same products as the list items. Something like this:

    private static IEnumerable<Stock> GetStocks<T>(IEnumerable<Stock> stocks, IEnumerable<T> items)
    {
      var productIds = items
        .Select(it => it.ProductId)
        .ToList();

      return stocks
        .Where(it => productIds.Contains(it.ProductId))
        .ToList();
    }

The problem is that this code won't compile; C# doesn't like the it.ProductId expression.

The way I solved this initially was to create a common Item interface with a ProductId property and change the SaleItem and AcquisitionItem classes to implement that interface. And sure, if you can do that then that works (and is probably faster than my trick). However, that's not always possible. So what I came up with instead (and I am absolutely not claiming this is my invention - it is more than likely that I saw this somewhere and it just took a while for it to bubble up from the deep dark hurricane that is my mind) was this:

    private static IEnumerable<Stock> GetStocks<T>(IEnumerable<Stock> stocks, IEnumerable<T> items)
    {
      var productIds = items
        .Select(it => ((dynamic) it).ProductId)
        .ToList();

      return stocks
        .Where(it => productIds.Contains(it.ProductId))
        .ToList();
    }

The money shot is in this expression:

((dynamic) it).ProductId

What it does is, basically, telling the compiler "I know that the object has a property called ProductId, go ahead and retrieve that for me". Since the objects do have that property, nothing blows up at run-time. That means I can keep my entities as unrelated as I want, with absolutely no need to inherit from a common class or even an interface. Of course, in this case both entities need to have a ProductId property, but that's a convention I'm already following (and one that, worst come to worst, I can work around by using the ?: operator to decide which property to access based on the actual type of the it entity).

Conclusion: use POCO entities with EF Code First and use the dynamic keyword instead of forcing the entity classes to inherit from a common ancestor.

No comments: