【问题标题】:Is this Repository pattern efficient with LINQ-to-SQL?这种存储库模式对 LINQ-to-SQL 有效吗?
【发布时间】:2010-12-12 18:43:24
【问题描述】:

我目前正在阅读 Pro Asp.Net MVC Framework 一书。在书中,作者建议使用类似于以下的存储库模式。

[Table(Name = "Products")]
public class Product
{
    [Column(IsPrimaryKey = true, 
            IsDbGenerated = true, 
            AutoSync = AutoSync.OnInsert)]
    public int ProductId { get; set; }
    [Column] public string Name { get; set; }
    [Column] public string Description { get; set; }
    [Column] public decimal Price { get; set; }
    [Column] public string Category { get; set; }
}

public interface IProductsRepository
{
    IQueryable<Product> Products { get; }
}

public class SqlProductsRepository : IProductsRepository
{
    private Table<Product> productsTable;

    public SqlProductsRepository(string connectionString)
    {
        productsTable = new DataContext(connectionString).GetTable<Product>();
    }

    public IQueryable<Product> Products
    {
        get { return productsTable; }
    }
}

然后通过以下方式访问数据:

public ViewResult List(string category)
{
    var productsInCategory =  (category == null) ? productsRepository.Products : productsRepository.Products.Where(p => p.Category == category);

    return View(productsInCategory);
}

这是访问数据的有效方法吗?是要从数据库中检索整个表并在内存中进行过滤,还是链式 Where() 方法会导致一些 LINQ 魔术来创建基于 lambda 的优化查询?

最后,当通过 LINQ-to-SQL 连接时,C# 中存储库模式的其他哪些实现可能会提供更好的性能?

【问题讨论】:

  • 当您说“C# 中存储库模式的其他哪些实现可能会提供更好的性能”时,您指的是其他哪些数据访问提供程序?

标签: c# performance linq-to-sql architecture repository-pattern


【解决方案1】:

我最近也读过那本书,这是我运行示例代码时生成的 SQL:

SELECT [t1].[Category] 
FROM ( SELECT DISTINCT [t0].[Category] 
FROM [Products] AS [t0] ) AS [t1] ORDER BY [t1].[Category]

鉴于该数据库,我认为您无法编写任何更有效的东西。然而,在大多数真实数据库中,您的类别将位于单独的表中以保持干燥。

