2012-02-02 3 views
4

Мы используем Entity Framework 4.2 с использованием первого подхода модели и генерации кода DbContext.Асинхронные запросы и ленивая загрузка в Entity Framework

Допустим, мы имеем следующие модели данных в рамках объекта:

class Person 
{ 
    public string Name { get; set; } 
    public Address Address { get; set; } 
} 

class Address 
{ 
    public string City; { get; set; } 
} 

Сценарий следующий:

  1. ViewModel хочет загрузить некоторые данные из базы данных
  2. Задача (асинхронной операция) создается для загрузки данных. Это состоит в том, что мы не хотим, чтобы пользовательский интерфейс зависал при загрузке данных.
  3. Задача (которая выполняется в отдельном потоке) создает новый контекст базы данных и загружает данные (например, объект Person) из базы данных
  4. Завершено завершение работы и контекст базы данных.
  5. Основная тема сообщается о завершении задачи. Основной поток может теперь получить доступ к объекту Person.
  6. Вид пытается показать имя и адрес человека в текстовом поле через привязку данных
  7. Точки зрения не обращается к Person.Name (никаких проблем здесь)
  8. Вида Допуски Person.Address.City -> OOPS! Контекст базы данных уже был удален (поскольку загрузка выполнялась в отдельном потоке) и из-за ленивой загрузки Person.Address недоступен!

В фазе 3 человек загружен в следующим образом:

using (DatabaseContext context = new DatabaseContext()) 
{ 
    Person person = from p in context.Persons.FirstOrDefault(); 
    return person; 
} 

Хорошо, я знаю, что (в теории) я мог бы заставить загрузку объекта Адрес двумя способами: 1) Использование DbSet.Include, например:

context.Persons.Include("Address").FirstOrDefault(); 

2) Person.Address Access в то время как контекст базы данных все еще жив, поскольку это заставит адрес должен быть загружен

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. (Хорошо, возможно, в этом случае я мог бы написать запрос, как-то, начиная с контекста. Персоны вместо контекста.Назначения, так что я мог бы использовать Include но есть много сценариев, где это невозможно)


Так в основном вопросы, которые:

  1. Является ли это правильный способом сделать асинхронный доступ к базе данных в первое место?
  2. Как решить проблемы с ленивой загрузкой? Поворот ленивой загрузки не является решением (если только это не может быть сделано только для конкретных организаций?)

ответ

2

Вы можете установить реализацию стратегии включения при запуске приложения или теста.

Сначала определим метод расширения: Включить, который определяет фактический включая механизм (по умолчанию ничего не делая)

/// <summary> 
/// Extension methods specifically for include since this is essential for DomainContext but not part of IQueryable by default 
/// </summary> 
public static class QueryableIncludeExtensions 
{ 
    public static IIncluder Includer = new NullIncluder(); 

    public static IQueryable<T> Include<T, TProperty>(this IQueryable<T> source, Expression<Func<T, TProperty>> path) 
     where T : class 
    { 
     return Includer.Include(source, path); 
    } 

    public interface IIncluder 
    { 
     IQueryable<T> Include<T, TProperty>(IQueryable<T> source, Expression<Func<T, TProperty>> path) where T : class; 
    } 

    internal class NullIncluder : IIncluder 
    { 
     public IQueryable<T> Include<T, TProperty>(IQueryable<T> source, Expression<Func<T, TProperty>> path) 
      where T : class 
     { 
      return source; 
     } 
    } 
} 

Затем создать EF реализацию конкретных includer как:

internal class DbIncluder : QueryableIncludeExtensions.IIncluder 
{ 
    public IQueryable<T> Include<T, TProperty>(IQueryable<T> source, Expression<Func<T, TProperty>> path) 
     where T : class 
    { 
     return DbExtensions.Include(source, path); 
    } 
} 

Наконец, крюк реализация DbIncluder в проекте, в котором вы нуждаетесь, в моем случае мне это понравилось:

public class DomainContext : DbContext, IDomainContext 
{ 
    static DomainContext() 
    { 
     // register the DbIncluder for making sure the right call to include is made (standard is null) 
     QueryableIncludeExtensions.Includer = new DbIncluder(); 
    } 

Теперь IQueryable всегда имеет метод расширения: Include available. Вы можете расширить его до IEnumerable, если хотите. Фактическая реализация просто устанавливается QueryableIncludeExtensions.Includer

Смежные вопросы