我们可以使用枚举作为类型安全实体ID吗?

我们正在使用EF 6.1代码首次设置中的一个相当大的模型,我们正在使用实体ID的int。

不幸的是,这并不像我们想要的那样类型安全,因为人们可以很容易地混合id,例如比较不同类型的实体(myblog.Id == somePost.Id)或类似的ID。 甚至更糟:myBlog.Id ++。

因此,我提出了使用类型ID的想法,因此您无法混淆ID。 所以我们需要为我们的博客实体提供BlogId类型。 现在,显而易见的选择是使用包装在结构中的int,但不能使用结构作为键。 而你无法扩展… …等等,你可以! 使用枚举!

所以我想出了这个:

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

所以现在我们有了类型安全ID – 比较BlogId和PostId是一个编译时错误。 我们不能将3添加到BlogId。 空的枚举可能看起来有点奇怪,但这更像是一个实现细节。 我们必须在映射中显式设置DatabaseGeneratedOption.Identity选项,但这是一次性的努力。

在我们开始将所有代码转换为此模式之前,是否有任何明显的问题?

编辑:我可能需要澄清为什么我们必须首先使用id而不是完整实体。 有时我们需要匹配EF Linq查询中的实体 – 并且比较实体在那里不起作用。 例如(在博客示例的基础上,并假设一个更丰富的域模型):查找当前用户博客条目的评论。 请记住,我们希望在数据库中执行此操作(我们有大量数据),并且我们假设没有直接的导航属性。 并且当前的用户没有附加。 一种天真的方法

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

这不起作用,因为您无法比较EF Linq中的实体。 所以我们试试

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

这编译并运行但是错误 – 它应该是

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

Typesafe id会抓住它。 我们有比这更复杂的查询。 尝试“查找当前用户博客条目的评论,由当前用户以后没有自己评论的特定其他用户”。

此致,尼尔斯

这是一个有趣的方法,但问题是:是否值得,后果是什么?

你仍然可以做类似的事情

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

我个人会花更多的时间来教育人们,编写好的测试和代码审查,而不是试图添加一些影响你使用和查询实体的方式的额外复杂性。

想想 – 例如:

  • 这个演员对LINQ查询的性能有什么影响?
  • 如果通过WCF的Web API公开您的操作,您将如何强制执行此操作?
  • 这适用于导航属性吗?
  • 此外,您可以用作主键的类型受限; 我不相信这适用于Guids。

另一种保护方法是让您的域层通过接受实体实例而不是ID来处理这些类型的事情。

我甚至不熟悉这种用法,但做了一点挖掘,甚至EF团队也说它可行。 从他们在EF的enum支持的最初博客文章中,列出了:

枚举作为键此外,枚举类型的属性可以参与主键,唯一约束和外键的定义,以及参与并发控制检查,并声明了默认值。

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

我自己也没有这样做,但这句话给了我信心。 所以它是可能的,但正如L-Three所暗示的那样:真的考虑它是否是你想要的(优点和缺点……但听起来你已经这样做了)和测试测试!

我真的不试图打击你,但是怎么能把X型的Ids和Z型的ID混合在一起呢? 我从来没有见过任何像myBlog.Id ++这样的人(或者至少没有被解雇)。

无论如何,这里有一个解决方案,它可以减少工作量并提高维护性(特别是对于db-admins):

– 在TypeConfiguration中,通过流畅的API创建一个Id(稍后你会明白为什么)

– 为所有实体创建一个抽象基类:

* property:proteced int Id

*方法:public int getIdValue()

*方法:public bool isSameRecord(T otherEntity)其中T:EntityBaseClass

我想第一种方法是不言自明的,isSameRecord将采用你的基类的另一个实例,先进行类型检查,如果它通过它,它也会进行id检查。


这是一种未经测试的方法,您很有可能无法创建受保护的标识符。

如果它不起作用,您可以创建public int _id并告诉您的团队不要直接使用它。

不确定这在EF中是否有效,但您可以做的一件事是让您的实体实现IEquatable

例如你的Blog类:

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

或者,您可以使用更灵活的ORM,例如NHibernate。 如果这是有意义的,请告诉我,我会扩展我的答案。

我知道我参加这个派对有点晚了,但是我已经使用过这种技术了,它绝对有效!

类型安全完全按照您的建议工作。 编译器会遇到诸如此类的错误

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

它可以防止愚蠢的数学。

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

导航属性仍然​​可以正常工作,只要导航的两端都是相同的枚举类型。

SQL查询的工作方式与对int

这当然是一个有趣的想法!

你能使用类型安全的实体ID吗? – 是的!

你呢? 我不确定。 这似乎不是EF的设计方式,而且感觉有点hacky。