Entity Framework 6和集合相关问题

8
我正在开发我的第一个Entity Framework应用程序,我正在使用EF版本6(来自Nuget)和.NET 4.0。然而,我遇到了一些困难,对我来说似乎应该非常简单。我在互联网上找到了很多相互矛盾的建议和解决方案,但是经过几天的努力,我真的很困惑,并且开始质疑我对Entity Framework的一些基本理解。我想要做的是这样的:创建一个简单的相关实体集合,并在从父项中删除它们时自动删除它们。 以下是我将如何在普通C#中建模。按照Microsoft示例,假设我们有两个类Post和Tag,如下所示:
public class Post
{
    public string Name { get; set; }
    public string Author { get; set; }

    public ICollection<Tag> Tags { get; set; }
}

public class Tag
{
    public string Text { get; set; }
    // Possibly other properties here
}

那么,添加标签就像 myPost.Tags.Add(myTag)一样简单,删除标签就像 myPost.Tags.Remove(myTag)一样简单。

现在让我们看看Entity Framework方面的事情:我看到这个并想到了“外键,当然!”,但是我添加FK时遇到了很多问题:当从帖子中删除标签时,标签不会从数据库中删除,尽管SQL浏览器显示PostId值正确,但加载时myPost.Tags将具有0个元素,我折腾了很多技巧,例如将 Tag.PostId 标记为Key、手动删除Tags、实际上作为DbSet将Tag添加到上下文中、手动设置 myTag.Post = null;(我尝试启用和禁用Lazy loading,不知道是否有用-尽管如果可能,我想将其关闭)

现在(在很大程度上得益于似乎矛盾和过于复杂的示例),我感到非常困惑和失落。有人能告诉我如何在EF中设置这种关系吗?(顺便说一下,我正在使用Code First)

解决方案:

多亏了Moho,我想出了这个结构,它正好符合我的要求:

public class Post
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Author { get; set; }

    public virtual ICollection<Tag> Tags { get; set; }

    public Post()
    {
        Tags = new HashSet<Tag>();
    }
}

public class Tag
{
    [Key, Column(Order=1), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Text { get; set; }
    // Possibly other properties here

    public virtual Post Post { get; set; }
    [Key, Column(Order=2)]
    public virtual int PostId { get; set; }
}

public class TestContext : DbContext
{
    public DbSet<Post> Posts { get; set; }
}

当从Post的标签集合中删除Tag时,Entity Framework会发出一个DELETE tag的命令,如此处所述 (#2):http://www.kianryan.co.uk/2013/03/orphaned-child/ 同样地,将标签添加到帖子中会自动发出INSERT并设置FK关系。
需要注意的一点是:确保您使用了virtual!我认为这也是我的许多挫败的根源。

你没有设置“Id”属性,EF自动创建了关联?你能给我们举个例子,说明一下你是如何查询的吗? - trinaldi
@Tico 我故意省略了EF实现的内容 - 我想避免任何来自我的实际实现的负面影响/先入为主的观念(更不用说随着我对其进行调整,它已经发生了很大变化!),以便我能够获得如何从一开始正确设置它的建议。我的示例查询也是同样的情况,尽管我设想我的使用情况是这样的:“1)创建带有一些初始标签的帖子2)编辑帖子的标签,删除一些,添加一些。3)保存帖子。” - Xcelled
所以我猜你对上下文和相关知识都很熟悉。既然你在 SQL 中有一些数据,就用 LINQ 查询其中一个。如果它仍然返回 null,那么可能是查询出了问题。 - trinaldi
你,我的朋友,绝对是个天才!我一直在苦苦思索如何让这个工作,而你的解决方案完美地达到了我的期望。 - Mark Kram
2个回答

3

同时也尝试从Tag侧定义关系,指定每个Tag与单个Post相关联且是必须的。

Tag中添加一个必需的导航属性到Post

public class Tag
{
    // you need an ID
    public int Id { get; set; }

    public string Text { get; set; }

    [Required]
    public virtual Post Post { get; set; }
}

或者,如果您真的不想添加导航属性,您可以使用Fluent API:

modelBuilder.Entity<Post>().HasMany( p => p.Tags ).WithRequired();

我的意思是,您的实体需要一个主键ID字段;我假设您省略了它们以缩短长度。 - Moho
[Required] 数据注释用于创建必需的关系。我的意思是 public virtual Post Post { get; set; },以及一个属性:public int PostId { get; set; }。当我搜索一个对象时,我只有Id。如果我搜索一个“帖子”,它会返回null - trinaldi
[Required]注解是否也会防止孤立的子元素(标签)?就像这篇文章中所示:http://www.kianryan.co.uk/2013/03/orphaned-child/(我在我的各种尝试中尝试了#2) - Xcelled
2
是的 - 将“标签”->“帖子”关系设置为必需意味着没有相应的“帖子”就不能有“标签”。这就是为什么从“标签”集合中删除“标签”将会删除DB中的“标签”记录。 - Moho
没错,@Moho。如果你打算使用Xcelled194,你可能需要将级联删除设置为false。 - trinaldi
显示剩余5条评论

1
这可能是评论过时了,但我没有足够的声望来进行评论。以下内容是否能满足您的需求?
public class Tag
{
    [Key, Column(Order=1), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Text { get; set; }
    // Possibly other properties here

    public int PostId { get; set; }

    [ForeignKey("PostId")]
    public virtual Post Post { get; set; }

}

从数据库中加载PostId,然后通过注释将其用作Post类的外键。Post是虚拟的,没有其他内容。您可能也可以只使用基本的[Key]注释。


EF会自动将名为ID的属性识别为类的唯一标识符,因此您是多余的。此外,流畅API似乎比属性装饰更好(更可靠)地描述关系。此外,在DbContext中定义关系(以及其他数据访问层文件中的其余持久性描述)允许您稍后只进行少量更改即可摆脱EF。 - J.D. Ray

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接