【问题标题】:How can I use EF6 to update a many to many table如何使用 EF6 更新多对多表
【发布时间】:2014-02-12 21:20:42
【问题描述】:

我有两个班级:

public partial class ObjectiveDetail {
    public ObjectiveDetail() {
        this.SubTopics = new List<SubTopic>();
    }
    public int ObjectiveDetailId { get; set; }
    public int Number { get; set; }
    public string Text { get; set; }
    public virtual ICollection<SubTopic> SubTopics { get; set; }
}
public partial class SubTopic {
    public int SubTopicId { get; set; }
    public string Name { get; set; }
}

我有一个来自用户的 ObjectiveDetail 对象:

var web = {
 "objectiveDetailId":1,
 "number":1,
 "text":"datafromweb",
 "subTopics":[
              {"subTopicId":1,
               "name":"one"
              },
              {"subTopicId":3,
               "name":"three",
              }
             ]
}

还有来自数据库的 ObjectiveDetail:

var db = {
 "objectiveDetailId":1,
 "number":1,
 "text":"datafromdb",
 "subTopics":[
              {"subTopicId":1,
               "name":"one"
              },
              {"subTopicId":2,
               "name":"two",
              }
             ]
}

使用 Entity Framework 6,我知道我可以使用以下方法更新 ObjectiveDetail 类中的文本:

_uow.ObjectiveDetails.Update(web));

但是如何在连接这两个表的多对多表中更新对 ObjectiveDetail 和 SubTopics 的引用。例如,在这里我想要它,以便将 ObjectiveDetail 1 的多对多更改为引用 subTopicId 1 和 3 而不是值 1 和 2。请注意,ObjectiveDetail 和 SubTopic 存储在表中,它们之间有另一个表。这是 DDL:

CREATE TABLE [dbo].[ObjectiveDetail] (
    [ObjectiveDetailId] INT            IDENTITY (1, 1) NOT NULL,
    [Text]              NVARCHAR (MAX) NOT NULL,
    [ObjectiveTopicId]  INT            NULL,
    CONSTRAINT [PK_ObjectiveDetail] PRIMARY KEY CLUSTERED ([ObjectiveDetailId] ASC),
);

CREATE TABLE [dbo].[ObjectiveTopic] (
    [ObjectiveDetailId] INT NOT NULL,
    [SubTopicId]        INT NOT NULL,
    CONSTRAINT [FK_ObjectiveTopicObjectiveDetail] FOREIGN KEY ([ObjectiveDetailId]) REFERENCES [dbo].[ObjectiveDetail] ([ObjectiveDetailId]),
    CONSTRAINT [FK_ObjectiveTopicSubTopic] FOREIGN KEY ([SubTopicId]) REFERENCES [dbo].[SubTopic] ([SubTopicId])
);

CREATE TABLE [dbo].[SubTopic] (
    [SubTopicId] INT             IDENTITY (1, 1) NOT NULL,
    [Name]       NVARCHAR (150)  NOT NULL,
    CONSTRAINT [PK_SubTopic] PRIMARY KEY CLUSTERED ([SubTopicId] ASC),
);

这是我拥有的 EF 映射:

public class ObjectiveDetailMap : EntityTypeConfiguration<ObjectiveDetail>
{
    public ObjectiveDetailMap()
    {
        // Primary Key
        this.HasKey(t => t.ObjectiveDetailId);
        // Relationships
        this.HasMany(t => t.SubTopics)
           .WithMany(t => t.ObjectiveDetails)
           .Map(m =>
           {
               m.ToTable("ObjectiveTopic");
               m.MapLeftKey("ObjectiveDetailId");
               m.MapRightKey("SubTopicId");
           });

    }
}

【问题讨论】:

  • 您可能想在ObjectiveTopic 表上放置一个主键或索引或两个或其他东西...
  • 其他人在这里问了同样的问题:stackoverflow.com/questions/21274521/…
  • @Moho - 另一个问题来自我的办公室伙伴。她问如何找出哪些物体不同。这个问题是关于如何进行更新的。我们仍在查看您的答案。一旦我们确定要做什么,我们就会接受这个问题的答案。感谢您的帮助。

标签: c# asp.net asp.net-mvc entity-framework entity-framework-6


【解决方案1】:

