EF +通用存储库+加载相关实体:仅显式加载有效
本文关键字:加载 有效 实体 存储 EF | 更新日期: 2023-09-27 18:01:35
我有两个简单的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的"形状"对于我稍后将通过WCF发送的dto来说是完美的)。
我终于试着从MyY
属性中删除virtual
,正如另一个问题的答案所建议的那样,但这根本没有效果。
最后,我想使用通用存储库模式。我最终得到的是如下部分所示的设计,它在修改以正常工作时支持显式加载(不是首选)和急切加载。我如何修改它以获得单个db往返急切负载?
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
的结果,但对.Set<X>
应用了.FirstOrDefault
,而不是.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));
}
应该可以:
X entity = context.Set<X>().Include(x => x.MyY).FirstOrDefault();
如果没有,问题一定在别处。