【问题标题】:EF4 Code First - Many to many relationship issueEF4 Code First - 多对多关系问题
【发布时间】:2011-03-11 07:53:40
【问题描述】:

在将关系保存到多对多关系时,我的 EF Code First 模型遇到了一些问题。我的模特:

public class Event
{
    public int Id { get; set; }
    public string Name { get; set; }  
    public virtual ICollection<Tag> Tags { get; set; }
}


public class Tag
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Event> Events { get; set; }
}

在我的控制器中,我将一个或多个 TagViewModel 映射到 Tag 类型,并将其发送到我的服务层以进行持久化。此时通过检查实体Tag既有Id也有Name(Id是隐藏字段,在我看来名字是文本框)

当我现在尝试将标签添加到事件时出现问题。让我们看下面的场景:

事件已经在我的数据库中,假设它已经有相关标签C#, ASP.NET

如果我现在将以下标签列表发送到服务层:

ID  Name
1   C#
2   ASP.NET
3   EF4

并通过首先从数据库中获取事件来添加它们,这样我就可以从我的 DbContext 中获得一个实际的事件,然后我只需这样做

myEvent.Tags.Add

添加标签。问题是在SaveChanges()之后我的数据库现在包含这组标签:

ID  Name
1   C#
2   ASP.NET
3   EF4
4   C#
5   ASP.NET

这个,即使我保存的标签在我保存时设置了它的 ID(尽管我没有从数据库中获取它)

【问题讨论】:

  • 在这里阅读我的答案:stackoverflow.com/questions/3635071/… 代码优先的情况仍然相同。
  • 0 反对票 我很困惑。您是说您正在尝试为特定事件指定标签,但这些标签被添加到数据库的 TAGS 表中,而不仅仅是在连接表中创建新行?
  • @Julie - 完全正确。由于我首先使用 EF 代码,因此我不直接访问 EventTags 表,而只是将内容添加到 someEvent.Tags 集合...

标签: c# asp.net-mvc c#-4.0 entity-framework-4 ef-code-first


【解决方案1】:

我想我知道您的代码中发生了什么。让我用简单的例子来解释一下我的观点:

using (var context = new Context())
{
    // Just let assume these are your tags received from view model.
    var csharp = ...;
    var aspnet = ...;
    var ef4 = ...;

    // Now you load Event
    var e = context.Events.Where(e => e.Id == someId);
    // Ups first access to Tag collection triggers lazy loading which
    // is enabled by default so, all current tags are loaded
    e.Tags.Add(csharp);
    e.Tags.Add(aspnet);
    e.Tags.Add(ef4);

    // Now e.Tags.Count == 5 !!! Why?
    context.SaveChanges();
}

第一个问题:因为在您的Event 实例之上创建的动态代理使用HashSet 代替Tags,它会检查集合中是否已经存在添加的实体。如果没有,它会添加实体,否则它会跳过实体。为了能够正确地执行此检查您必须在标签中实现 Equals 和 GetHashCode!因此,因为您没有这样做,它会将添加的标签作为带有临时键的新标签,并使用自动生成的键将它们添加到 Tags 表中。

第二个问题:即使实现EqualsGetHashCode,也只能解决C# 和ASP.NET 标签的重复性问题。目前,上下文不跟踪 EF4 标签,因此该标签仍被视为新标签。您必须通知上下文数据库中存在 EF4 标记。因此,在您触发 Tags 集合的延迟加载之前,让 Attach 将所有标签添加到上下文中。默认情况下,将实体附加到上下文会将其状态设置为Unchanged

using (var context = new Context())
{
    foreach (var tag in TagsFromView)
    {
        context.Attach(tag);
    }

    // Now you load Event
    var e = context.Events.Where(e => e.Id == someId);
    foreach(var tag in TagsFromView)
    {
        // First access will trigger lazy loading but already
        // attached instances of tags are used
        e.Tags.Add(tag);
    }

    // Now you must delete all tags present in e.Tags and not
    // present in TagsFromView

    context.SaveChanges();
}

如果您不在视图中创建新标签,这将有效。如果您也想这样做,则不得将新标签附加到上下文中。您必须区分现有标签和新标签(例如,新标签的 Id = 0)。

【讨论】:

  • 优秀的答案.. 我明天试试这个,让你知道它是如何工作的:)
  • 我接受了这个答案,因为它适用于我的问题。问题是它并没有真正解决我的问题。关键是我的问题没有解释部分问题。所以我会把这个作为接受的答案,稍后再发布一个新问题:)
【解决方案2】:

你需要从db中获取标签,否则EF会将它们视为新项目并覆盖id。

【讨论】:

  • 嗯,这实际上是我想知道的.. 有没有办法让 EF 自己解决这个问题?如果它有一个 Id 集,它不应该插入一个新实体吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-18
  • 2013-01-12
  • 1970-01-01
相关资源
最近更新 更多