Сохраняющиеся изменения в EF 4.3 — проблемы с отслеживанием изменений

У меня есть класс ReportConfigurationManager, который управляет операциями CRUD в отношении объекта UserReport. Интересуют две операции: «Получить» и «Сохранить обновление». В обоих случаях я заключаю операцию в оператор using, чтобы DbContext располагался в конце запроса.

Со временем эти методы станут частью службы WCF, но их также можно будет вызывать внутри службы. Мои нынешние трудности связаны с получением набора модульных тестов для работы, которые напрямую вызывают ReportConfigurationManager.

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

Мои проблемы сейчас связаны с обновлениями.

Несмотря на наличие

    context.Configuration.ProxyCreationEnabled = false;
    context.Configuration.AutoDetectChangesEnabled = false;

во ВСЕХ методах, которые используют ReportConfigurationManager, когда я пришел, чтобы прикрепить UserReport, он потерпел неудачу с классическим «объектом с тем же ключом, который уже существует в ObjectStateManager» (я думал, что отключение отслеживания изменений предназначено для обработки этого?).

Итак, теперь я переключился на использование следующего кода, который я нашел здесь

 public UserReport SaveUpdateUserReport(UserReport userReport)
    {
        using (var context = new ReportDataEF())
        {
            context.Configuration.ProxyCreationEnabled = false;
            context.Configuration.AutoDetectChangesEnabled = false;
            if (userReport.Id > 0)
            {
                {
                    UserReport oldReport = context.UserReports.Where(ur => ur.Id == userReport.Id).FirstOrDefault();
                    context.Entry(oldReport).CurrentValues.SetValues(userReport);
                }                  
            }
            else
            {
                //Need to attach everything to prevent EF trying to create duplicates in the database
                context.ReportTopTypes.Attach(userReport.ReportTopType);
                context.ReportWindows.Attach(userReport.ReportWindow);
                context.ReportSortOptions.Attach(userReport.ReportSortOption);

                foreach (var col in userReport.ReportColumnGroups)
                {
                    context.ReportColumnGroups.Attach(col);
                }

                context.ReportTemplates.Attach(userReport.ReportTemplate);

                //just add the new data
                context.UserReports.Add(userReport);
            }

            context.SaveChanges();
        }

        return userReport;
    }

Меня беспокоит то, что мой код кажется трудоемким - мне нужно получить копию старого объекта, прежде чем я смогу сохранить обновленную копию? И меня тоже не убеждает моя логика «Сохранить новое».

Итак, правильный ли это подход, или есть лучший способ написать вышеизложенное?

Более подробная информация о других событиях:

Потому что я буду отправлять графы объектов через WCF. Я реализовал Eager Loading:

    public static DbQuery<ReportTemplate> IncludeAll(this DbQuery<ReportTemplate> self)
    {
        return self
            .Include("ReportColumnGroups.ReportColumns.ReportColumnType")
            .Include("ReportColumnGroups.ReportColumnType")
            .Include("ReportSortOptions.ReportSortColumns.ReportColumn.ReportColumnType")
            .Include("ReportSortOptions.ReportSortColumns.ReportSortType");
    }

    public static DbQuery<UserReport> IncludeAll(this DbQuery<UserReport> self)
    {
        return self
            .Include("ReportTemplate")
            .Include("ReportTopType")
            .Include("ReportWindow")
            .Include("ReportSortOption.ReportSortColumns.ReportColumn.ReportColumnType")
            .Include("ReportSortOption.ReportSortColumns.ReportSortType")
            .Include("ReportColumnGroups.ReportColumns.ReportColumnType")
            .Include("ReportColumnGroups.ReportColumnType");

    }


    public static DbQuery<ReportSortOption> IncludeAll(this DbQuery<ReportSortOption> self)
    {
        return self
            .Include("ReportSortColumns.ReportColumn.ReportColumnType")
            .Include("ReportSortColumns.ReportSortType");
    }

    public static DbQuery<ReportColumnGroup> IncludeAll(this DbQuery<ReportColumnGroup> self)
    {
        return self
            .Include("ReportColumn.ReportColumnType")
            .Include("ReportColumnType");
    }

    public static DbQuery<ReportColumn> IncludeAll(this DbQuery<ReportColumn> self)
    {
        return self
            .Include("ReportColumnType");
    }

    public static DbQuery<ReportSortColumn> IncludeAll(this DbQuery<ReportSortColumn> self)
    {
        return self
            .Include("ReportColumn.ReportColumnType")
            .Include("ReportSortType");
    }

У меня есть набор статических кэшированных данных, которые я получаю следующим образом:

