I’m currently reading the book Pro Asp.Net MVC Framework. In the book, the author suggests using a repository pattern similar to the following.
[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; }
}
}
Data is then accessed in the following manner:
public ViewResult List(string category)
{
var productsInCategory = (category == null) ? productsRepository.Products : productsRepository.Products.Where(p => p.Category == category);
return View(productsInCategory);
}
Is this an efficient means of accessing data? Is the entire table going to be retrieved from the database and filtered in memory or is the chained Where() method going to cause some LINQ magic to create an optimized query based on the lambda?
Finally, what other implementations of the Repository pattern in C# might provide better performance when hooked up via LINQ-to-SQL?
I can understand Johannes’ desire to control the execution of the SQL more tightly and with the implementation of what i sometimes call ‘lazy anchor points’ i have been able to do that in my app.
I use a combination of custom
LazyList<T>andLazyItem<T>classes that encapsulate lazy initialization:LazyList<T>wraps theIQueryablefunctionality of anIListcollection but maximises some of LinqToSql’s Deferred Execution functions andLazyItem<T>will wrap a lazy invocation of a single item using the LinqToSqlIQueryableor a genericFunc<T>method for executing other code deferred.Here is an example – i have this model object
Announcementwhich may have an attached image or pdf document:The
ImageandPdfDocclasses inherit form a typeFilethat contains thebyte[]containing the binary data. This binary data is heavy and i might not always need it returned from the DB every time i want anAnnouncement. So i want to keep my object graph ‘anchored’ but not ‘populated’ (if you like).So if i do something like this:
..i can knowing that i have only loaded from by db the data for the immediate
Announcementobject. But if on the following line i need to do this:..i can be sure that the
LazyItem<T>knows how to go and get the rest of the data.Another great benefit is that these ‘lazy’ classes can hide the particular implementation of the underlying repository so i don’t necessarily have to be using LinqToSql. I am (using LinqToSql) in the case of the app I’m cutting examples from, but it would be easy to plug another data source (or even completely different data layer that perhaps does not use the Repository pattern).
LINQ but not LinqToSql
You will find that sometimes you want to do some fancy LINQ query that happens to barf when the execution flows down to the LinqToSql provider. That is because LinqToSql works by translating the effective LINQ query logic into T-SQL code, and sometimes that is not always possible.
For example, i have this function that i want an
IQueryableresult from:Why that code does not translate to SQL is not important, but just believe me that the conditions in that
IsUpcomingEvent()predicate involve a number ofDateTimecomparisons that simply are far too complicated for LinqToSql to convert to T-SQL.By using
.ToList()then the condition (.Where(..) and then.AsQueryable()i’m effectively telling LinqToSql that i need all of the.GetSortedEvents()items even tho i’m then going to filter them. This is an instance where my filter expression will not render to SQL correctly so i need to filter it in memory. This would be what i might call the limitation of LinqToSql’s performance as far as Deferred Execution and lazy loading goes – but i only have a small number of theseWARNING: HEAVY SQL QUERY!blocks in my app and i think further smart refactoring could eliminate them completely.Finally, LinqToSql can make a fine data access provider in large apps if you want it to. I found that to get the results i want and to abstract away and isolate certain things i’ve needed to add code here and there. And where i want more control over the actual SQL performance from LinqToSql, i’ve added smarts to get the desired results. So IMHO LinqToSql is perfectly ok for heavy apps that need db query optimization provided you understand how LinqToSql works. My design was originally based on Rob’s Storefront tutorial so you might find it useful if you need more explanation about my rants above.
And if you want to use those lazy classes above, you can get them here and here.