【讨论】:

    【解决方案2】:

    我可以理解Johannes' 希望更严格地控​​制 SQL 的执行,并且通过实施我有时称之为“惰性锚点”的东西,我已经能够在我的应用程序中做到这一点。

    我使用自定义 LazyList&lt;T&gt;LazyItem&lt;T&gt; 的组合来封装延迟初始化:

    • LazyList&lt;T&gt; 包装了 IList 集合的 IQueryable 功能,但最大化了 LinqToSql 的一些延迟执行功能和
    • LazyItem&lt;T&gt; 将使用 LinqToSql IQueryable 或通用 Func&lt;T&gt; 方法包装单个项目的惰性调用,以执行其他延迟代码。

    这是一个例子——我有这个模型对象Announcement,它可能有一个附加的图像或pdf文档:

    public class Announcement : //..
    {
        public int ID { get; set; }
        public string Title { get; set; }
        public AnnouncementCategory Category { get; set; }
        public string Body { get; set; }
        public LazyItem<Image> Image { get; set; }
        public LazyItem<PdfDoc> PdfDoc { get; set; }
    }
    

    ImagePdfDoc 类继承了一个类型 File,其中包含包含二进制数据的 byte[]。这个二进制数据很重,每次我想要Announcement 时,我可能并不总是需要从数据库返回它。所以我想保持我的对象图“锚定”而不是“填充”(如果你愿意的话)。

    所以如果我这样做:

    Console.WriteLine(anAnnouncement.Title);
    

    ..我知道我只从 db 加载了直接Announcement 对象的数据。但如果在以下行我需要这样做:

    Console.WriteLine(anAnnouncement.Image.Inner.Width);
    

    ..我可以确定LazyItem&lt;T&gt; 知道如何去获取其余数据。

    另一个很大的好处是这些“惰性”类可以隐藏底层存储库的特定实现,因此我不必使用 LinqToSql。我是(使用 LinqToSql)在我从中剪切示例的应用程序的情况下,但插入另一个数据源(甚至可能不使用存储库模式的完全不同的数据层)会很容易。

    LINQ 但不是 LinqToSql

    您会发现有时您想要执行一些奇特的 LINQ 查询,当执行流向 LinqToSql 提供程序时会发生这种情况。这是因为 LinqToSql 的工作原理是将有效的 LINQ 查询逻辑转换为 T-SQL 代码,但有时这并不总是可行的。

    例如,我有这个函数,我想要一个 IQueryable 的结果来自:

        private IQueryable<Event> GetLatestSortedEvents()
        {
            // TODO: WARNING: HEAVY SQL QUERY! fix
            return this.GetSortedEvents().ToList()
                .Where(ModelExtensions.Event.IsUpcomingEvent())
                .AsQueryable();
        }
    

    为什么该代码不转换为 SQL 并不重要,但请相信我,IsUpcomingEvent() 谓词中的条件涉及许多 DateTime 比较,对于 LinqToSql 转换为 T-SQL 来说太复杂了.

    通过使用.ToList(),然后使用条件(.Where(..),然后使用.AsQueryable(),我有效地告诉LinqToSql 我需要所有.GetSortedEvents() 项目,即使我要过滤它们。这是我的过滤器表达式无法正确呈现给 SQL 的实例,因此我需要在内存中对其进行过滤。就延迟执行和延迟加载而言,这可能是我所说的 LinqToSql 性能的限制——但我的应用程序中只有少量的 WARNING: HEAVY SQL QUERY! 块,我认为进一步的智能重构可以完全消除它们。

    最后,如果您愿意,LinqToSql 可以在大型应用程序中成为出色的数据访问提供程序。我发现为了得到我想要的结果并抽象出来并隔离我需要在这里和那里添加代码的某些东西。在我想要更多地控制 LinqToSql 的实际 SQL 性能的地方,我添加了智能来获得所需的结果。所以恕我直言,如果您了解 LinqToSql 的工作原理,那么对于需要数据库查询优化的重型应用程序来说,恕我直言 LinqToSql 是完全可以的。我的设计最初基于 Rob 的 Storefront tutorial,所以如果您需要更多关于我上面的咆哮的解释,您可能会发现它很有用。

    如果你想使用上面的那些惰性类,你可以得到它们herehere

    【讨论】:

    • 谢谢。我之前在 Storefront 中查看过 Connery 的实现并喜欢它,但我一直在四处寻找其他实现,看看我是否更喜欢其他东西。我还检查了他在持久层中使用 ADO 的 Kona 实现。我认为 ADO 解决方案对于我所考虑的项目来说太过分了。我是 LinqToSql 的忠实粉丝,我更愿意使用它。既然我对 Linq 更熟悉了,我想是时候整理一下 Linq 书了,然后再读一遍,目标是这次更好地学习内部知识。
    • 那就试试 SubSonic。它应该比 LinqToSql 更成熟,帮助你更快地发展。我还没试过,但我想在你的位置上我可能 - subsonicproject.com
    【解决方案3】:

    是的 linq2sql 会产生魔法以提高效率。这取决于您使用 IQueryable 接口。如果你想检查一下 SQL 分析器,你可以看到它生成了适当的查询。

    我建议引入一个服务层来抽象您对 linq2sql 的依赖。

    【讨论】:

      【解决方案4】:

      这是一种有效的方法吗? 访问数据?是整张桌子 将从 数据库并在内存中过滤或者是 链式 Where() 方法将 导致一些 LINQ 魔法创建一个 基于 lambda 的优化查询?

      如果你愿意的话,它是有效的。存储库公开了一个IQueryable 接口,它基本上代表任何 LINQ 数据提供程序(在本例中为 Linq2Sql)。

      在您开始对结果进行迭代时执行查询。 IQueryable 因此支持查询组合。您可以将任何.Where().GroupBy().OrderBy() 调用添加到查询中,它将由数据库进行统计。

      如果您在查询中放入枚举,例如 .ToList(),则之后的所有内容都将发生在内存中 (LinqToObjects)。

      但我认为存储库实现是无用的。我希望我的存储库控制查询执行,这在公开 IQueryable 时是不可能的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-04-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-21
        相关资源
        最近更新 更多