EF +通用存储库+加载相关实体:仅显式加载工作

Kit*_*Kit 8 c# ef-code-first entity-framework-4.1 dbcontext

我有两个简单的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; }
     // ...
}
Run Code Online (Sandbox Code Playgroud)

我在我DbContext的构造函数的子类中通过这个调用关闭了延迟加载:

Configuration.LazyLoadingEnabled = false;
Run Code Online (Sandbox Code Playgroud)

检索时X我试过了

context.Set<X>.Include("MyY").FirstOrDefault(x => ....);
Run Code Online (Sandbox Code Playgroud)

这没用.我试过了

var result = context.Set<X>.FirstOrDefault(x => ....);
context.Entry(result).Reference("MyY").Load();
Run Code Online (Sandbox Code Playgroud)

哪个有效,但需要两次往返数据库.我试过了

context.Set<X>.Select(x => new { X = x, Y = x.MyY }).FirstOrDefault(x => ...);
Run Code Online (Sandbox Code Playgroud)

这也有效,但"削弱"我的模型(通常投射到一个新类型并不是那么糟糕,但这些EF POCO的"形状"适用于我稍后将通过WCF发送的DTO).

我终于尝试virtual从另一个问题MyY答案中建议移除财产,但这根本没有效果.

最后,我想使用通用存储库模式.我最终得到的是以下设计,部分显示,支持显式加载(非首选)和eager-load,当修改为正常工作时.如何修改它以获得单个数据库往返急切加载?

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

示例用途:

// 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));
Run Code Online (Sandbox Code Playgroud)

基于答案的更新:

事实证明我的临时代码示例(上面的那些单行).我实际上已经缓存.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));
    }
Run Code Online (Sandbox Code Playgroud)

Lad*_*nka 1

这应该有效:

X entity = context.Set<X>().Include(x => x.MyY).FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)

如果没有,问题一定出在其他地方。

如果您需要一些急切加载策略,请检查此答案