1

I have project entity structure like this as below with project name and project number and along with some list objects as well as properties.

public class DesignProject
{
    [Key, GraphQLNonNullType]
    public string ProjectNumber { get; set; }
    public string Name { get; set; }
    [Column(TypeName = "jsonb")]
    public ProjectSectionStatus SectionStatuses { get; set; } = new ProjectSectionStatus();
    [ForeignKey("AshraeClimateZone"), GraphQLIgnore]
    public Guid? AshraeClimateZoneId { get; set; }
    public virtual AshraeClimateZone AshraeClimateZone { get; set; }
    [Column(TypeName = "jsonb")]
    public List<ProjectObject<classA>> classAList{ get; set; } = new List<ProjectObject<classA>>();
    [Column(TypeName = "jsonb")]
    public List<ProjectObject<classB>> classBList{ get; set; } = new List<ProjectObject<classB>>();
    ...... 
    ......
    ......
   Some more json columns
} 

and the project object class like this

public class ProjectObject<T>
{
    public Guid? Id { get; set; }
    public T OriginalObject { get; set; }
    public T ModifiedObject { get; set; }

    [GraphQLIgnore, JsonIgnore]
    public T TargetObject
    {
        get
        {
            return ModifiedObject ?? OriginalObject;
        }
    }
}

and ClassA entity structure like as below

public class ClassA
{
    public string Edition { get; set; }
    public string City { get; set; }  
}

and i have some similar children entities (ClassA) same like as above, I want to copy the contents and statuses from one project entity to other project entity.

I have project entity with ProjectNumber 1212 and have another project having ProjectNumber like 23323 so i would like copy entire project contents from 1212 to 23323. So is there any way we can achieve this with C# and i am using .Net Core with Entity framework core.

Here the source design project that i am going to copy have same structure with destination design project and i am fine with overriding the destination project values and i don't want to update the project number here.

Could any one please let me know the how can i achieve this copying? Thanks in advance!!

Please let me know if i need to add any details for this question

Update : Deep copy related code

   public InsertResponse<string> CopyBookmarkproject(string SourceProjectNumber, string destinationProjectNumber)
   {
        var sourceDesignProject = this._dbContext.DesignProject.Where(a => a.ProjectNumber == SourceProjectNumber).SingleOrDefault();
        var destinationProject = this._dbContext.DesignProject.Where(a => a.ProjectNumber == destinationProjectNumber).SingleOrDefault();

        CopyProject(sourceDesignProject, destinationProject);

       // Need to update the Db context at here after deep copy
    }

    private void CopyProject(DesignProject sourceDesignProject, DesignProject destinationProject)
    {
        destinationProject.classAList= sourceDesignProject.classAList; // Not sure whether this copy will works 
        destinationProject.AshraeClimateZone = sourceDesignProject.AshraeClimateZone; // not sure whether this copy will works also 
    }

Updated solution 2:

    var sourceDesignProject = this._dbContext.DesignProjects.AsNoTracking()
                                                    .Where(a => a.ProjectNumber == sourceProjectNumber)
                                                    .Include(a => a.PrimaryBuildingType)
                                                    .Include(a => a.AshraeClimateZone).SingleOrDefault();

     var targetDesignProject = this._dbContext.DesignProjects.Where(a => a.ProjectNumber == targetProjectNumber).SingleOrDefault();

     sourceDesignProject.ProjectNumber = targetDesignProject.ProjectNumber;
     sourceDesignProject.SectionStatuses.AirSystemsSectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
            
     sourceDesignProject.SectionStatuses.CodesAndGuidelinesSectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
     sourceDesignProject.SectionStatuses.ExecutiveSummarySectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
     sourceDesignProject.SectionStatuses.ExhaustEquipmentSectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
     sourceDesignProject.SectionStatuses.InfiltrationSectionStatus = Entities.Enums.ProjectSectionStage.INCOMPLETE;
      

     this._dbContext.Entry(sourceDesignProject).State = EntityState.Modified; // getting the below error at this line
     this._dbContext.SaveChanges(); 
     ok = true;

getting an error like as below

