使用仓储和工作单元查询EF 5.0

3
我已经在一个项目上工作了一段时间。最初,我阅读了几本书和在线文章中的一堆模式,然后花费了大量时间规划程序架构。我决定将EF 5.0与存储库/工作单元、MVVM和WPF结合起来。
我的初步测试表明它看起来完美运行,于是我承诺了我的模式选择,并花了一个月去实现它。现在我快做完了,但我遇到了一些问题。
其中一个无法解决的问题是我似乎无法构建复杂查询。无论我尝试什么,都会得到一个错误或另一个错误。
该程序使用Entity Framework 5.0连接到我们公司的MS Sql 2005 Server上的数据库。从那里,为每个实体存在一个存储库类。每个存储库在其构造函数中接收一个上下文,并实现标准接口:
interface IRepository<T> where T : class
{
    IEnumerable<T> GetAll();
    IEnumerable<T> Query(Expression<Func<T, bool>> filter);
    void Add(T entity);
    void Remove(T entity);
}

基类的样子如下:
public abstract class Repository<T> : IRepository<T> where T : class
{
    protected IObjectSet<T> _objectSet;

    public Repository(ObjectContext context)
    {
        _objectSet = context.CreateObjectSet<T>();
    }

    public IEnumerable<T> GetAll()
    {
        return _objectSet;
    }

    public List<T> ToList()
    {
        return _objectSet.ToList<T>();
    }

    public IEnumerable<T> Query(Expression<Func<T, bool>> filter)
    {
        return _objectSet.Where(filter);
    }

    public void Add(T entity)
    {
        _objectSet.AddObject(entity);
    }

    public void Remove(T entity)
    {
        _objectSet.DeleteObject(entity);
    }

    public void Update(T entity)
    {
        _objectSet.UpdateModel(entity);
    }

    public abstract void Upsert(T entity);


}

一个具体的仓库看起来像:

public class UserAccountRepository : Repository<UserAccount>
{
    public UserAccountRepository(ObjectContext context)
        : base(context)
    {
    }

    public UserAccount GetById(int id)
    {
        return _objectSet.SingleOrDefault(user => user.ID == id);
    }

    public override void Upsert(UserAccount user)
    {
        if (user == null)
            throw new ArgumentNullException();
        if (user.ID == 0 || GetById(user.ID) == null)
            Add(user);
        else
            Update(user);
    }
}

下一个类是UnitOfWork。这个类包含了每个仓库的懒加载实例,并实例化它自己的ObjectContext,该ObjectContext在UnitOfWork的生命周期内保持活动状态。这个上下文被传递给仓库的构造函数。
通过公共可访问的仓库进行更改,然后通过UnitOfWork类的公共方法保存或销毁。
UnitOfWork类: (我只包括了UserAccount、ComponentPermissions和Component。一个UserAccount可以访问多个具有不同权限级别的Component,这些信息存储在中间表ComponentPermissions中。)
public class UnitOfWork : IDisposable
{


    #region Fields

    private readonly ObjectContext _context;

    private ComponentPermissionsRepository _componentPermissions;
    private ComponentRepository _components;
    private UserAccountRepository _userAccounts;

    #endregion //Fields


    #region Constructor

    public UnitOfWork()
    {
        _context = (new DataAccess.Entities() as IObjectContextAdapter).ObjectContext;
    }

    #endregion //Constructor


    #region Public Interface

    public ComponentPermissionsRepository ComponentPermissions
    {
        get
        {
            if (_componentPermissions == null)
            {
                _componentPermissions = new ComponentPermissionsRepository(_context);
            }

            return _componentPermissions;
        }

    }

    public ComponentRepository Components
    {
        get
        {
            if (_components == null)
            {
                _components = new ComponentRepository(_context);
            }

            return _components;
        }

    }


    public UserAccountRepository UserAccounts
    {
        get
        {
            if (_userAccounts == null)
            {
                _userAccounts = new UserAccountRepository(_context);
            }

            return _userAccounts;
        }

    }

    public void Commit()
    {
        _context.SaveChanges();
    }

