У меня есть два простых класса 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.Include("MyY").FirstOrDefault(x => ....);
, но это не помогло. Я попробовал
var result = context.Set.FirstOrDefault(x => ....);
context.Entry(result).Reference("MyY").Load();
, который работает, но требует двух обращений к базе данных. Я попробовал
context.Set.Select(x => new { X = x, Y = x.MyY }).FirstOrDefault(x => ...);
, который тоже работает, но «ослабляет» мою модель (обычно проектирование на новый тип не так уж и плохо, но «форма» этих EF POCO отлично работает для DTO, которые я отправлю через WCF позже) .
Наконец, я попытался удалить virtual
из свойства MyY
, как было предложено в ответе на другой вопрос, но это не дало никакого эффекта.
Наконец, я хочу использовать общий шаблон репозитория. В итоге я получил следующий дизайн, показанный частично, который поддерживает явную загрузку (не рекомендуется) и активную загрузку при изменении для правильной работы . Как мне изменить его, чтобы получить единый db-тур туда и обратно -load?
public class EFRepository : IRepository
{
public T Get(Specification specification) where T : class, IEntity
{
var result = ApplyEagerLoading(context.Set()).FirstOrDefault(specification.IsMatch);
ApplyPostQueryLoading(new List { result });
return result;
}
// doesn't really seem to work yet...
private DbSet ApplyEagerLoading(DbSet set) where T : class, IEntity
{
var ls = loadSpecs.GetOrAdd(typeof(T), () => new List());
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(IEnumerable entities) where T : class, IEntity
{
var ls = loadSpecs.GetOrAdd(typeof(T), () => new List());
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> loadSpecs = new Dictionary>();
private class LoadSpec
{
internal string PropertyName;
internal bool ExplicitLoad;
internal bool IsCollection;
}
}
Пример использования:
// add a rule to load MyY explicitly
repository.AddLoadRule(x => x.MyY, explicit:true, isCollection:false)
...
var x = repository.Get(new Specification(x => x.Id == 5));
// add a rule to load MyY with X
repository.AddLoadRule(x => x.MyY, explicit:false)
...
// x.MyY will be null! Doesn't work!
var x = repository.Get(new Specification(x => x.Id == 5));
Оказывается, мои примеры временного кода лгали (те однострочные строки выше). Я фактически кэшировал результат .Include
в локальной переменной, но применил .FirstOrDefault
к .Set
, а не результат .Включить
. Вот исправление для ApplyEagerLoading
, которое отражает то, что другие предлагали в связанных вопросах:
private IQueryable ApplyEagerLoading(IEnumerable set) where T : class, IEntity
{
var ls = loadSpecs.GetOrAdd(typeof(T), () => new List());
var query = set.AsQueryable();
return ls.Where(s => !s.ExplicitLoad).Aggregate(query, (current, spec) => current.Include(spec.PropertyName));
}