我们正在使用EF 6.1代码首次设置中的一个相当大的模型,我们正在使用int实体ID。
不幸的是,这并不像我们想要的那样类型安全,因为人们可以很容易地混淆id,例如比较不同类型的实体(myblog.Id == somePost.Id)或类似的ID。或者甚至更糟:myBlog.Id ++。
因此,我提出了使用类型ID的想法,因此您无法混淆ID。 所以我们需要为我们的博客实体提供BlogId类型。现在,显而易见的选择是使用包装在结构中的int,但不能使用结构作为键。而你无法扩展... ...等等,你可以!使用枚举!
所以我想出了这个:
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);
}
}
现在我们有了类型安全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会抓住它。我们有比这更复杂的查询。尝试“查找当前用户博客条目的评论,这些博客条目是由当前用户以后没有自己评论过的特定其他用户”。
此致,Niels
答案 0 :(得分:3)
这是一个有趣的方法,但问题是:是否值得,后果是什么?
您仍然可以执行类似
的操作 if ((int)blog.BlogId == (int)comment.CommentId) { }
就个人而言,我会花更多的时间来教育人们,编写好的测试和代码审查,而不是试图添加某种形式的额外复杂性,这会影响您使用和查询实体的方式。
思考 - 例如:
另一种保护方法是让您的域层通过接受实体实例而不是ID来处理这些事情。
答案 1 :(得分:2)
我对这种用法并不熟悉,但做了一点挖掘,甚至EF团队也表示可行。从最初的关于EF中枚举支持的博客文章中可以看出:
枚举为 此外,枚举类型的属性可以参与主键的定义,唯一约束和外键,以及参与并发控制检查,并声明了默认值。
来源:http://blogs.msdn.com/b/efdesign/archive/2011/06/29/enumeration-support-in-entity-framework.aspx
我自己从未这样做,但这句话给了我信心。所以它是可能的,但正如L-Three所暗示的那样:真的考虑它是否是你想要的(优点和缺点......但听起来你已经这样做了)和测试测试!
答案 2 :(得分:0)
我真的不想尝试打击你,但是怎么能把X型的I与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并告诉您的团队不要直接使用它。
答案 3 :(得分:0)
不确定这会在EF中有效,但您可以做的一件事就是让您的实体实现IEquatable<T>
:
例如您的Blog
班级:
public class Blog : IEquatable<Blog>
{
// other stuff
public bool Equals(Blog other)
{
return this.Id.Equals(other.Id);
}
}
或者,您可以使用更灵活的ORM,例如NHibernate。如果有兴趣,请告诉我,我会扩展我的答案。
答案 4 :(得分:0)
我知道我参加这个派对有点晚了,但是我已经使用过这种技术了,这绝对有效!
类型安全与您的建议完全一致。编译器会遇到诸如
之类的错误from c in ctx.Comments where c.ParentPost.Blog.Id == currentUser.Id
它可以防止愚蠢的数学。
currentUser.Id++;
currentUser.Id * 3;
导航属性也可以正常工作,只要导航的两端都是相同的枚举类型。
SQL查询的工作方式与int
的工作方式相同。
这当然是一个有趣的想法!
可以使用类型安全实体ID吗? - 是的!
应该你呢?我不确定。这似乎不是EF的设计方式,而且感觉有点笨拙。