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

Сущность Framework и лучшие практики WPF

Хорошо ли работать с контекстом? Например, скажем, у меня есть база данных клиентов, и пользователь может искать их по имени, отображать список, выбирать один, а затем редактировать его свойства.

Кажется, я должен использовать контекст, чтобы получить список клиентов (сопоставленных с POCOs или CustomerViewModels), а затем сразу закрыть контекст. Затем, когда пользователь выбирает один из CustomerViewModels в списке, пользовательский раздел пользовательского интерфейса заполняется.

Затем они могут изменить имя, тип, адрес веб-сайта, размер компании и т.д. После нажатия кнопки сохранения я открою новый контекст, использую идентификатор из CustomerViewModel для получения этой записи клиента и обновляю каждый его свойств. Наконец, я вызываю SaveChanges() и закрываю контекст. Это много работы.

Мой вопрос: почему бы просто не работать напрямую с контекстом, оставив его открытым? Я прочитал, используя тот же контекст с большой продолжительностью жизни, очень плохо и неизбежно вызовет проблемы. Мое предположение - если приложение будет использоваться только ОДНИМ человеком, я могу оставить контекст открытым и сделать все. Однако, если будет много пользователей, я хочу поддерживать краткий блок работы и тем самым открывать и закрывать контекст для каждого запроса.

Любые предложения? Спасибо.


@PGallagher - Спасибо за подробный ответ.
@Brice - ваш ввод также полезен

Тем не менее, @Manos D. комментарий "олицетворения избыточного кода" меня немного беспокоит. Позвольте мне привести пример. Предположим, что я храню клиентов в базе данных, и одним из моих свойств клиента является CommunicationMethod.

[Flags]
public enum CommunicationMethod
{
    None = 0,
    Print = 1,
    Email = 2,
    Fax = 4
}

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

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

using(MyContext ctx = new MyContext())
{
    CurrentCustomerVM = new CustomerViewModel(ctx.Customers.Find(customerId));
}

Теперь пользователь может проверить/снять флажки с кнопок "Печать", "Электронная почта" и "Факс", поскольку они привязаны к трем свойствам bool в CustomerViewModel, который также имеет метод Save(). Здесь идет.

public class CustomerViewModel : ViewModelBase
{
    Customer _customer;

    public CustomerViewModel(Customer customer)
    {
        _customer = customer;
    }


    public bool CommunicateViaEmail
    {
        get { return _customer.CommunicationMethod.HasFlag(CommunicationMethod.Email); }
        set
        {
            if (value == _customer.CommunicationMethod.HasFlag(CommunicationMethod.Email)) return;

            if (value)
                _customer.CommunicationMethod |= CommunicationMethod.Email;
            else
                _customer.CommunicationMethod &= ~CommunicationMethod.Email;
        }
    }
    public bool CommunicateViaFax
    {
        get { return _customer.CommunicationMethod.HasFlag(CommunicationMethod.Fax); }
        set
        {
            if (value == _customer.CommunicationMethod.HasFlag(CommunicationMethod.Fax)) return;

            if (value)
                _customer.CommunicationMethod |= CommunicationMethod.Fax;
            else
                _customer.CommunicationMethod &= ~CommunicationMethod.Fax;
        }
    }
    public bool CommunicateViaPrint
    {
        get { return _customer.CommunicateViaPrint.HasFlag(CommunicationMethod.Print); }
        set
        {
            if (value == _customer.CommunicateViaPrint.HasFlag(CommunicationMethod.Print)) return;

            if (value)
                _customer.CommunicateViaPrint |= CommunicationMethod.Print;
            else
                _customer.CommunicateViaPrint &= ~CommunicationMethod.Print;
        }
    }

    public void Save()
    {
        using (MyContext ctx = new MyContext())
        {
            var toUpdate = ctx.Customers.Find(_customer.Id);
            toUpdate.CommunicateViaEmail = _customer.CommunicateViaEmail;
            toUpdate.CommunicateViaFax = _customer.CommunicateViaFax;
            toUpdate.CommunicateViaPrint = _customer.CommunicateViaPrint;

            ctx.SaveChanges();
        }
    }
}

Вы видите что-то не так с этим?

4b9b3361

Ответ 1

В порядке использования долгосрочного контекста; вам просто нужно знать о последствиях.

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

