Can we use enums as typesafe entity ids?

僤鯓⒐⒋嵵緔 提交于 2020-01-31 03:11:13

问题


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


回答1:


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.




回答2:


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!




回答3:


I really don't try to bash you, but how can one mix up Ids of Type X with Ids of Type Z? I never met anybody who did stuff like myBlog.Id++ either (or at least not without getting fired).

Anyhow, here is a solution which seams to be less work and better maintainable (especiall for the db-admins):

-In the TypeConfiguration, create an Id through the fluent API (you'll see why later)

-Create an abstract base-class for all your entities with:

*property: proteced int Id

*method: public int getIdValue()

*method: public bool isSameRecord(T otherEntity) where T: EntityBaseClass

I guess the first method are self-explanatory, the isSameRecord will take the other instance of your base-class, does a type-check first and if it passes it, it will do a id-checkup, too.


This is an untested approach, there's a good chance you can't create protected identifiers.

If it doesn't work, you could create public int _id and just tell your team to not use it directly.




回答4:


Not sure that this will work in EF but one thing you could do is to have your entities implement IEquatable<T>:

For example your Blog class:

public class Blog : IEquatable<Blog>
{
    // other stuff
    public bool Equals(Blog other)
    {
        return this.Id.Equals(other.Id);
    }
}

Alternatively you could use a more flexible ORM such as NHibernate. If this is of interest, let me know and I'll expand my answer.




回答5:


I know I'm a bit late to this party, but I've used this technique and it definitely works!

Type safety works exactly as you suggest. Compiler will catch mistakes such as

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

And it prevents silly maths.

currentUser.Id++;
currentUser.Id * 3;

Navigation properties still work fine too, as long as both ends of the navigation are the same enum type.

And the SQL queries work just as they do with an int.

It's certainly an interesting idea!

Can you use typesafe entity IDs? - Yes!

Should you? I'm not sure. It doesn't seem that this is how EF was designed and feels a little hacky.



来源:https://stackoverflow.com/questions/23550656/can-we-use-enums-as-typesafe-entity-ids

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!