Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can we use enums as typesafe entity ids?

We are working with a rather large model in a EF 6.1 code first setup and we are using ints for entity ids.

Unfortunately, this is not as typesafe as we would like, since one can easily mix up ids, for example comparing ids of entities of different types (myblog.Id == somePost.Id) or similar. Or even worse: myBlog.Id++.

Therefore, I came up with the idea of using typed ids, so you cannot mix up ids. So we need a BlogId type for our blog entity. Now, the obvious choice would be to use an int wrapped in a struct, but you cannot use structs as keys. And you cannot extend int... - wait, you can! Using enum!

So I came up with this:

public enum BlogId : int { } 
public class Blog
{
    public Blog() { Posts = new List<Post>(); }
    public BlogId BlogId { get; set; }
    public string Name { get; set; }
    public virtual List<Post> Posts { get; set; }
}
internal class BlogConfiguration : EntityTypeConfiguration<Blog>
{
    internal BlogConfiguration()
    {
        HasKey(b => b.BlogId);
        Property(b=>b.BlogId)
           .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    }
}

So now we have typesafe ids - comparing a BlogId and a PostId is a compile time error. And we cannot add 3 to a BlogId. The empty enums may look a bit strange, but that is more of an implementation detail. And we have to set the DatabaseGeneratedOption.Identity option explicitly in our mapping, but that's a one-time effort.

Before we start converting all our code to this pattern, are there any obvious problems?

Edit: I probably need to clarify why we must work with ids instead of full entities in the first place. Sometimes we need to match entities in EF Linq queries - and comparing entities doesn't work there. For example (building on the blog example and assuming a somewhat richer domain model): Find comments on the current users blog entries. Remember, that we want to do it in the database (we have lots of data) and we assume there are no direct navigational properties. And the currentUser is not attached. A naive approach would be

from c in ctx.Comments where c.ParentPost.Blog.Author == currentUser 

This doesn't work, since you cannot compare entities in EF Linq. So we try

from c in ctx.Comments where c.ParentPost.Blog.Id == currentUser.Id

This compiles and runs but is wrong - it should have been

from c in ctx.Comments where c.ParentPost.Blog.Author.Id == currentUser.Id

Typesafe ids would have caught it. And we have much more complex queries than this. Try "find comments to current users blog entries made by specific other user which the current user has not himself commented on later".

Regards, Niels

like image 416
Niels Harremoes Avatar asked May 08 '14 19:05

Niels Harremoes


People also ask

What is enum in Entity Framework?

In Entity Framework, this feature will allow you to define a property on a domain class that is an enum type and map it to a database column of an integer type. Entity Framework will then convert the database value to and from the relevant enum as it queries and saves data.

How enums are type safe?

The enums are type-safe means that an enum has its own namespace, we can't assign any other value other than specified in enum constants. Typesafe enums are introduced in Java 1.5 Version. Additionally, an enum is a reference type, which means that it behaves more like a class or an interface.


2 Answers

It's an interesting approach, but the question is: is it worth it and what are the consequences?

You could still do something like

 if ((int)blog.BlogId == (int)comment.CommentId) { }

Personally I would invest more time in educating people, writing good tests, and code reviews, instead of trying to add some form of extra complexity that influences the way you use and query your entities.

Think - for example:

  • What effect does this casting have on performance in LINQ queries?
  • How would you enforce this if you expose your operations through Web API of WCF?
  • Does this work with navigation properties??
  • Also, you are limited in the types you could use as primary key; I don't believe this works with Guids.

A way of additional protection is to have your domain layer handle these kinds of things by accepting entity instances instead of ID's.

like image 77
L-Four Avatar answered Sep 23 '22 22:09

L-Four


I was not even familiar with this usage, but did a little digging and even the EF team says it's do-able. From their initial blog post on enum support in EF, this is listed:

Enums as keys In addition, properties of enum types can participate in the definition of primary keys, unique constraints and foreign keys, as well as take part in concurrency control checks, and have declared default values.

source: http://blogs.msdn.com/b/efdesign/archive/2011/06/29/enumeration-support-in-entity-framework.aspx

I have not ever done this myself, but that quote gives me confidence. So it's possible, but as L-Three suggests: really consider if it's what you want (pros & cons .. but sounds like you have already done that) and test test test!

like image 30
Julie Lerman Avatar answered Sep 24 '22 22:09

Julie Lerman