更改存储库以支持IQueryable(LINQ to SQL查询)

时间:2013-02-06 10:51:14

标签: linq-to-sql dependency-injection castle-windsor iqueryable

我继承了一个使用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
    }

任何帮助都非常感谢将它从'加载'后迭代到数据库中的每一行'到'select x where id = 1'!

2 个答案:

答案 0 :(得分:0)

如果要公开linq,可以停止使用存储库模式并直接使用Linq2Sql。原因是每个Linq To Sql提供商都有自己的自定义解决方案。因此,如果你公开LINQ,你会得到一个漏洞的抽象。那么使用抽象层是没有意义的。

您没有暴露LINQ,而是有两个选择:

  1. 实施规范模式
  2. 使用我在此处描述的存储库模式:http://blog.gauffin.org/2013/01/repository-pattern-done-right/

答案 1 :(得分:0)

所以,虽然它可能不再是真正的抽象,但重点是在不更新已编写的所有查询的情况下将linq的好处发送给sql。

所以,我使IRepository实现了IQueryable而不是IEnumerable。

然后在SqlClientRepository实现中,我可以调用AsQueryable()将Table转换为IQueryable,然后一切都很好,就像这样。

现在到处都有人编写了IRepository()。其中​​(qi =&gt; 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="SqlClientRepository{T}"/> class.</summary>
    /// <param name="source">A <see cref="Table{T}"/> 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();
    }