更改存储库以支持 IQueryable(LINQ to SQL 查询)
Posted
技术标签:
【中文标题】更改存储库以支持 IQueryable(LINQ to SQL 查询)【英文标题】:Changing IRepository to support IQueryable (LINQtoSQL queries) 【发布时间】:2013-01-21 12:58:58 【问题描述】:我继承了一个系统,该系统使用 Castle Windsor IRepository 模式从 DAL 中抽象出来,即 LinqToSQL。
我可以看到的主要问题是 IRepository 只实现了 IEnumerable。因此,即使是最简单的查询也必须从数据表中加载所有数据,才能返回单个对象。
目前使用情况如下
using (IUnitOfWork context2 = IocServiceFactory.Resolve<IUnitOfWork>())
KpiFormDocumentEntry entry = context2.GetRepository<KpiFormDocumentEntry>().FindById(id, KpiFormDocumentEntry.LoadOptions.FormItem);
这使用 lambda 来过滤,就像这样
public static KpiFormDocumentEntry FindById(this IRepository<KpiFormDocumentEntry> source, int id, KpiFormDocumentEntry.LoadOptions loadOptions)
return source.Where( qi => qi.Id == id ).LoadWith( loadOptions ).FirstOrDefault();
所以它变成了一个不错的扩展方法。
我的问题是,我如何才能使用相同的接口/模式等,同时实现 IQueryable 以正确支持 LinqToSQL 并获得一些重大的性能改进?
IRepository 目前的实现/接口如下
public interface IRepository<T> : IEnumerable<T> where T : class
void Add(T entity);
void AddMany(IEnumerable<T> entities);
void Delete(T entity);
void DeleteMany(IEnumerable<T> entities);
IEnumerable<T> All();
IEnumerable<T> Find(Func<T, bool> predicate);
T FindFirst(Func<T, bool> predicate);
然后这是由像这样的 SqlClientRepository 实现的
public sealed class SqlClientRepository<T> : IRepository<T> where T : class
private readonly Table<T> _source;
internal SqlClientRepository(Table<T> source)
if( source == null ) throw new ArgumentNullException( "source", Gratte.Aurora.SHlib.labelText("All_TableIsNull",1) );
_source = source;
//removed add delete etc
public IEnumerable<T> All()
return _source;
public IEnumerator<T> GetEnumerator()
return _source.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
return GetEnumerator();
目前的问题是,在我们上面的示例中,.Where 正在调用“GetEnumerator”,然后它将所有行加载到内存中,然后查找我们需要的行。
如果我更改 IRepository 以实现 IQueryable,我将无法实现所需的三个方法,因为它们在 Table 类中不是公共的。
我认为我应该将 SQLClientRepository 更改为这样定义
public sealed class SqlClientRepository<T> : IQueryable<T>, IRepository<T> where T : class
然后实现必要的方法,但我不知道如何传递表达式等,因为它们是 Table 类的私有成员,就像这样
public override Type ElementType
get return _source.ElementType; //Won't work as ElementType is private
public override Expression Expression
get return _source.Expression; //Won't work as Expression is private
public override IQueryProvider Provider
get return _source.Provider; //Won't work as Provider is private
非常感谢您将其从“加载后遍历数据库中的每一行”转换为“选择 x 其中 id=1”!
【问题讨论】:
【参考方案1】:如果你想公开 linq,你可以停止使用存储库模式,直接使用 Linq2Sql。原因是每个 Linq To Sql 提供程序都有自己的自定义解决方案。因此,如果您公开 LINQ,您会得到一个泄漏的抽象。那么使用抽象层就没有意义了。
您有两个选择,而不是公开 LINQ:
-
实现规范模式
使用我在这里描述的存储库模式:http://blog.gauffin.org/2013/01/repository-pattern-done-right/
【讨论】:
谢谢。是的,实际上这正是我在某些地方所做的,但在这方面,我希望在不重写我们已经完成的所有内容的情况下获得好处。我会看看你的文章。谢谢。【参考方案2】:因此,虽然它可能不再是一个真正的抽象,但重点是在不更新所有已编写的查询的情况下获得 linq to sql 的好处。
所以,我让 IRepository 实现了 IQueryable 而不是 IEnumerable。
然后在SqlClientRepository实现中,我可以调用AsQueryable()将Table强制转换为IQueryable,然后就一切顺利,像这样。
现在到处有人写了 IRepository().Where(qi => qi.id = id) 或类似的东西,它实际上将 ID 传递给 sql server 并且只拉回一条记录,而不是全部,然后循环寻找正确的。
/// <summary>Provides the ability to query and access entities within a SQL Server data store.</summary>
/// <typeparam name="T">The type of entity in the repository.</typeparam>
public sealed class SqlClientRepository<T> : IRepository<T> where T : class
private readonly Table<T> _source;
private readonly IQueryable<T> _sourceQuery;
IQueryable<T> Query()
return (IQueryable<T>)_source;
public Type ElementType
get return _sourceQuery.GetType();
public Expression Expression
get return _sourceQuery.Expression;
public IQueryProvider Provider
get return _sourceQuery.Provider;
/// <summary>Initializes a new instance of the <see cref="SqlClientRepositoryT"/> class.</summary>
/// <param name="source">A <see cref="TableT"/> to a collection representing the entities from a SQL Server data store.</param>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is a <c>null</c> reference (<c>Nothing</c> in Visual Basic).</exception>
internal SqlClientRepository(Table<T> source)
if( source == null ) throw new ArgumentNullException( "source", "All_TableIsNull" ) );
_source = source;
_sourceQuery = _source.AsQueryable();
【讨论】:
以上是关于更改存储库以支持 IQueryable(LINQ to SQL 查询)的主要内容,如果未能解决你的问题,请参考以下文章
如何更改 Android Leanback 库以支持背景视频而不是背景图像
当我只需要计数而不读取 Document-Db 数据库中的所有文档时,如何使用 Linq 构造 IQueryable 查询?