Эффективен ли этот шаблон репозитория с 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<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 для создания оптимизированного запроса на основе лямбда-выражения?

Наконец, какие другие реализации шаблона Repository в C# могут обеспечить лучшую производительность при подключении через LINQ-to-SQL?

4 ответа

Решение

I can understand Johannes Rudolph 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> а также LazyItem<T> classes that encapsulate lazy initialization:

  • LazyList<T> оборачивает 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 или общий 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; }
}

Image а также PdfDoc classes inherit form a type File который содержит 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 объект. 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.

Еще одно большое преимущество заключается в том, что эти "ленивые" классы могут скрывать конкретную реализацию базового репозитория, поэтому мне не обязательно использовать LinqToSql. Я использую LinqToSql в случае приложения, из которого я вырезал примеры, но было бы легко подключить другой источник данных (или даже совершенно другой слой данных, который, возможно, не использует шаблон Repository).

LINQ, но не LinqToSql

Вы обнаружите, что иногда вы хотите выполнить какой-то необычный запрос LINQ, который происходит с barf, когда выполнение передается поставщику 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 может стать хорошим поставщиком доступа к данным в больших приложениях, если вы этого хотите. Я обнаружил, что для получения желаемых результатов, абстрагирования и выделения определенных вещей, мне нужно было добавлять код здесь и там. И там, где я хочу больше контроля над фактической производительностью SQL от LinqToSql, я добавил смарты, чтобы получить желаемые результаты. Так что IMHO LinqToSql идеально подходит для тяжелых приложений, которым требуется оптимизация запросов к БД, если вы понимаете, как работает LinqToSql. Мой дизайн изначально был основан на учебном пособии Роба по витрине магазина, так что вы можете найти его полезным, если вам нужно больше объяснений о моих рантах выше.

И если вы хотите использовать эти ленивые классы выше, вы можете получить их здесь и здесь.

Является ли это эффективным средством доступа к данным? Будет ли вся таблица извлечена из базы данных и отфильтрована в памяти, или цепочечный метод Where() вызовет магию LINQ для создания оптимизированного запроса на основе лямбда-выражения?

Это эффективно, если вы хотите так сказать. Репозиторий выставляет IQueryable inteface, который в основном представляет любой поставщик данных 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]

Я не думаю, что вы можете написать что-нибудь более эффективное, учитывая эту базу данных. Тем не менее, в большинстве реальных баз данных ваши категории будут храниться в отдельной таблице, чтобы хранить вещи сухими.

Другие вопросы по тегам