Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
169 views
in Technique[技术] by (71.8m points)

c# - Is this Repository pattern efficient with LINQ-to-SQL?

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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> and LazyItem<T> classes that encapsulate lazy initialization:

  • LazyList<T> wraps the IQueryable functionality of an IList collection but maximises some of LinqToSql's Deferred Execution functions and
  • LazyItem<T> will wrap a lazy invocation of a single item using the LinqToSql IQueryable or a generic Func<T> method for executing other code deferred.

Here is an example - i have this model object Announcement which may have an attached image or pdf document:

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; }
}

The Image and PdfDoc classes inherit form a type File that contains the byte[] containing the binary data. This binary data is heavy and i might not always need it returned from the DB every time i want an Announcement. So i want to keep my object graph 'anchored' but not 'populated' (if you like).

So if i do something like this:

Console.WriteLine(anAnnouncement.Title);

..i can knowing that i have only loaded from by db the data for the immediate Announcement object. But if on the following line i need to do this:

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

..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 IQueryable result from:

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

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 of DateTime comparisons 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 these WARNING: 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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...