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

Entity Framework 6: объект клонирования, кроме ID

В моей программе MVVM у меня есть класс Model (скажем MyModel), из которого у меня есть экземпляр чтения из базы данных (с использованием Entity Framework). При получении объекта я представляю все данные пользователю. Позже пользователь будет изменять некоторые поля.
Я хочу создать один и тот же объект, кроме него ID (так как ID - это первичный ключ и автоматически увеличивается).
Итак, как я мог подойти к этому? Я не хочу копировать все поля один за другим, это не надежный подход. Потому что, возможно, в будущем модель может быть изменена, поэтому таким образом я буду учитывать это в методе клонирования.

Итак, есть ли какой-нибудь элегантный способ для копирования объекта и при сохранении в базе данных, идентификатор автоматически увеличивается? (Установка ID на null дает мне ошибку компилятора, потому что это тип int).

4b9b3361

Ответ 1

Я заметил, что копировать не нужно. Очевидно, что при добавлении экземпляра модели в базу данных (даже если для идентификатора установлено значение, уже существующее в базе данных), Entity Framework вставляет новую строку в базу данных и автоматически увеличивает ее первичный ключ. Так что эта функциональность уже встроена в EF. Я этого не знал, извини.
Просто для ясности вот пример:

using(var database = new MyDbContext()) {
    MyModel myModel = database.FirstOrDefault(m => m.SomeProperty == someValue);
    myModel.SomeOtherProperty = someOtherValue; //user changed a value
    database.MyModels.Add(myModel); //even though the ID of myModel exists in the database, it gets added as a new row and the ID gets auto-incremented 
    database.SaveChanges();
}

Ответ 2

Лори Петерсон предложил использовать .AsNoTracking() для выполнения клонирования в EF6. Я использую этот метод и могу подтвердить, что он работает. Вы даже можете включать дочерние объекты.

var entity = context.Entities
                    .AsNoTracking()
                    .Include(x => x.ChildEntities)
                    .FirstOrDefault(x => x.EntityId == entityId);

entity.SomeProperty = DateTime.Now;

context.Entities.Add(entity);
context.SaveChanges();

Когда вы извлекаете сущность или сущности из набора данных, вы можете указать Entity Framework не отслеживать любые изменения, которые вы делаете для этого объекта, а затем добавить этот объект в качестве нового объекта в набор данных. С использованием .AsNoTracking контекст ничего не знает о существующей сущности.

Ответ 3

При использовании ObjectContext ответ, предоставленный QuantumHive, не работает.

Ошибка в этой ситуации:

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
System.InvalidOperationException: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
   at System.Data.Objects.ObjectStateManager.AddEntry(IEntityWrapper wrappedObject, EntityKey passedKey, EntitySet entitySet, String argumentName, Boolean isAdded)
   at System.Data.Objects.ObjectContext.AddSingleObject(EntitySet entitySet, IEntityWrapper wrappedEntity, String argumentName)
   at System.Data.Objects.DataClasses.RelatedEnd.AddEntityToObjectStateManager(IEntityWrapper wrappedEntity, Boolean doAttach)
   at System.Data.Objects.DataClasses.RelatedEnd.AddGraphToObjectStateManager(IEntityWrapper wrappedEntity, Boolean relationshipAlreadyExists, Boolean addRelationshipAsUnchanged, Boolean doAttach)
   at System.Data.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedTarget, Boolean applyConstraints, Boolean addRelationshipAsUnchanged, Boolean relationshipAlreadyExists, Boolean allowModifyingOtherEndOfRelationship, Boolean forceForeignKeyChanges)
   at System.Data.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedEntity, Boolean applyConstraints)
   at System.Data.Objects.DataClasses.EntityReference`1.set_ReferenceValue(IEntityWrapper value)
   at System.Data.Objects.DataClasses.EntityReference`1.set_Value(TEntity value)

Чтобы правильно клонировать объект инфраструктуры сущности (по крайней мере, в EF6.0):

/// <summary>
/// Clone a replica of this item in the database
/// </summary>
/// <returns>The cloned item</returns>
public Item CloneDeep()
{
    using (var context = new EntityObjectContext())
    {
        var item = context.Items
            .Where(i => i.ItemID == this.ItemID)
            .Single();
        context.Detach(item);
        item.EntityKey = null;
        item.ItemID = 0;
        return item;
    }
}

Ответ 4

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

Я не знаю, лучший ли это подход к клонированию, поэтому я искал другой путь. Но это работает. Если вам нужно клонировать объект несколько раз, вы можете использовать сериализацию JSON для клонирования... что-то вроде этого (используя Newtonsoft JSON).

using( var context = new Context() ) {
    Link link    = context.Links.Where(x => x.Id == someId);
    bool isFirst = true;
    foreach( var id in userIds ) {
        if( isFirst ) {
            link.UserId = id;
            isFirst     = false;
        }
        else {
            string cloneString = JsonConvert.SerializeObject(link);
            Link clone = JsonConvert.DeserializeObject<Link>(cloneString);
            clone.UserId = id;
            context.Links.Add(clone);
        }
    }
    context.SaveChanges();
}

Ответ 5

Я использую базу данных postgres:

CREATE TABLE public."Table" ( 
    "Id" integer NOT NULL DEFAULT nextval('"Table_Id_seq"'::regclass),
    ...

Ни один из упомянутых способов не работает в моем случае. Я использую во-вторых:

Table table = _context.Table.AsNoTracking().Select(s => new Table {
 // some properties, exept id
    }).FirstOrDefault();
_context.Table.Add(table);
await _context.SaveChangesAsync();