    public void Dispose()
    {
        _userAccount = null;
        _component = null;
        _componentPermissions = null;
        _context.Dispose();
    }

    #endregion //Public Interface


}

我已经阅读了许多关于使用EF查询的文章,但是我所读的内容似乎在应用到上述架构时都不起作用... 我感觉自己把自己绊倒了,但是我不知道出了哪个问题。
我想要获取给定单个UserAccount时可访问组件及其对应的ComponentPermissions列表。我该如何进行这种查询?EF导航属性似乎只适用于直接相邻的关系...我是否遗漏了一些明显的东西?
编辑
已经有人指出我应该更具体地说明我的问题。我无法克服的第一个障碍是所有示例似乎都遵循以下方式:
context.Component.Include( c => c.ComponentPermissions.Select(cps => cps.UserAccount)).ToList();

看起来不错,但我没有暴露我的上下文,即使我这样做了,它也没有任何实体类型,比如组件。但现在我已经将GetAll()更改为IQueryable,我设法拼凑出了以下内容:

List<Component> comps = uow.Components.GetAll().Include(c => c.UserPermissions.Select(cps => cps.UserAccount)).ToList();
ComponentPermissions compPer = comps[0].UserPermissions.Select(up => up).Where(up => up.UserAccount.UserName == userAccount).FirstOrDefault();

这将返回与给定用户帐户相关的第一个ComponentPermissions对象...但即使它有点奏效(我说有点奏效,因为它只得到了一个ComponentPermissions对象,而不是所有对象...但我无法弄清楚如何做到这一点),它似乎是错误的。

那么,看着这个,有人能解释一下正确的做法吗?


这是您所采用的模式的优秀描述,但没有关于具体错误的任何信息。哪些查询失败了?出现了什么错误?简单查询中是否没有模式也能正常工作?您有进行过跟踪吗? - Dave Swersky
我已经在这个问题上苦苦思索了2-3天。我尝试了很多方法,以至于很难记住其中的任何一个。我尝试使用嵌套包含、一个名为IncludeMany的扩展方法、通过NavigationProperties进行直接引用...老实说,我不知道从哪里开始针对EF进行这种查询。如果我能看到一个工作示例,那么我就可以专注于学习它是如何工作的,而不是在黑暗中盲目地挣扎,甚至不知道我在寻找什么... - Chronicide
1
你采用了许多开发人员使用的常见模式,这个模式本身没有问题。听起来你在遇到复杂查询和扩展方法方面遇到了问题。如果你提出一个关于特定查询及其错误的具体问题,你将从StackOverflow中得到更好的解决办法。我认为你会发现问题与查询有关,而不是模式本身。 - Dave Swersky
如果您在存储库中将 IEnumerable<T> 更改为 IQueryable<T>,是否有所帮助?目前,对存储库进行的任何查询都将整个表拉到内存中,然后使用LINQ-to-objects。 - Richard Deeming
1
你的实现不是仓储模式旨在实现的适当抽象。请阅读我的答案:http://stackoverflow.com/a/13394334/70386 - jgauffin
显示剩余2条评论
1个回答

1

嗯,我感觉自己有点蠢...

我是通过以下方式在UnitOfWork中获取ObjectContext的:

_context = (new DataAccess.Entities() as IObjectContextAdapter).ObjectContext;

我本应该只使用DataAccess.Entities()的实例(EF为我的数据创建的自定义上下文)来工作。

我不得不重新设计我的存储库,但另一个好处是我认为现在可以摆脱具体类,只使用通用存储库。

我遇到所有这些问题是因为我将DataAccess.Entities()转换为ObjectContext,它不包含任何特定的实体类型...所以我试图从存储库查询,但只加载了它们自己类型的数据。

一行代码...三天的头痛。典型。


另一个需要考虑的方面(正如在问题评论中已经提到的)是将“Query”的返回类型从“IEnumerable”更改为“IQueryable”,如果您正在对结果链接复杂查询,那么这应该会带来性能上的好处,因为您不必在原始“Where”子句的整个集合上操作。例如:var results = repo.Where(...); ... results.Single(...),尽管我想你也可以使用.GetAll来达到相同的效果。 - drzaus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接