使用LINQ-to-SQL,这个存储库模式是否有效?

我目前正在阅读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 Products { get; } } public class SqlProductsRepository : IProductsRepository { private Table productsTable; public SqlProductsRepository(string connectionString) { productsTable = new DataContext(connectionString).GetTable(); } public IQueryable 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#中Repository模式的其他实现可能会提供更好的性能?

我可以理解约翰尼斯更加严格控制SQL执行的愿望,并且我在我的应用程序中实现了我有时称之为“懒人锚点”的实现。

我使用自定义LazyListLazyItem类的组合来封装延迟初始化:

  • LazyList包装了IList集合的IQueryablefunction,但最大化了LinqToSql的一些延迟执行function和
  • LazyItem将使用LinqToSql IQueryable或通用Func方法来包装对单个项的延迟调用,以执行其他延迟的代码。

这是一个例子 – 我有这个模型对象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 { get; set; } public LazyItem PdfDoc { get; set; } } 

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

所以,如果我做这样的事情:

 Console.WriteLine(anAnnouncement.Title); 

..i可以知道我只是从db加载了立即Announcement对象的数据。 但如果在以下行中我需要这样做:

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

..i可以确定LazyItem知道如何去获取剩余的数据。

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

LINQ但不是LinqToSql

你会发现有时你想做一些花哨的LINQ查询,当执行流向LinqToSql提供程序时会发生barf。 这是因为LinqToSql通过将有效的LINQ查询逻辑转换为T-SQL代码来工作,有时这并不总是可行的。

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

  private IQueryable 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()项目,即使我将要过滤它们。这是一个我的filter表达式无法正确呈现给SQL的实例,所以我需要在内存中过滤它。这就是我可能称之为LinqToSql的性能限制,就延迟执行和延迟加载而言 – 但我只有一个少数这些WARNING: HEAVY SQL QUERY!在我的应用程序中使用WARNING: HEAVY SQL QUERY!块,我认为进一步的智能重构可以完全消除它们。

最后 ,如果您愿意,LinqToSql可以在大型应用程序中创建一个优秀的数据访问提供程序。 我发现要获得我想要的结果并抽象并隔离某些我需要在这里和那里添加代码的东西。 而且我想要更多地控制来自LinqToSql的实际SQL性能,我添加了智能来获得所需的结果。 因此,如果您了解LinqToSql的工作原理,那么对于需要数据库查询优化的繁重应用程序,IMHO LinqToSql是完全可以的。 我的设计最初是基于Rob的店面教程,所以如果你需要更多关于我的rants的解释,你可能会觉得它很有用。

如果你想使用上面的那些懒惰的类,你可以在这里和这里得到它们。

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

如果你想这么说,这是有效的。 Repository公开了一个IQueryable接口,它基本上代表任何LINQ数据提供程序(在本例中为Linq2Sql)。

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

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

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

是的linq2sql将生成魔法,使其更高效。 这取决于您使用IQueryable接口。 如果要检查钳位SQL事件探查器,您可以看到它生成相应的查询。

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

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

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

鉴于该数据库,我认为你不能写任何更有效的东西。 但是,在大多数真实数据库中,您的类别将位于单独的表中以保持干燥状态。