using (var context = new ReportDataEF())
        {
            context.Configuration.ProxyCreationEnabled = false;
            context.Configuration.AutoDetectChangesEnabled = false;
            reportConfigurationData = new ReportingMetaData()
                                          {
                                              WatchTypes = context.WatchTypes.ToList(),
                                              ReportTemplates = context.ReportTemplates.IncludeAll().ToList(),
                                              ReportTopTypes = context.ReportTopTypes.ToList(),
                                              ReportWindows = context.ReportWindows.ToList(),
                                              ReportSortOptions =
                                                  context.ReportSortOptions.IncludeAll().ToList()
                                          };
        }

и я получаю UserReports следующим образом:

public UserReport GetUserReport(int userReportId)
    {
        using (var context = new ReportDataEF())
        {
            context.Configuration.ProxyCreationEnabled = false;
            context.Configuration.AutoDetectChangesEnabled = false;
            var visibleReports =
                context.UserReports.IncludeAll().Where(ur => ur.Id == userReportId).FirstOrDefault();
            return visibleReports;
        }
    }

Тест, который меня интересует, получает существующий UserReport из БД, обновляет его свойства ReportTemplate и ReportColumnGroups объектами из класса статических данных, а затем пытается сохранить обновленный UserReport.

Используя код из ответа Ладислава, это не удается, когда я пытаюсь прикрепить UserReport, предположительно потому, что один из прикрепленных к нему объектов уже существует в базе данных.


person BonyT    schedule 20.06.2012    source источник


Ответы (1)


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

public UserReport SaveUpdateUserReport(UserReport userReport)
{
    using (var context = new ReportDataEF())
    {
        context.Configuration.ProxyCreationEnabled = false;
        context.Configuration.AutoDetectChangesEnabled = false;

        // Now all entities in the graph are attached in unchanged state
        context.ReportTopTypes.Attach(userReport);

        if (userReport.Id > 0 && 
            context.UserReports.Any(ur => ur.Id == userReport.Id))
        {
            context.Entry(userReport).State = EntityState.Modified;
        }
        else
        {
            context.Entry(userReport).State = EntityState.Added;
        }

        context.SaveChanges();
    }

    return userReport;
}

Это эквивалентно вашему исходному коду. Вы не загружаете пользовательский отчет повторно - вы просто проверяете его наличие в БД. Этот код имеет много проблем - например, если вы изменили любой другой связанный объект, он не будет сохранен в базе данных, потому что в настоящее время его состояние Unchanged. Еще сложнее может быть, если нужно изменить отношения.

person Ladislav Mrnka    schedule 20.06.2012
comment
В графе объектов изменяется только сам объект UserReport, но он имеет связь многие ко многим с классом ReportColumnGroup. Набор ReportColumnGroups в базе данных никогда не изменится, но отношения с UserReports будут, поскольку отчеты могут добавлять или удалять ReportColumnGroups из коллекции. - person BonyT; 20.06.2012
comment
Я думал, что пытался сделать то, что вы указали, но не удалось прикрепить - я прошел через много итераций, поэтому попробую именно то, что вы указали выше, и посмотрю, работает ли это. - person BonyT; 20.06.2012
comment
Да - вылетает с ошибкой: Объект с таким же ключом уже существует в ObjectStateManager. Когда я запускаю строку context.UserReports.Attach(userReport); Я добавлю некоторые подробности моего кода выше. - person BonyT; 20.06.2012
comment
ОК - если я отключу LazyLoading и удалю расширения EagerLoading, то все, похоже, сработает. - person BonyT; 20.06.2012
comment
Исправление - в основном это работает - проблема, с которой я столкнулся, заключается в том, что ни одно из отношений "многие ко многим" не заполнено. Таким образом, мой UserReport больше не имеет извлеченных ReportColumnGroups. - person BonyT; 20.06.2012
comment
ОК - Так что мне нужно снова загрузить Eager... Я думаю, что в конце концов я доберусь до этого :) спасибо за ваш вклад - безмерно благодарен - person BonyT; 20.06.2012
comment
Последний вопрос, который может объяснить проблемы, которые у меня были - есть ли два способа получить одни и те же данные в графе объектов - например. UserReport содержит SortOption и ColumnGroup, оба содержат объекты Column. Вызовет ли это ошибку, которую я получаю при подключении, потому что он идет вниз по иерархии объектов, добавляет столбцы при первом их нахождении, а затем, когда он идет дальше в иерархию и снова находит те же данные, он выдает подгонку ? - person BonyT; 20.06.2012
comment
Это может вызвать проблему, если столбец с одним и тем же ключом представлен двумя экземплярами. - person Ladislav Mrnka; 20.06.2012