我认为您正在尝试模拟为您的用户工作的离线模式。因此,当您从用户那里获得某些东西时,您希望将数据库与用户数据同步。我举了一个例子,让你的问题更进一步:) 我添加了一个需要在数据库中更新的子主题。好的,代码如下:

static void Main(string[] args)
{
    //the database
    var ObjectiveDetails = new List<ObjectiveDetail>()
    {
        new ObjectiveDetail()
        {
            ObjectiveDetailId = 1,
            Number = 1,
            Text = "datafromdb",
            SubTopics = new List<SubTopic>()
            {
                new SubTopic(){ SubTopicId = 1, Name="one"}, //no change
                new SubTopic(){ SubTopicId = 2, Name="two"}, //to be deleted
                new SubTopic(){ SubTopicId = 4, Name="four"} //to be updated
            }
        }
    };

    //the object comes as json and serialized to defined object.
    var web = new ObjectiveDetail()
    {
        ObjectiveDetailId = 1,
        Number = 1,
        Text = "datafromweb",
        SubTopics = new List<SubTopic>()
        {
            new SubTopic(){ SubTopicId = 1, Name="one"}, //no change
            new SubTopic(){ SubTopicId = 3, Name="three"}, //new row
            new SubTopic(){ SubTopicId = 4, Name="new four"} //must be updated
        }
    };

    var objDet = ObjectiveDetails.FirstOrDefault(x => x.ObjectiveDetailId == web.ObjectiveDetailId);
    if (objDet != null)
    {
        //you can use AutoMapper or ValueInjecter for mapping and binding same objects
        //but it is out of scope of this question
        //update ObjectDetail
        objDet.Number = web.Number;
        objDet.Text = web.Text;
        var subtops = objDet.SubTopics.ToList();

        //Delete removed parameters from database
        //Entity framework can handle it for you via change tracking
        //subtopicId = 2 has been deleted 
        subtops.RemoveAll(x => !web.SubTopics.Select(y => y.SubTopicId).Contains(x.SubTopicId));

        //adds new items which comes from web
        //adds subtopicId = 3 to the list
        var newItems = web.SubTopics.Where(x => !subtops.Select(y => y.SubTopicId).Contains(x.SubTopicId)).ToList();
        subtops.AddRange(newItems);

        //this items must be updated
        var updatedItems = web.SubTopics.Except(newItems).ToList();

        foreach (var item in updatedItems)
        {
            var dbItem = subtops.First(x => x.SubTopicId == item.SubTopicId);
            dbItem.Name = item.Name;
        }

        //let's see is it working
        Console.WriteLine("{0}:\t{1}\t{2}\n---------",objDet.ObjectiveDetailId, objDet.Number, objDet.Text);
        foreach (var item in subtops)
        {
            Console.WriteLine("{0}: {1}", item.SubTopicId, item.Name);
        }
    }
    else
    {
         //insert new ObjectiveDetail
    }

    //In real scenario after doing everything you need to call SaveChanges or it's equal in your Unit of Work.
}

结果:

1:      1       datafromweb
---------
1: one
4: new four
3: three

就是这样。您可以像这样同步您的数据库和用户数据。还有AutoMapperValueInjecter 都是非常有用和强大的工具,我强烈推荐你看看这些。我希望你喜欢,愉快的编码:)

【讨论】:

  • 如果我错了,请纠正我,但在这一点上: var updatedItems = web.SubTopics.Except(newItems).ToList(); web.SubTopics 有 3 个项目(id 为 1、3 和 4),newItems 有 1 个项目(id 为 3)。因此,执行此行后,updatedItems 将包含 2 个项目(id 为 1 和 4)。是不是id为1的子主题不应该被触及,因为它没有改变,唯一更新的子主题应该是id为4的那个?
  • @HoudiniSutherland 是的,你说的是对的,但请记住,在这些 linq 过滤器中,没有变化跟踪起作用。它实际上与另一个主题有关。您必须了解实体中发生了什么变化。这是由现代 ORM 工具完成的,例如 Entity Framework Change Tracker。
【解决方案2】:

这里有一个方法,它采用目标ObjectiveDetail 的ID 和一个IEnumerable&lt;int&gt;SubTopic ID,您要添加到目标ObjectiveDetail