Наличие множества объектов, отслеживаемых контекстом, также может замедлить DetectChanges. Один из способов смягчить это - использовать прокси-серверы отслеживания изменений.

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

Одна интересная вещь, которую вы можете сделать с долгоживущими контекстами в WPF, связана с свойством DbSet.Local(например, context.Customers.Local). это ObservableCollection, который содержит все отслеживаемые объекты, которые не помечены для удаления.

Надеюсь, это даст вам немного больше информации, чтобы помочь вам решить, какой подход поможет.

Ответ 2

Справочник Microsoft:

http://msdn.microsoft.com/en-gb/library/cc853327.aspx

Они говорят:

Ограничить область ObjectContext

В большинстве случаев вы должны создать экземпляр ObjectContext в операторе using (Использование... End Использование в Visual Basic).

Это может повысить производительность, гарантируя, что ресурсы, связанные с контекстом объекта автоматически, когда код выходит из блока операторов.

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

Дополнительные сведения см. в разделе Управление ресурсами в службах объектов (Entity Framework). http://msdn.microsoft.com/en-gb/library/bb896325.aspx

Что говорит:

В долгосрочном контексте объекта вы должны убедиться, что контекст если он больше не требуется.


Справка StackOverflow:

В этом вопросе StackOverflow также есть полезные ответы...

Оптимизация структуры Entity Framework в бизнес-логике?

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


Моя ценность в десять пенсов:

Обтекание контекста в операторе using позволяет сборщику мусора очищать ресурсы и предотвращает утечку памяти.

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

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


Обновление свойств объекта:

Что касается обновления свойств, я использовал простую функцию, которая использует отражение для копирования всех свойств из одного объекта в другой,

Public Shared Function CopyProperties(Of sourceType As {Class, New}, targetType As {Class, New})(ByVal source As sourceType, ByVal target As targetType) As targetType
    Dim sourceProperties() As PropertyInfo = source.GetType().GetProperties()
    Dim targetProperties() As PropertyInfo = GetType(targetType).GetProperties()

    For Each sourceProp As PropertyInfo In sourceProperties
        For Each targetProp As PropertyInfo In targetProperties
            If sourceProp.Name <> targetProp.Name Then Continue For

            ' Only try to set property when able to read the source and write the target
            '
            ' *** Note: We are checking for Entity Types by Checking for the PropertyType to Start with either a Collection or a Member of the Context Namespace!
            '
            If sourceProp.CanRead And _
                  targetProp.CanWrite Then
                ' We want to leave System types alone
                If sourceProp.PropertyType.FullName.StartsWith("System.Collections") Or (sourceProp.PropertyType.IsClass And _
                       sourceProp.PropertyType.FullName.StartsWith("System.Collections")) Or sourceProp.PropertyType.FullName.StartsWith("MyContextNameSpace.") Then
                    '
                    ' Do Not Store
                    '
                Else

                    Try

                        targetProp.SetValue(target, sourceProp.GetValue(source, Nothing), Nothing)

                    Catch ex As Exception

                    End Try

                End If
            End If

            Exit For
        Next
    Next

    Return target
End Function

Где я делаю что-то вроде:

dbColour = Classes.clsHelpers.CopyProperties(Of Colour, Colour)(RecordToSave, dbColour)

Это уменьшает количество кода, которое мне нужно написать для каждого репозитория, конечно!

Ответ 3

Контекст не постоянно подключен к базе данных. По сути, это кеш-память в памяти, которую вы загрузили с диска. Он запрашивает записи только из базы данных, когда вы запрашиваете ранее не загруженную запись, если вы заставляете ее обновляться или когда вы сохраняете изменения на диске.

Открытие контекста, захват записи, закрытие контекста, а затем копирование измененных свойств в объект из совершенно нового контекста - это эпитомия избыточного кода. Вы должны оставить исходный контекст самостоятельно и использовать его для создания SaveChanges().

Если вы хотите иметь дело с проблемами concurrency, вы должны сделать поиск по Google "обработка concurrency" для вашей версии инфраструктуры сущностей.

В качестве примера я нашел this.

Изменить в ответ на комментарий:

Итак, из того, что я понимаю, вам нужно подмножество столбцов записи, которая будет переопределена новыми значениями, в то время как остальное не будет затронуто? Если да, да, вам нужно вручную обновить эти несколько столбцов на "новом" объекте.

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