【问题标题】:Updating a many to many collection with EF code first首先使用 EF 代码更新多对多集合
【发布时间】:2012-09-16 09:58:59
【问题描述】:

我正在使用 EF Code First 和以下配置

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<UserProfile>()
        .HasMany(x => x.TeamLeaders)
        .WithMany()
        .Map(m => m.MapLeftKey("UserId")
        .MapRightKey("TeamLeaderId")
        .ToTable("UserTeamLeaders"));
}

TeamLeaders 是用户的 ICollection,即它是自引用多对多关系。

简单来说,一个用户可以拥有多个团队负责人。这个配置似乎是正确的,因为它创建了我所期望的决斗 FK/PK 链接表。

我有一个 MVC4 应用程序,它允许从集合中编辑、添加和删除团队负责人。

在我的控制器中,我最初有以下内容:

    var original = context.UserProfiles
        .Include("TeamLeaders")
        .Single(x => x.UserId == model.UserId);

    context.Entry(original).CurrentValues.SetValues(model);

但是,最后一行未能将 TeamLeaders 集合标记为已更新,当我调用 SaveChanges() 时,没有记录任何更改。

相反,我在我的 User 类上编写了一个简单的 CopyProperties 方法,它使用反射来手动复制属性,所以在我的控制器中我现在有:

    var original = context.UserProfiles
        .Include("TeamLeaders")
        .Single(x => x.UserId == model.UserId);

    //context.Entry(original).CurrentValues.SetValues(model);

    original.CopyProperties(model);

但是,这太过分了,SaveChanges 尝试将新用户添加到与所选团队负责人的个人资料相匹配的系统中。

谁能建议我做错了哪一部分?我不确定是否需要更新映射,或者更改将属性从视图模型复制到模型的方式

【问题讨论】:

    标签: asp.net-mvc entity-framework many-to-many code-first


    【解决方案1】:

    您必须根据您的更改修改original 用户中加载的TeamLeaders 集合,以便更改检测可以识别哪些领导者已被移除,哪些领导者已被添加。当您调用SaveChanges 时,EF 将根据检测到的更改为连接表编写适当的 DELETE 和 INSERT 语句。最简单的方法如下所示:

    var original = context.UserProfiles
        .Include("TeamLeaders")
        .Single(x => x.UserId == model.UserId);
    
    original.TeamLeaders.Clear();
    foreach (var teamLeader in model.TeamLeaders)
    {
        var user = context.UserProfiles.Find(teamLeader.UserId);
        if (user != null)
            original.TeamLeaders.Add(user)
    }
    context.SaveChanges();
    

    Find 将从上下文中获取已经加载的团队负责人。如果它们没有加载,它将查询数据库。

    如果您想避免额外的查询,您可以手动将团队负责人附加到上下文中:

    var original = context.UserProfiles
        .Include("TeamLeaders")
        .Single(x => x.UserId == model.UserId);
    
    original.TeamLeaders.Clear();
    foreach (var teamLeader in model.TeamLeaders)
    {
        var user = context.UserProfiles.Local
            .SingleOrDefault(o => o.UserId == teamLeader.UserId);
        if (user == null)
        {
            user = new User { UserId = teamLeader.UserId };
            context.UserProfiles.Attach(user);
        }
        original.TeamLeaders.Add(user)
    }
    context.SaveChanges();
    

    除了加载original的第一个查询之外,这里不涉及进一步的数据库查询。

    顺便说一句:您应该能够使用带有 EF Code First 的强类型 Include 版本:

    Include(u => u.TeamLeaders)
    

    您只需在代码文件中添加using System.Data.Entity; 即可访问此版本。

    【讨论】:

    • 感谢您的支持 - 很不错。我宁愿不必在控制器中清除/重新添加 - 相反,如果有一种设置方法会很好,这样它就可以通过模型/映射上的一些简单配置/属性自行解决,但是这行得通,所以我现在会坚持下去。
    • @GGG:不幸的是,更新分离的对象图总是需要编写相当多的代码。没有可以简化工作的配置选项。 CurrentValues.SetValues(model) 仅影响标量属性,它不会更新任何导航属性,正如您在第一次尝试中看到的那样。
    猜你喜欢
    • 2012-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-09
    • 1970-01-01
    相关资源
    最近更新 更多