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

时间:2014-05-08 19:17:10

标签: c# entity-framework entity-framework-6

我们正在使用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

5 个答案:

答案 0 :(得分:3)

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

您仍然可以执行类似

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

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

思考 - 例如:

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

另一种保护方法是让您的域层通过接受实体实例而不是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的设计方式,而且感觉有点笨拙。