Landman Code Exploring my outer regions of coding.

A blog about C#, Delphi, assembler and general developer stuff.

Landman Code Exploring my outer regions of coding.

A blog about C#, Delphi, assembler and general developer stuff.

Adding support for enum properties on your entities in Entity Framework

While Entity Frameworks is a reasonably nice ORM and the push of Microsoft behind a technology sure helps adoption from the corporate people, there are some seriously annoying limitations. The heavy dependencies can be abstracted away by (ab)using interfaces and other object oriented constructs. But some limitations are deeply nested within the assumptions of Entity Framework and are hard to work around.

One that has annoyed me very much was that (before POCO support) only certain types of properties are allowed. Using any other property type gives annoying NotSupportedExceptions, or the designer does not allow it. This became very apparent with enumerations, there are multiple use cases for enumerations in a data model and should cause no real harm to the queries to cast a integer back and forth to a enum, but EF only likes scalar type in the expressions to be converted to SQL. You'll enjoy messages such as "LINQ to Entities only supports casting Entity Data Model primitive types.". While I can understand that supporting enumerations is apparently very hard to solve (for the EF team that is), they could have added a way to offer wrapping the access to these fields so the external users of the entities do not have to share intimate internal details.

Assuming you have a Order entity and you hide the Status field in your entity behdind DbStatus and create a custom property which casts the Integer to a enum and back. As follows:

public enum OrderState
{
	Unknown = 0,
	InProcess = 1,
	Approved = 2,
	Backordered = 3,
	Rejected = 4,
	Shipped = 5,
	Cancelled = 6
}

public partial class Order
{
	public OrderState Status
	{
		get { return (OrderState)DbStatus; }
		set { DbStatus = (int)value; }
	}
}

If you would then write a Query such as:

var approvedOrders = new EFTestDatabaseEntities().Orders.Where(o => o.Status == OrderState.Approved).ToList();

You would get a nice exception:

System.NotSupportedException : The specified type member 'Status' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.

So I created an extension which adds wrapping support to Entity Framework 1 (and 4), this wrapping offers a way to support these queries by changing the actual query just before EF starts translating the expression tree to SQL. This in itself is not that big of a challenge, but Entity Framework being closed and inextensible as it is, it took some time (and a StackOverflow question) to find the right place to inject this logic.

After using it for a few projects I thought I should share this with the community, so I created a Github project to host the source code, and share it with the rest of the world suffering from Entity Framework. The change required to be able to wrap is that the original ObjectQuery must be replaced with a instance of WrappedFieldsObjectQuery containing the original ObjectQuery. When your using a pattern such as repository, this change should only have to be in one file in perhaps a few places.

The previous example than becomes:

var approvedOrders = new WrappedFieldsObjectQuery<Order>(new EFTestDatabaseEntities().Orders)
	.Where(o => o.Status == OrderState.Approved).ToList();

And will now run and replace the Status field in the Expression tree to the DbStatus field in the model just before the translation to SQL. The way this works is by convention, the WrappedFieldsObjectQuery tries to find for every property used if there is an property with the "Db" perfix and than replaces the reference with that one. So if the wrapped field was named WrappedStatus, this wouldn't have worked, but it avoids any clutter on the Entity site with either attributes or interfaces.

You can get the source here. (you can also download compiled dll's for EF 1 and EF 4 from Github) Both Entity Framework 1 and Entity Framework 4 are supported, but for EF4 I've only tested it with the generate model from database. I added EF4 support while I haven't used it yet in any project, but I will investigate soon if EF4 offers better ways to do this, and if POCO even needs this kind of extension.

ps. you can also use this wrapper to protect your relations (such as collections) to other entities, see my other post describing this.

7 comments:

  1. joebloe said:

    I'm confused. Wouldn't this be just as easy?

    public class Order
    {
    public int OrderStatusId {get;set;}
    public OrderStatus Status
    {
    get{return(OrderStatus)this.OrderStatusId;}
    set{this.OrderStatusId = (int)value;}
    }
    }
    var query = from o in Orders
    where o.OrderStatusId == (int)orderStatus
    select o.ToList();

    at 19 June, 2011 20:43  
  2. Davy Landman said:

    Hi joebloe,

    Well, I think that depends on how you interpret easy.

    For one time usage, sure your solution is good enough.

    But image this limitation of EF sprinkled over your complete code base, who would want that?
    You want an ORM because it abstracts away the way you store data, this casting from int to enum and back is not a domain concern, but more a data/infrastructure concern.

    So in the end, I guess it comes down to the need of separation of concerns.

    But feel free to use your solution for small projects, just think about the maintainability!

    at 19 June, 2011 20:50  
  3. joebloe said:

    Ok. I get what you are saying. I actually spent a little more time looking at your code after I posted (I should do that more often) and see where you were going.

    I guess I got hung up on the how of an isolated incident, where you were talking about things on a larger project scale.

    Good post.

    at 20 June, 2011 19:38  
  4. joebloe said:

    Oh, and due to EF's lack of support for enums I guess I have gotten used to sprinkling this type of code in my projects.

    Maybe I will give your project another look. Thanks Davy.

    at 20 June, 2011 19:45  
  5. Anthony said:

    Hi Davy, I've been searching around and your solution seems to be the best. Unfortunately we're still stuck using EF 4.3.1 because our Data Provider does not support EF5 (Pervasive SQL), so we can't take advantage of EF5's enum support.

    Do you think your strategy is still valid here with EF 4.3? Have you made any updates/revisions?

    Thanks

    at 06 March, 2013 16:57  
  6. Anthony said:

    Another question, Davy, it seems like this approach may be useful in type mapping. For example, I have a CHAR value in a database that's either Y or N, and I want my model to represent that as a bool value (especially in Queries).

    Have you explored that scenario at all? Any thoughts?

    Thanks
    Anthony

    at 06 March, 2013 17:34  
  7. Davy Landman said:

    I have not worked on this code for the last 3 years, however I would not expect any problems running it for 4.3, perhaps some minor adaptions to the library?

    Regarding your other question, have you looked at the other post? http://landman-code.blogspot.nl/2010/08/protection-your-entitycollections-from.html if I remember correctly, the result type has to be cast-able from the other.

    However, using the same expression rewriting you could create a translation to do that conversion of the Boolean. However, it would be better to evolve the DB schema.

    at 06 March, 2013 19:46  

Post a Comment