Мы используем Entity Framework 4.2 с первым подходом к модели и генерацией кода DbContext.
Допустим, у нас есть следующая модель данных в структуре сущностей:
class Person
{
public string Name { get; set; }
public Address Address { get; set; }
}
class Address
{
public string City; { get; set; }
}
Сценарий следующий:
- ViewModel хочет загрузить некоторые данные из базы данных
- Для загрузки данных создается задача (асинхронная операция). это
потому что мы не хотим, чтобы пользовательский интерфейс зависал при загрузке данных.
- Задача (которая выполняется в отдельном потоке) создает новые
контекст базы данных и загружает данные (например, объект Person) из базы данных
- Задача завершается и контекст базы данных уничтожается.
- Главный поток уведомляется о завершении задачи. Основная нить может
теперь получить доступ к загруженному объекту Person.
- Представление пытается отобразить имя и адрес человека в текстовом поле через привязку данных
- Представление обращается к Person.Name (здесь нет проблем)
- Представление обращается к Person.Address.City -> OOPS! Контекст базы данных уже удален (так как загрузка выполнялась в отдельном потоке) и из-за отложенной загрузки Person.Address недоступен!
На этапе 3 человек загружается следующим образом:
using (DatabaseContext context = new DatabaseContext())
{
Person person = from p in context.Persons.FirstOrDefault();
return person;
}
Хорошо, я знаю, что (теоретически) я мог бы форсировать загрузку объекта Address двумя способами:
1) Используйте DbSet.Include, например:
context.Persons.Include("Address").FirstOrDefault();
2) Доступ к Person.Address, пока контекст базы данных еще жив, так как это приведет к загрузке адреса
Person person = context.Persons.FirstOrDefault();
Address address = person.Address;
return person;
Конечно, первое является предпочтительным решением, потому что оно не так страшно, как второе (просто получить доступ к свойству, чтобы принудительно загрузить данные, а затем отбросить результат, уродливо). Кроме того, в случае коллекции (например, список лиц), я должен был бы пройтись по коллекции и получить доступ к Адресу отдельно для каждого человека. Проблема с первым решением состоит в том, что только в DbSet есть метод Include, а во всех остальных коллекциях, возвращаемых по запросам, нет. Итак, допустим, у меня следующая структура базы данных
class Resource {}
class Person : Resource { public Address Address { get; set; } }
class Appointment { public IList<Resource> Resources { get; set; } }
и я хочу загрузить все конкретные встречи и включить адрес в каждый ресурс, который является человеком, у меня проблемы (или, по крайней мере, я не мог найти способ написать запрос для него). Это связано с тем, что context.Appointments.Resources не относится к типу DbSet, а представляет собой коллекцию ICollection, у которой нет метода Include. (Хорошо, возможно, в этом случае я мог бы написать запрос как-нибудь, начиная с context.Persons вместо context.Appointments, чтобы я мог использовать Include, но есть много сценариев, где это невозможно)
Так что в основном вопросы :
- Это правильный способ асинхронного доступа к базе данных?
- Как решить проблемы с ленивой загрузкой? Отключение отложенной загрузки не является решением (разве это может быть сделано только для определенных объектов?)