可以使用枚举作为类型安全的实体id吗?

本文关键字:实体 id 类型安全 枚举 可以使 | 更新日期: 2023-09-27 18:02:21

我们在EF 6.1代码第一次设置中使用一个相当大的模型,我们使用int作为实体id。

不幸的是,这并不像我们希望的那样类型安全,因为很容易混淆id,例如比较不同类型实体的id (myblog;Id == somePost.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是一个编译时错误。我们不能给BlogId加上3。空枚举可能看起来有点奇怪,但这更多的是实现细节。我们需要设置DatabaseGeneratedOption。Identity选项,但这是一次性的工作。

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

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

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

类型安全id会捕获它。我们有比这复杂得多的问题。试试"查找其他用户对当前用户的博客条目的评论,而当前用户自己后来没有评论过"。

Regards, Niels

可以使用枚举作为类型安全的实体id吗?

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

你还可以这样写

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

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

思考——例如:

  • 这种类型转换对LINQ查询的性能有什么影响?
  • 如果您通过WCF的Web API公开您的操作,您将如何强制执行这一点?
  • 这个工作与导航属性??
  • 另外,可以用作主键的类型是有限的;我不相信这对Guids有效。
一种额外的保护方法是让你的域层通过接受实体实例而不是ID来处理这些事情。

我甚至不熟悉这种用法,但做了一些调查,甚至EF团队都说这是可行的。从他们最初的关于EF中枚举支持的博客文章中,列出了以下内容:

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

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

我自己从来没有这样做过,但这句话给了我信心。所以这是可能的,但正如L-Three建议的那样:认真考虑一下这是否是你想要的。缺点. .但听起来你已经这么做了),然后测试测试测试!

我知道我有点晚了,但我已经使用了这个技巧,它绝对有效!

类型安全完全按照您的建议工作。编译器将捕获诸如

之类的错误
from c in ctx.Comments where c.ParentPost.Blog.Id == currentUser.Id

还能防止愚蠢的数学。

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

导航属性也可以正常工作,只要导航的两端是相同的enum类型。

SQL查询和int一样工作。

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

可以使用类型安全的实体id吗?——是的!

吗?我不确定。这似乎不是EF的设计方式,感觉有点粗糙。

我真的不想批评你,但是怎么能把X类型的id和Z类型的id搞混呢?我从来没有遇到过像我的博客这样的人。要么(或者至少不被解雇)。

无论如何,这里有一个解决方案,似乎更少的工作和更好的维护(特别是对于数据库管理员):

在TypeConfiguration中,通过fluent API创建一个Id(稍后您将看到原因)

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

*property: protected int Id

*方法:public int getIdValue()

*方法:public bool isamerecord (T otherEntity) where T: EntityBaseClass

我猜第一个方法是不言自明的,isSameRecord会采取你的基类的另一个实例,做一个类型检查,如果它通过它,它会做一个id检查,太。


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

如果它不起作用,你可以创建一个public int _id,然后告诉你的团队不要直接使用它。

不确定这将在EF中工作,但你可以做的一件事是让你的实体实现IEquatable<T>:

例如你的Blog类:

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

或者你可以使用更灵活的ORM,比如NHibernate。如果你对这个问题感兴趣,请告诉我,我会详细回答的。