The instance of entity type 'DesignProject' cannot be tracked because another instance with the same key value for {'ProjectNumber'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

Glory Raj
  • 17,397
  • 27
  • 100
  • 203

5 Answers5

1

There's a simple and short solution which uses .AsNoTracking() for source entry, assigning destination's ProjectNumber to the source and then changing the EntityState as Modified for it. It will copy all the properties to the destination entry :

var sourceDesignProject = this._dbContext.DesignProject.AsNoTracking().Where(a => a.ProjectNumber == SourceProjectNumber).SingleOrDefault();
var destinationProject = this._dbContext.DesignProject.Where(a => a.ProjectNumber == destinationProjectNumber).SingleOrDefault();

sourceDesignProject.ProjectNumber = destinationProject.ProjectNumber;
this._dbContext.Entry(sourceDesignProject).State = System.Data.Entity.EntityState.Modified;
this._dbContext.SaveChanges();

Make sure your relational properties are loaded before.

Amirhossein Mehrvarzi
  • 18,024
  • 7
  • 45
  • 70
  • thanks for the input, I would like not to clone one of the navigation properties from source to destination .. in this case what are the changes need to be done – Glory Raj Jul 09 '20 at 18:21
  • I need to modify the section statuses of cloned object before updating the entity, how can i do that .. – Glory Raj Jul 09 '20 at 18:56
  • I am getting this error when i tried your method `The instance of entity type 'DesignProject' cannot be tracked because another instance with the same key value for {'ProjectNumber'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.` – Glory Raj Jul 09 '20 at 20:47
  • @EnigmaState You should not use more than one instance of the same model with `Tracking` state. I tested my solution before and it works well. – Amirhossein Mehrvarzi Jul 09 '20 at 21:13
  • @ AmirhosseinMehrvarzi i will update my solution here – Glory Raj Jul 09 '20 at 21:14
  • I have updated my code and i am doing exactly the same as your answer – Glory Raj Jul 09 '20 at 21:18
  • could you please point me right direction why i am getting that error, thanks – Glory Raj Jul 09 '20 at 21:19
  • is there any issue with the above code could you please let me know – Glory Raj Jul 09 '20 at 21:21
  • @EnigmaState It seems your next changes to `sourceDesignProject`'s properties caused to Track the instance. Have you tried my solution without additional changes? – Amirhossein Mehrvarzi Jul 09 '20 at 21:32
  • i need to do additional changes i mean need to update those statuses as well. – Glory Raj Jul 09 '20 at 21:33
  • @EnigmaState Please refer to the question title. Also please answer my question to clear the problem. Otherwise, not only me, but also any experienced programmer can't track the problem. – Amirhossein Mehrvarzi Jul 09 '20 at 21:49
  • I have mentioned in the question like i need to copy or clone `Contents and statuses from one entity to another entity` so with this i need to be able to change the statuses as well. Sorry for confusion .. – Glory Raj Jul 09 '20 at 21:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/217567/discussion-between-enigma-state-and-amirhossein-mehrvarzi). – Glory Raj Jul 09 '20 at 22:05
0

You can make use of Reflection. As you have both, source and destination objects and are of the same type. You can just traverse through source object and fetch the properties and replace the values. Something like :

if (inputObjectA != null && inputObjectB != null)
{
    //create variables to store object values
    object value1, value2;
 
    PropertyInfo[] properties = inputObjectA.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
     
    //get all public properties of the object using reflection  
    foreach (PropertyInfo propertyInfo in properties)
    {
    //get the property values of both the objects
      value1 = propertyInfo.GetValue(inputObjectA, null);
      value2 = propertyInfo.GetValue(inputObjectB, null);
    }

Ignore the properties that are not needed to be copied. The method will have a signature like below :

CopyObjects(object inputObjectA, object inputObjectB, string[] ignorePropertiesList)
Kiran Thokal
  • 385
  • 4
  • 7
  • 21
0

Two choices bring to mind for deep copy of objects:

1- Using BinaryFormatter (Needs Serializable attribute on your class, See original answer):

public static T DeepClone<T>(this T obj)
{
    using (var ms = new MemoryStream())
    {
        var formatter = new BinaryFormatter();
        formatter.Serialize(ms, obj);
        ms.Position = 0;

        return (T) formatter.Deserialize(ms);
    }
}

2- Using JsonSerializers like NewtoneSoft:

public static T CloneJson<T>(this T source)
{            
    // Don't serialize a null object, simply return the default for that object
    if (Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    // initialize inner objects individually
    // for example in default constructor some list property initialized with some values,
    // but in 'source' these items are cleaned -
    // without ObjectCreationHandling.Replace default constructor values will be added to result
    var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};

    return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}

Update

Here's the code you can use with NewtonSoft Json library:

private void CopyProject(DesignProject sourceDesignProject, ref DesignProject destinationProject)
{
    if (Object.ReferenceEquals(sourceDesignProject, null))
    {
        destinationProject = null;
        return;
    }

    var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};

    var destProjNumber = destinationProject.ProjectNumber;

    destinationProject = JsonConvert.DeserializeObject<DesignProject>
        (JsonConvert.SerializeObject(sourceDesignProject), deserializeSettings);

    destinationProject.ProjectNumber = destProjNumber;
}
Arman Ebrahimpour
  • 4,252
  • 1
  • 14
  • 46
  • thanks for pointing out, i cannot make serializable attribute to the entities and could you please provide pseudo code to `JsonSerializers` – Glory Raj Jun 26 '20 at 15:26
0

I always prefer using AutoMapper for these type of work. It's optimized and lower risk of making mistakes. And better than serialize/deserialize method since that would cause performance issues.

In your constructor (or in startup.cs and passing via dependency injection)

var config = new MapperConfiguration(cfg => { 
                 cfg.CreateMap<DesignProject, DesignProject>().ForMember(x => x.Id, opt => opt.Ignore());
                 cfg.CreateMap<ProjectObject<ClassA>, ProjectObject<ClassA>>().ForMember(x => x.Id, opt => opt.Ignore()));
                 cfg.CreateMap<ProjectObject<ClassA>, ProjectObject<ClassB>>().ForMember(x => x.Id, opt => opt.Ignore()));
                 cfg.CreateMap<ProjectObject<ClassB>, ProjectObject<ClassB>>().ForMember(x => x.Id, opt => opt.Ignore()));
                 cfg.CreateMap<ProjectObject<ClassB>, ProjectObject<ClassA>>().ForMember(x => x.Id, opt => opt.Ignore()));

 });
 var mapper = config.CreateMapper();

