更改存储库以支持 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 查询)的主要内容,如果未能解决你的问题,请参考以下文章

将 IQueryable 与 Linq 一起使用

函数中的LINQ查询,该函数使用param来更改结果集

如何更改 Android Leanback 库以支持背景视频而不是背景图像

当我只需要计数而不读取 Document-Db 数据库中的所有文档时,如何使用 Linq 构造 IQueryable 查询?

MVC / LINQ /合并Iqueryable对象[关闭]

使用 LINQ 的 IQueryable 左外连接的扩展方法