EF4 Code First - 多对多关系问题

发布于 2024-10-21 17:22:54 字数 992 浏览 1 评论 0原文

在保存与多对多关系的关系时,我的 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 映射到标签类型,并将其发送到我的服务层以进行持久化。此时通过检查实体标签同时具有 ID 和名称(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(尽管我没有从数据库中获取它)

I'm having some trouble with my EF Code First model when saving a relation to a many to many relationship. My Models:

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; }
}

In my controller, I map one or many TagViewModels into type of Tag, and send it down to my servicelayer for persistence. At this time by inspecting the entities the Tag has both Id and Name (The Id is a hidden field, and the name is a textbox in my view)

The problem occurs when I now try to add the Tag to the Event. Let's take the following scenario:

The Event is already in my database, and let's say it already has the related tags C#, ASP.NET

If I now send the following list of tags to the servicelayer:

ID  Name
1   C#
2   ASP.NET
3   EF4

and add them by first fetching the Event from the DB, so that I have an actual Event from my DbContext, then I simply do

myEvent.Tags.Add

to add the tags.. Problem is that after SaveChanges() my DB now contains this set of tags:

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

This, even though my Tags that I save has it's ID set when I save it (although I didn't fetch it from the DB)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

相对绾红妆 2024-10-28 17:22:54

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

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 集合上的延迟加载之前,让我们将所有标签附加到上下文。默认情况下,将实体附加到上下文会将其状态设置为未更改

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)。

I think I know what happend in your code. Let me explain my opinion in simple example:

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();
}

The first problem: Because a dynamic proxy created on top of your Event instance uses HashSet for Tags it checks if added entity already exists in the collection. If not, it adds the entity othewise it skips the entity. To be able to do this check correctly YOU MUST IMPLEMENT Equals and GetHashCode in Tag! So because you didn't do it, it takes added tags as new ones with temporary keys and it adds them to Tags table with autogenerated key.

The second problem: Even if you implement Equals and GetHashCode you will solve only duplicity of C# and ASP.NET tags. At the moment the context doesn't track EF4 tag so this tag is still considered as a new one. You must inform the context that EF4 tag exists in DB. So lets Attach all tags to the context before you trigger lazy loading on Tags collection. Attaching entity to the context by default sets its state to 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();
}

This works if you don't create new tags in your view. If you want to do it as well, you mustn't attach new tags to the context. You have to differ between existing tags and new tags (for example new tags can have Id = 0).

慕巷 2024-10-28 17:22:54

您需要从数据库获取标签,否则 EF 会将它们视为新项目并覆盖 id。

You need to get the tags from the db, otherwise EF will treat them as new items and override the id.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文