Cloning Object with many-to-many relationship in E

2019-05-21 13:06发布

问题:

All I want is just to create an exact copy of an object.

I have a class

[Serializable]
public class Project 
{
    public int Id { get; set; }
    public String Name { get; set; }

    //navigational fields..
    public virtual List<BusinessRequirement> BusinessRequirements { get; set; }
 }

and another

[Serializable]
public class BusinessRequirement 
{
   public int Id { get; set; }
   public String Name { get; set; }
   public String Description { get; set; }
   public virtual List<Project> Projects { get; set; }
}

so somewhere I've configured the many-to-many relationship b/w Project and BusinessRequirement like this:

HasMany(s => s.BusinessRequirements)
           .WithMany(s => s.Projects)
           .Map(m =>
            {
                   m.MapLeftKey("ProjectId");
                   m.MapRightKey("BusinessRequirementId");
                   m.ToTable("ProjectBusinessRequirementMapping");

            });

and also I've made my dbcontext static i.e.

public static class DataLayer
{
    public static MyDbContext db;
}

now, all m doing is, trying to make a copy of an object of Project i.e.

public Project Clone(Project source)
{

     Project  target = new Project();
     target.Name = source.Name;
     //1.
     // target = source;    

     //2.
     target.BusinessRequirements = new List<BusinessRequirement>();
     foreach(BusinessRequirement br in source.BusinessRequirements)
     {
       BusinessRequirement nbr = DataLayer.Get<BusinessRequirement>(s=>s.Id=br.Id).SingleOrDefault();
        if(nbr!=null)
          target.BusinessRequirements.Add(nbr);
     }  


     //3.
     //target.BusinessRequirements = source.BusinessRequirements;

     //4.
     //target.BusinessRequirements = new List<BusinessRequirement>();
     //foreach(BusinessRequirement br in source.BusinessRequirements)
     //{
     //  BusinessRequirement nbr = br;
     //   if(nbr!=null)
     //     target.BusinessRequirements.Add(nbr);
     //}  

      return target;
}

none of the four methods work properly.

the one which is closest to working is 2, but a strange thing happens. Now, If i add any BusinessRequirements to Original Project, it also gets added to Clonned One and vice-versa, same goes for deletion.

Somehow, entityframework is treating both the projects as one. Though this behavior occurs only in many-to-many related navigational properties.

Why EntityFramework is behaving like this???. What am I missing? please help..

Its been almost a day, but I cannot get it to work.

I have tried this, this, this and this but they didn't work either..

回答1:

You can use the fact that adding an object to a context changes the state of any child objects in its object graph to Added:

Project proj;
using (var db = new MyDbContext())
{
    // Fetch a detached project and populate its BusinessRequirements.
    proj = db.Projects.AsNoTracking().Include(p => p.BusinessRequirements)
             .First(p => p.Id == source.Id);
    db.Projects.Add(proj);
    db.SaveChanges();
}

By fetching the source project with AsNoTracking the context does not add it to its change tracker and the next line db.Projects.Add(proj); considers the project and its adhering child objects as brand new.

Silently, I renounced your strategy to work with one static context. It's a different topic, but you should not do that. Contexts are supposed to have a short life span.



回答2:

The problem exists due to the way you are copying the BusinessRequirement. You are just adding references from the source to the target. So each BusinessRequirement end up with a reference to both Projects.

You need to do something like this.

    target.BusinessRequirements = new List<BusinessRequirement>();
         foreach(BusinessRequirement br in source.BusinessRequirements)
         {
           BusinessRequirement obr = DataLayer.Get<BusinessRequirement>(s=>s.Id=br.Id).SingleOrDefault();
BusinessRequirement obr = new BuisnessRequirment();
            if(nbr!=null){
//copy protperies in obr to nbr
}
              target.BusinessRequirements.Add(nbr);
         }