In your method :

 var newProject = mapper.Map(sourceProject, new DesignProject());

OR

 mapper.Map(sourceProject, targetProject);
Noldor
  • 1,122
  • 9
  • 22
-1

This shouldn't work. If I understand you want to copy all elements from list into new list, but to make new object. Than maybe you can use extension method to clone complete list as described here link. For updating navigation properties check this post link.

  • i want to update the destination entity values with source entity values with out changing the project number and looking a way to update with the above structure .. and looking to update the navigation properties as well. – Glory Raj Jun 22 '20 at 18:33
  • But you can call function to clone list. List that is attribute of project. – Lazar Đorđević Jun 22 '20 at 18:39
  • yeah thanks, i can do that but i am looking for is there any other way to go in a single shot that will update the navigational properties as well like `AshraeClimateZone` here – Glory Raj Jun 22 '20 at 18:42
  • How is `AshraeClimateZone` related to `classAList`? Why is important for you to copy both at the same time? I think that you can't do this. – Lazar Đorđević Jun 22 '20 at 18:48
  • Sorry for the confusion, i am looking for any other approach to copy or update the entire source entity values to destination entity values rather than going each individual list update that . I just updated the question – Glory Raj Jun 22 '20 at 18:52
  • I saw edit, but there is no difference. Are you sure that is possible? – Lazar Đorđević Jun 22 '20 at 19:04
  • I am looking for the same is it possible to update the navigational properties also along with other objects in single shot `OR` need to update individually and `ProjectSectionStatus` object i need to update manually as well – Glory Raj Jun 22 '20 at 19:06
  • and it is just like updating row values with other row if you consider that project number – Glory Raj Jun 22 '20 at 19:14