Upsert родитель и дети с EntityFramework - PullRequest
1 голос
/ 28 мая 2019

Мне часто хочется, чтобы в EntityFramework работала функция «Upsert» - у меня есть отключенный граф родительских + дочерних объектов, и я хочу «защитить» родителя (и его дочерних элементов).Под этим я подразумеваю

  • Когда у родителя нет назначенного идентификатора (id = 0), я хотел бы просто вставить родителя и детей (идентификаторы с автоматическим назначением)в обоих случаях)
  • Когда родительский имеет назначенный идентификатор, я хотел бы заменить существующий родительский объект в базе данных (имеющий такой же идентификатор) новым.Кроме того, все дочерние элементы, связанные с этим родителем, должны быть удалены (родитель «владеет» дочерними элементами).Дети на новом родителе должны быть вставлены на свое место (сохраняя назначенные идентификаторы).

Это «в основном» работает в том смысле, что оно сохраняет идентификатор родителя, но не сохраняет идентификаторы дочерних элементов (дочерним элементам назначаются новые идентификаторы для каждого «upsert»).

public void Upsert(ParentEntity parentEntity) {
    ParentEntity existing = DB.Parents.Find(parentEntity.Id);

    if (existing == null) {
        DB.Parents.Add(parentEntity);
    } else {
        var existingChildren = DB.Children.Where(m => m.ParentId == parentEntity.Id).ToArray();

        foreach (var child in parentEntity.Children) {
            child.ParentId = parentEntity.Id;
        }

        DB.Children.RemoveRange(existingChildren);
        DB.Children.AddRange(parentEntity.Children);

        DB.Entry(existing).State = EntityState.Detached;
        DB.Parents.Attach(parentEntity);
        DB.Entry(parentEntity).State = EntityState.Modified;
    }

    SaveChanges();
}

Ответы [ 2 ]

0 голосов
/ 30 мая 2019

Это работает:

public void Upsert(ParentEntity parentEntity) {
    ParentEntity existing = DB.Parents.Find(parentEntity.Id);

    if (existing == null) {
        DB.Parents.Add(parentEntity);
    } else {
        var newChildKeys = new HashSet<int>(parentEntity.Children.Select(m => m.Id));

        var existingChildren = DB.Children.Where(m => m.ParentId == parentEntity.Id).ToArray();
        var existingChildrenKeys = new HashSet<int>(existingChildren.Select(m => m.Id));

        foreach (var existingChild in existingChildren) {
            if (newChildKeys.Contains(existingChild.Id)) {
                DB.Entry(existingChild).State = EntityState.Detached;
            } else {
                DB.Children.Remove(existingChild);
            }
        }

        foreach (var child in parentEntity.Children) {
            child.ParentId = parentEntity.Id;
            if (existingChildrenKeys.Contains((child.Id))) {
                DB.Children.Attach(child);
                DB.Entry(child).State = EntityState.Modified;
            } else {
                DB.Children.Add(child);
            }
        }

        DB.Entry(existing).State = EntityState.Detached;
        DB.Parents.Attach(parentEntity);
        DB.Entry(parentEntity).State = EntityState.Modified;
    }

    SaveChanges();
}

Возможно, его можно улучшить со стилистической точки зрения / сделать более общим. Основная идея

  • Найти общие ключи для всех сущностей (существующие в базе данных и замещающие сущности)
  • Для общих ключей, отсоединить старый объект, прикрепить новый объект и пометить как измененный
  • Для ключей, которые больше не существуют - удалить сущность
  • Для новых ключей - добавить сущность
0 голосов
/ 28 мая 2019

Это то, что я использую, чтобы получить то, что я хочу. Для моей цели это прекрасно работает:

foreach (var metric in metrics)
                {
                    var added = context.Set(metric.GetType()).Add(metric);

                    foreach (var dbEntityEntry in context.ChangeTracker.Entries())
                    {
                        //If these entities exist, don't add them.
                        var metricContext = dbEntityEntry.Entity as MetricContext;
                        if (metricContext != null)
                        {
                            var found = context.MetricContext.FirstOrDefault(c => c.Context == metricContext.Context);
                            if (found != default(MetricContext))
                            {
                                dbEntityEntry.State = EntityState.Detached;
                            }
                        }
                    }
                }

Это отсоединяет любые сущности, которые уже живут в БД, что означает, что они не будут обработаны.

...