EF + универсальный репозиторий + загрузка связанной сущности: работает только явная загрузка - PullRequest
8 голосов
/ 06 июля 2011

У меня есть два простых класса POCO; Я пытаюсь получить свойство MyY ниже гидратированного с экземпляром Y. Я пытался сделать это несколькими способами и думаю, что мне не хватает чего-то очевидного или простого.

public class X
{
     public int Id { get; set;}
     public virtual Y MyY { get; set; }
}

public class Y
{
     public int Id { get; set; }
     // ...
}

Я отключил ленивую загрузку с помощью этого вызова в своем подклассе конструктора DbContext:

Configuration.LazyLoadingEnabled = false;

При получении X я пытался

context.Set<X>.Include("MyY").FirstOrDefault(x => ....);

который не работал. Я пытался

var result = context.Set<X>.FirstOrDefault(x => ....);
context.Entry(result).Reference("MyY").Load();

, который работает, но требует двух обращений к базе данных. Я пытался

context.Set<X>.Select(x => new { X = x, Y = x.MyY }).FirstOrDefault(x => ...);

, который также работает, но «ослабляет» мою модель (обычно проецирование на новый тип не так уж и плохо, но «форма» этих EF POCO отлично работает для DTO, которые я буду отправлять через WCF позже).

В конце концов я попытался удалить virtual из свойства MyY, как предложено в ответе на другой вопрос, но это никак не отразилось.

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

public class EFRepository : IRepository
{
    public T Get<T>(Specification<T> specification) where T : class, IEntity
    {
        var result = ApplyEagerLoading(context.Set<T>()).FirstOrDefault(specification.IsMatch);
        ApplyPostQueryLoading(new List<T> { result });
        return result;
    }

    // doesn't really seem to work yet...
    private DbSet<T> ApplyEagerLoading<T>(DbSet<T> set) where T : class, IEntity
    {
        var ls = loadSpecs.GetOrAdd(typeof(T), () => new List<LoadSpec>());
        foreach (var spec in ls.Where(s => !s.ExplicitLoad))
            set.Include(spec.PropertyName);
        return set;
    }

    // works, but wrong on so many levels...
    private void ApplyPostQueryLoading<T>(IEnumerable<T> entities) where T : class, IEntity
    {
        var ls = loadSpecs.GetOrAdd(typeof(T), () => new List<LoadSpec>());
        foreach (var e in entities)
            foreach (var spec in ls.Where(s => s.ExplicitLoad))
                if (spec.IsCollection)
                    context.Entry(e).Collection(spec.PropertyName).Load();
                else
                    context.Entry(e).Reference(spec.PropertyName).Load();
    }

    private readonly IDictionary<Type, IList<LoadSpec>> loadSpecs = new Dictionary<Type, IList<LoadSpec>>();

    private class LoadSpec
    {
        internal string PropertyName;
        internal bool ExplicitLoad;
        internal bool IsCollection;
    }
}

Пример использования:

// add a rule to load MyY explicitly
repository.AddLoadRule<X>(x => x.MyY, explicit:true, isCollection:false)
...
var x = repository.Get<X>(new Specification<X>(x => x.Id == 5));

// add a rule to load MyY with X
repository.AddLoadRule<X>(x => x.MyY, explicit:false)
...
// x.MyY will be null! Doesn't work!
var x = repository.Get<X>(new Specification<X>(x => x.Id == 5));

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

Оказывается, мои примеры временного кода соврали (те, что указаны выше). Я фактически кэшировал результат .Include в локальной переменной, но применил .FirstOrDefault к .Set<X>, а не результат .Include. Вот исправление к ApplyEagerLoading, которое отражает то, что другие предложили в связанных вопросах:

    private IQueryable<T> ApplyEagerLoading<T>(IEnumerable<T> set) where T : class, IEntity
    {
        var ls = loadSpecs.GetOrAdd(typeof(T), () => new List<LoadSpec>());
        var query = set.AsQueryable();
        return ls.Where(s => !s.ExplicitLoad).Aggregate(query, (current, spec) => current.Include(spec.PropertyName));
    }

1 Ответ

1 голос
/ 06 июля 2011

Это должно работать:

X entity = context.Set<X>().Include(x => x.MyY).FirstOrDefault();

ЕСЛИ проблема не в другом месте.

Если вам нужна какая-то активная стратегия загрузки, проверьте этот ответ .

...