我们可以使用枚举作为类型安全实体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。