我目前正在阅读Pro Asp.Net MVC框架书。在书中,作者建议使用类似于以下内容的存储库模式。
[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#中的Repository模式的其他哪些实现可能会提供更好的性能?
最佳答案
我可以理解Johannes'希望更严格地控制SQL的执行,并且通过实现我有时称为“惰性锚点”的方法,我已经能够在我的应用程序中做到这一点。
我使用定制的LazyList<T>
和LazyItem<T>
类的组合来封装延迟初始化:LazyList<T>
包装了IQueryable
集合的IList
功能,但最大化了LinqToSql的Deferred Execution函数,以及LazyItem<T>
将使用LinqToSql IQueryable
或用于执行其他延迟代码的通用Func<T>
方法包装单个项目的惰性调用。
这是一个示例-我有这个模型对象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; }
}
Image
和PdfDoc
类继承形成的类型File
,该类型包含包含二进制数据的byte[]
。这个二进制数据很重,我可能不需要总是每次都需要Announcement
从数据库返回它。所以我想保持对象图“锚定”而不是“填充”(如果您愿意)。所以,如果我做这样的事情:
Console.WriteLine(anAnnouncement.Title);
..i可以知道我只是从db加载了直接
Announcement
对象的数据。但是,如果在以下行中,我需要这样做:Console.WriteLine(anAnnouncement.Image.Inner.Width);
..i可以确保
LazyItem<T>
知道如何获取其余数据。另一个很大的好处是,这些“惰性”类可以隐藏基础存储库的特定实现,因此我不必一定要使用LinqToSql。对于正在从中剪切示例的应用程序,我正在(使用LinqToSql),但是插入另一个数据源(甚至可能完全不使用存储库模式的完全不同的数据层)将很容易。
LINQ但不是LinqToSql
您会发现,有时您需要执行一些执行到LinqToSql提供程序的,恰好是barf的精美LINQ查询。这是因为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,因此,如果您需要以上有关我的言论的更多解释,您可能会发现它很有用。
而且,如果您想使用上面的那些惰性类,则可以将它们获取为here和here。