public void UpdateSubTopics( int objectiveDetailId, IEnumerable<int> newSubTopicIds )
{
    using( var db = new YourDbContext() )
    {
        // load SubTopics to add from DB
        var subTopicsToAdd = db.SubTopics
            .Where( st => newSubTopicIds.Contains( st.SubTopicId ) );

        // load target ObjectiveDetail from DB
        var targetObjDetail = db.ObjectiveDetail.Find( objectiveDetailId );

        // should check for targetObjDetail == null here

        // remove currently referenced SubTopics not found in subTopicsToAdd 
        foreach( var cst in targetObjDetail.SubTopics.Except( subTopicsToAdd ) )
        {
            cst.SubTopics.Remove( cst );
        }

        // add subTopicsToAdd not currently found in referenced SubTopics
        foreach( var nst in subTopicsToAdd.Except( targetObjDetail.SubTopics ) )
        {
            targetObjDetail.SubTopics.Add( nst );
        }

        // save changes
        db.SaveChanges();
    }
}

【讨论】:

    【解决方案3】:

    我只首先使用 EF 和 Code,要定义 3 个表,您可以定义所有 3 个表,或者只定义 2 个表,每个表都有一个集合,像这样

    public class ObjectiveDetail 
    {
        public ObjectiveDetail() {
            this.SubTopics = new HashSet<SubTopic>();
        }
        public int ObjectiveDetailId { get; set; }
        public int Number { get; set; }
        public string Text { get; set; }
        public virtual ICollection<SubTopic> SubTopics { get; set; }
    }
    
    public partial class SubTopic 
    {
        public SubTopic() {
            this.ObjectiveDetail = new HashSet<ObjectiveDetail>();
        }
        public int SubTopicId { get; set; }
        public string Name { get; set; }
        public virtual ICollection<ObjectiveDetail> ObjectiveDetails { get; set; }
    }
    

    如果您有 3 个表,只需使用新 id 更新中间表就很容易了。您必须获取所有要更新的 ObjectiveTopics,并更改 id,然后进行更新

    ObjectiveTopic objectiveTopic = _uow.ObjectiveTopic.Get(1);
    ObjectiveTopic.SubTopicId = 2;
    ObjectiveTopic.ObjectiveDetailId = 1;
    _uow.ObjectiveTopic.Update(objectiveTopic);
    

    如果您没有将第三个表定义为实体,并且您只能访问 ObjectiveDetail 和 SubTopic 表,那么您可以获得两个实体并删除您不再需要的实体,然后添加你想要的那个。

    ObjectiveDetail objectiveD = _uow.ObjectiveDetail.Get(1);
    SubTopic subTopic = _uow.SubTopic.Get(1); //SubTopic to remove
    SubTopic topicToAdd = _uow.SubTopic.Get(2); //SubTopic to add
    
    ObjectiveDetail.SubTopics.Remove(subTopic); //Remove the entity from the ObjectiveTopic table
    ObjectiveDetail.SubTopics.Add(topicToAdd); //Add the new entity, will create a new row in ObjectiveTopic Table
    _uow.ObjectiveDetail.Update(objectiveD);
    

    如果您想要(并且可能应该),您可以在 ObjectiveD 上使用 linq 从集合中获取实体,而不是从数据库中检索它。

    SubTopic subTopic = objectiveD.SubTopics.Single(x => x.SubTopicId == 1); //Instead of _uow.SubTopic.Get(1);
    ...
    

    【讨论】:

      【解决方案4】:

      您可以使用泛型方法,这样它就可以用于任何多对多关系。您只需在主对象的多对多集合中给出表示要更新的字段 ID 的整数列表:

      protected void UpdateManyToMany<T>(YourDBContext db, ICollection<T> collection, List<int> idList) where T : class
      {
        //update a many to many collection given a list of key IDs
        collection.Clear();      
        var source = db.Set<T>();
      
        if (idList != null)
        {
          foreach (int i in idList)
          {
            var record = source.Find(i);
            collection.Add(record);
          }
        }
      }
      

      你可以这样称呼它:

      UpdateManyToMany(db, objectiveDetail.SubTopics, subTopicIDList);
      

      【讨论】:

      • 这不是一个“更新”功能,因为它不会删除从先前事务中删除的对象。
      • collection.Clear() 命令在重新添加之前清除所有条目。我一直在使用它,而且效果很好。
      猜你喜欢
      • 1970-01-01
      • 2014-08-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-26
      相关资源
      最近更新 更多