Подтвердить что ты не робот

Entity Framework 5 глубокая копия/клон объекта

Я использую Entity Framework 5 (DBContext), и я пытаюсь найти лучший способ глубокой копии объекта (т.е. скопировать объект и все связанные объекты), а затем сохранить новые объекты в базе данных. Как я могу это сделать? Я изучил методы расширения, такие как CloneHelper, но я не уверен, применим ли он к DBContext.

4b9b3361

Ответ 1

Один дешевый простой способ клонирования объекта - сделать что-то вроде этого:

var originalEntity = Context.MySet.AsNoTracking()
                             .FirstOrDefault(e => e.Id == 1);
Context.MySet.Add(originalEntity);
Context.SaveChanges();

трюк здесь AsNoTracking() - когда вы загружаете такой объект, ваш контекст не знает об этом, и когда вы вызываете SaveChanges, он будет рассматривать его как новый объект.

Если MySet имеет ссылку на MyProperty и вам нужна его копия, просто используйте Include:

var originalEntity = Context.MySet.Include("MyProperty")
                            .AsNoTracking()
                            .FirstOrDefault(e => e.Id == 1);

Ответ 2

Вот еще один вариант.

Я предпочитаю его в некоторых случаях, потому что он не требует, чтобы вы специально запускали запрос, чтобы клонировать данные. Вы можете использовать этот метод для создания клонов сущностей, которые вы уже получили из базы данных.

//Get entity to be cloned
var source = Context.ExampleRows.FirstOrDefault();

//Create and add clone object to context before setting its values
var clone = new ExampleRow();
Context.ExampleRows.Add(clone);

//Copy values from source to clone
var sourceValues = Context.Entry(source).CurrentValues;
Context.Entry(clone).CurrentValues.SetValues(sourceValues);

//Change values of the copied entity
clone.ExampleProperty = "New Value";

//Insert clone with changes into database
Context.SaveChanges();

Этот метод копирует текущие значения из источника в новую добавленную строку.

Ответ 3

Это общий метод расширения, который позволяет генерировать клонирование.

Вам нужно извлечь System.Linq.Dynamic из nuget.

    public TEntity Clone<TEntity>(this DbContext context, TEntity entity) where TEntity : class
    {

        var keyName = GetKeyName<TEntity>();
        var keyValue = context.Entry(entity).Property(keyName).CurrentValue;
        var keyType = typeof(TEntity).GetProperty(keyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).PropertyType;

        var dbSet = context.Set<TEntity>();
        var newEntity =  dbSet
            .Where(keyName + " = @0", keyValue)
            .AsNoTracking()
            .Single();

        context.Entry(newEntity).Property(keyName).CurrentValue = keyType.GetDefault();

        context.Add(newEntity);

        return newEntity;
    }

Единственное, что вам нужно реализовать, это метод GetKeyName. Это может быть что угодно: от return typeof(TEntity).Name + "Id" до return the first guid property или вернуть первое свойство, помеченное знаком DatabaseGenerated(DatabaseGeneratedOption.Identity)].

В моем случае я уже пометил свои классы [DataServiceKeyAttribute("EntityId")]

    private string GetKeyName<TEntity>() where TEntity : class
    {
        return ((DataServiceKeyAttribute)typeof(TEntity)
           .GetCustomAttributes(typeof(DataServiceKeyAttribute), true).First())
           .KeyNames.Single();
    }

Ответ 4

У меня была такая же проблема в Entity Framework Core, где глубокое клонирование включает в себя несколько шагов, когда дочерние объекты загружаются лениво. Один из способов клонирования всей структуры заключается в следующем:

   var clonedItem = Context.Parent.AsNoTracking()
        .Include(u => u.Child1)
        .Include(u => u.Child2)
        // deep includes might go here (see ThenInclude)
        .FirstOrDefault(u => u.ParentId == parentId);

    // remove old id from parent
    clonedItem.ParentId = 0;

    // remove old ids from children
    clonedItem.Parent1.ForEach(x =>
    {
        x.Child1Id = 0;
        x.ParentId= 0;
    });
    clonedItem.Parent2.ForEach(x =>
    {
        x.Child2Id = 0;
        x.ParentId= 0;
    });

    // customize entities before inserting it

    // mark everything for insert
    Context.Parent.Add(clonedItem);

    // save everything in one single transaction
    Context.SaveChanges();

Конечно, есть способы сделать универсальные функции для загрузки всего и/или сброса значений для всех ключей, но это должно сделать все шаги очень понятными и настраиваемыми (например, все для некоторых детей вообще не клонировать, пропуская их Включают).