简体   繁体   English

如何使用EF6更新多对多表

[英]How can I use EF6 to update a many to many table

I have two classes: 我有两节课:

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

I have an ObjectiveDetail object from the user: 我有一个来自用户的ObjectiveDetail对象:

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

And an ObjectiveDetail from the database: 和数据库中的ObjectiveDetail:

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

With Entity Framework 6 I know I can update the text in the ObjectiveDetail class using: 使用Entity Framework 6,我知道我可以使用以下命令更新ObjectiveDetail类中的文本:

_uow.ObjectiveDetails.Update(web));

But how can I update the references to ObjectiveDetail and SubTopics in the many to many table that joins these two table. 但是如何在连接这两个表的多对多表中更新对ObjectiveDetail和SubTopics的引用。 Here for example I would want it so that for ObjectiveDetail 1 the many-many is changed to reference subTopicId 1 and 3 instead of the values 1 and 2. Note that ObjectiveDetail and SubTopic are stored in tables with another table between them. 这里例如我想要它,以便对于ObjectiveDetail 1,将many-many更改为引用subTopicId 1和3而不是值1和2.请注意,ObjectiveDetail和SubTopic存储在表中,并且它们之间有另一个表。 Here's the DDL: 这是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),
);

Here's the EF Mapping that I have: 这是我的EF Mapping:

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

    }
}

I think you are trying to simulating offline mode working for your users. 我认为您正在尝试模拟为您的用户工作的离线模式。 So when you get something from your users, you want to sync database with user data. 因此,当您从用户那里获得某些内容时,您希望将数据库与用户数据同步。 I make an example and take your question one step beyond :) I added a Subtopic which needs to be updated in database. 我做了一个例子,把你的问题提高了一步:)我添加了一个需要在数据库中更新的Subtopic。 Ok here is the code: 好的,这里是代码:

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

The result: 结果:

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

That's it. 而已。 You can sync your database and user data like this. 您可以像这样同步数据库和用户数据。 And also AutoMapper and ValueInjecter both are very useful and powerful tools, I deeply recommend you to take a look at those. AutoMapperValueInjecter都是非常有用和强大的工具,我深深建议你看看那些。 I hope you enjoyed, happy coding :) 我希望你喜欢,快乐编码:)

Here's a method that takes the target ObjectiveDetail 's ID and an IEnumerable<int> of SubTopic IDs that you want to add to the target ObjectiveDetail . 这是一个方法,它接受目标ObjectiveDetail的ID和要添加到目标ObjectiveDetailSubTopic ID的IEnumerable<int>

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

I've only used EF with Code first, and to define the 3 tables you either define all 3 tables or you just define the 2 tables with a collection in each like this 我只使用EF代码优先,并定义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; }
}

If you have the 3 tables, it's easy to just update the middle table with new ids. 如果您有3个表,则可以轻松地使用新的ID更新中间表。 You have to get all the ObjectiveTopics that you wish to update, and change the ids, and then do the update 您必须获取要更新的所有ObjectiveTopics,并更改ID,然后执行更新

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

If you don't have the third table defined as an entity, and you only have access to ObjectiveDetail and SubTopic tables, then you can get a hold of both entities and remove the one you don't want anymore, and add the one that you want. 如果您没有将第三个表定义为实体,并且您只能访问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);

If you want to (and probably should), you can use linq on the objectiveD to get the entity from the collection instead of retrieving it from the database. 如果你想(也许应该),你可以在objectiveD上使用linq从集合中获取实体,而不是从数据库中检索它。

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

You could use a generic method, so that it can be used for any many to many relationship. 您可以使用通用方法,以便它可以用于任何多对多关系。 You would just give this list of integers representing the IDs of the field you want to update in the many-to-many collection off your main object: 您只需在主对象的多对多集合中提供此整数列表,表示要更新的字段的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);
    }
  }
}

You would call it like this: 你会这样称呼它:

UpdateManyToMany(db, objectiveDetail.SubTopics, subTopicIDList);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM