Как мне изящно повторно присоединить структуру POCO Entity Framework 5 и сохранить ее?

Я делаю веб-приложение MVC4, используя Entity Framework 5 (сначала база данных с сгенерированными POCO) для доступа к данным.

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

Для хранения документа я определил несколько таблиц базы данных, которые, в свою очередь, сопоставляются с объектами EF POCO, используемыми бизнес-уровнем, которые затем используются контроллерами MVC. Таким образом, недолговечные DbContexts используются для получения POCO и сохранения их в сеансе между запросами.

В результате экран сохранения должен сохранять содержимое этого объекта POCO, который имеет свойства навигации, в существующие данные таблицы (таблицы категорий, макета и разделов), а также добавлять или обновлять данные (CaseStudySections и сам CaseStudy). Таким образом, все POCO либо новые, либо контекст, используемый для их извлечения, уже давно удален. Другими словами, все они «отстранены».

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

Я что-то упустил? Соответствует ли это передовой практике? Есть ли более изящный и / или краткий способ прикрепить структуру POCO и вставить / обновить?

Код для сохранения тематического исследования:

public void SaveCaseStudy(CaseStudy caseStudy)
{
    foreach (var s in caseStudy.CaseStudySections)
    {
        this.Entities.Sections.Attach(s.Section);

        if (s.CreatedByRefId == default(Guid))
        {
            s.CreatedByRefId = this.UserRefId;
            s.CreatedTime = DateTime.Now;
            this.Entities.CaseStudySections.Add(s);
        }
        else
        {
            this.Entities.CaseStudySections.Attach(s);
            var entry = this.Entities.Entry(s);
            entry.Property(e => e.TextData).IsModified = true;
            entry.Property(e => e.BinaryData).IsModified = true;
        }

        s.LastModifiedByRefId = this.UserRefId;
        s.LastModifiedTime = DateTime.Now;
    }

    foreach (var m in caseStudy.AdditionalMaterials)
    {
        if (m.CreatedByRefId == default(Guid))
        {
            m.CreatedByRefId = this.UserRefId;
            m.CreatedTime = DateTime.Now;
            this.Entities.AdditionalMaterials.Add(m);
        }
        else
        {
            this.Entities.AdditionalMaterials.Attach(m);
        }

        m.LastModifiedByRefId = this.UserRefId;
        m.LastModifiedByTime = DateTime.Now;
    }

    this.Entities.Layouts.Attach(caseStudy.Layout);
    this.Entities.Categories.Attach(caseStudy.Category);

    if (caseStudy.CreatedByRefId != default(Guid))
    {
        this.Entities.CaseStudies.Attach(caseStudy);
        var entry = this.Entities.Entry(caseStudy);
        entry.Property(e => e.CaseStudyName).IsModified = true;
        entry.Property(e => e.CaseStudyTitle).IsModified = true;
    }
    else
    {
        this.Entities.CaseStudies.Add(caseStudy);
        caseStudy.CreatedByRefId = this.UserRefId;
        caseStudy.CreatedTime = DateTime.Now;
    }

    caseStudy.LastModifiedByRefId = this.UserRefId;
    caseStudy.LastModifiedTime = DateTime.Now;

    if (caseStudy.CaseStudyStatus != (int)CaseStudyStatus.Personalized)
    {
        caseStudy.CaseStudyStatus = (int)CaseStudyStatus.PendingApproval;
    }

    caseStudy.ApprovedByRefId = null;
    caseStudy.ApprovedTime = null;
    this.Entities.SaveChanges();

    var existingAdditionalMaterialRefIds = caseStudy.AdditionalMaterials
        .Select(m => m.AdditionalMaterialRefId)
        .ToArray();

    var additionalMaterialsToRemove = this.Entities.AdditionalMaterials
        .Where(m => 
            m.CaseStudyRefId == caseStudy.CaseStudyRefId && 
            !existingAdditionalMaterialRefIds.Contains(m.AdditionalMaterialRefId))
        .ToArray();

    foreach (var additionalMaterialToRemove in additionalMaterialsToRemove)
    {
        this.Entities.AdditionalMaterials.Remove(additionalMaterialToRemove);
    }

    this.Entities.SaveChanges();
}

person Jim Noble    schedule 13.09.2012    source источник


Ответы (2)


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

Здесь немного старше, но все еще действительный ответ по теме - короче говоря, с тех пор, как я его написал, ничего не изменилось, был создан только новый API DbContext, который все еще находится поверх старого API. Лучшее описание этой темы, которое я видел до сих пор, находится в книге Программирование Entity Framework: DbContext.

person Ladislav Mrnka    schedule 13.09.2012

Как насчет того, чтобы просто сделать:

db.CaseStudies.Attach(caseStudy);
db.Entry(caseStudy).State = EntityState.Modified;
db.SaveChange();

Это сохранит все изменения в вашей модели в db.

person James Reategui    schedule 11.11.2012
comment
К сожалению, это не работает. В нашем случае это дает исключение DbUpdateConcurrencyException: Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.. Вероятно, это связано с тем, что caseStudy состоит из объектов данных, некоторые из которых уже существуют в базе данных (и поэтому, очевидно, должны быть явно повторно подключены), а некоторые являются новыми и должны быть вставлены. - person Jim Noble; 14.11.2012