Как стать автором
Обновить

Комментарии 35

Привет!
Всё верно. Добавлю: я в своих проектах стараюсь абсолютно все linq-запросы оформлять в виде скомпилированных expressions. Делается это при помощи хэлпера CompiledQuery.Compile(). Это позволяет избежать путаницы (зачастую из-за недостатка должного внимания), а так же значительно увеличивает производительность выполнения запроса.
Да ты прав, тоже очень полезная возможность для оптимизации.
НЛО прилетело и опубликовало эту надпись здесь
Да, хорошо. Однако я использую не LinqToSql, а EntityFramework в .net 4.0. Привет!
НЛО прилетело и опубликовало эту надпись здесь
Да, новый EF из .NET 4.0. сильно отличается от старого, очень сильно, мне нравится, они вставили и POCO объекты как в NHibernate, и можно в рантайме схему генерить, и LinqToEntities отшлифовали, может быть через пару месяцев напишу, а то сейчас горячка с проектом одним. Привет!
Привет!
Такое ощущение, что вы не шарите, почему в Linq2Sql используется Expression<Func<..>>, а не Func<..>
Я очень рад что вы, уважаемый в этом шарите. Но вы в этом не одиноки. Есть и другие избранные, которые понимают магию Expression<Func<..>> и умеют ее использовать :)
Ммм. Отлично. Было бы еще лчше, если бы другие избранные, про которых вы говорите, вам с самого начала пояснили, почему в вашем случае linq-запрос не достраивался, а распадался на sql-objects-sql, потому что такую «засаду» вы выяснили чисто случайно :) Надеюсь, в вашем проекте это было единственное место.

P.S. У нас бы за такое в процессе codereview погнали бы ссаными тряпками.
Ну так ждем от вас доступного объяснения. Давайте, доступное объяснение в студию!
Как тока мне вернут мою карму на место, так сразу… Иш, потянулись уже ручонки у кого-то.
Шутка.

На самом деле, вы уже написали половину ответа.
Тот Select, который вы делаете по IEnumerable[T], требует наличия сущностей в памяти. Поэтому IQueryable приводится к IEnumerable (своего рода слайсинг) единственным доступным ему способом — он делает то, что в ORM называется материализацией сущностей. После чего навигационный доступ в памяти по одному отправляет запросы в БД за параметрами.
Во втором случае IQueryable[T] комбинирует запрос, достраивая join. Потому что он знает целевой язык (sql), и потому что результаты первой части запроса еще не материализованы, а представлены в виде Expression[Func[T]].
Я кончил (доклад) и закурил
Ваш код эквивалентен следующему:
foreach (DataAccess.User u in users) (1)
{
	Model.User user = new Model.User();
	user.id = u.id;
	user....
	user.Parameters = parameters.Select(u => new Model.Parameter {… }); (2)
	yeld return user;
}

Происходит первый вызов в строке (1) и потом еще 10 раз в строке (2)
НЛО прилетело и опубликовало эту надпись здесь
Хех, согласен на миллион процентов. Дело в том, что LINQ 2 SQL — это и не реализация паттерна ActiveRecord, где можно было бы со спокойной душой забыть про слово репозиторий и позволить сущностям самим знать все о доступе к данным (без DataContext). И, тем более, L2S — никаким боком и рядом не стоит с парадигмами DDD (Domain-Driven-Design), чтобы пытаться им следовать.

Вот и получаются всякие RepositoryHelper'ы и HelperRepository'и :)
Мне кажется, что речь была не про «забыть про слово репозиторий», про концовку в виде «Helper».

Просто ходят слухи, то полезно называть класс так, чтоб он описывал то, что делает. К примеру ModelUserProvider/Convertor.

Часто советуют избегать приставок из серии Heler/Utils аргументируя тем, что это смахивает «процедурным программированием» и не отображает своей сути.

Не знаю, правда это или нет, но вот есть такое мнение («и не только мое» (с))
Нет, обычно в .NET используется название Helper, чтобы заключить в этот класс дополнительный функционал, построенный поверх уже существующего в основном классе, чтобы не перегружать последний. Extension-методы в .NET — по сути, созданы для той же цели.
Также бывает, что Helper-классы содержат некоторую логику, не имеющую прямого отношения к тому, что делает сам объект (обычно это «аспекты»/AOP). Впрочем, в этом случае, логику содержат не сами эти Helper-классы, а другие классы, используемые внутри хелперов.

Приведенный пример в статье, с RepositoryHelper, дейстительно, не отражает сути того, что он делает, и более того — содержит логику, которая должна, по идее быть либо в репозитории, либо в слое сервисов (Application Services).
Да это все ясно, но вот как мне кажется, суть комментария hazzik в том, что возможно стоит называть классы именно так, чтоб названия описывали то, что они делают. От «расширителей», «помощников», «фасадов» и т.д. все равно никуда не денемся.

Просто когда немного поработаешь в крупных проектах, то столько насмотришься на всякие Utils/Helper-а что невольно начинает типать когда смотришь на них. При чем могут быть десятки классов с одним названием (типа PageHelper), содержащий совершенно разных «вспомогательный» функционал.

В прочем, nevermind, думаю поднятая тема никак не касается именования классов.
Это уже работая над «очередным» проектом, вы только столкнулись с сутью проблемы SELECT N+1? :)
Очевидно, что автор не в теме.
dmitry_h, честно, не создается впечатление о том, что вы разбираетесь в этом. Не обижайтесь, здесь мы для того, чтобы помогать друг другу. Советую вам ознакомиться с книгой:

LINQ. Карманный справочник. Албахари Б. Албахари Д.

Бумажный вариант:
shop.top-kniga.ru/books/item/in/395268/

Электронный:
www.twirpx.com/file/156984/
Не люблю тех. литературу на русском(
Да и есть уже куда более новые книги по Linq
Как вам верно написали выше, вы просто не понимаете, как внутри себя работает Linq2Sql. А никакого (ровным счетом никакого) отношения к интерфейсам IEnumerable/IQueryable описанная вами проблема не имеет. Если вы решите просто привести одно к другому, запросов в базу это не уменьшит.

Потому что, вы не поверите, стоит при создании датаконтекста указать правильные LoadOptions, как в первом же вашем примере будет один запрос, а не N+1 (вне зависимости от всех ваших измышлений с типами).
На всякий случай проиллюстрирую мысль «Если вы решите просто привести одно к другому, запросов в базу это не уменьшит.»

Что делает вызов Enumerable.Select(source, selector)? Если выкинуть обработку параметров, то он сведется к return WhereSelectEnumerableIterator(source, null, selector);

(… тьфу ты, продолжаем)

Что делает вызов Queryable.Select(source, selector)? Создает Expression, который описывает… вызов метода Select на source (с параметром selector). Кем будет выполнен этот expression? QueryProvider-ом, заданный в source (а source, напомню, пришел к нам из AsQueryable). А вот теперь посмотрим на AsQueryable: EnumerableQuery.Create(..., source), что, в свою очередь, дает нам создание EnumerableQuery(Of T), которая является провайдером сама для себя. Что делает ее Execute? В итоге, после множества перенаправлений, выясняется, что он всего лишь компилирует expression и вызывает его.

Вот и все.

Подводя итог: AsQueryable — это всего лишь враппер, работа которого все равно сведется к вызову оригинальных методов на Enumerable.

Для тех кто в танке:

Так выглядит метод статического класса System.Linq.Queryable

public static IQueryable Select<TSource, TResult>(this IQueryable source, Expression<Func<TSource, TResult>> selector)
{
if (source == null)
{
throw Error.ArgumentNull(«source»);
}
if (selector == null)
{
throw Error.ArgumentNull(«selector»);
}
return source.Provider.CreateQuery(Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSource), typeof(TResult) }), new Expression[] { source.Expression, Expression.Quote(selector) }));
}

а так System.Linq.Enumerable:

public static IEnumerable Select<TSource, TResult>(this IEnumerable source, Func<TSource, int, TResult> selector)
{
if (source == null)
{
throw Error.ArgumentNull(«source»);
}
if (selector == null)
{
throw Error.ArgumentNull(«selector»);
}
return SelectIterator<TSource, TResult>(source, selector);
}

О чем говорит этот код?

От том что логика формирования выражения совершенно разная, в первом случае мы вызываем 'компилятор' выражения, который в зависимости от типа провайдера соответствующим образом преобразует Expression. Во втором случае просто создается итератор, на каждой итерации которого просто вызывается selector.

Ох. Вы мой коммент-то прочитали? source.Provider для Queryable берется из переданного объекта. Ваш передаваемый объект — это (некий enumerable).AsQueryable. Внутри AsQueryable происходит обертка enumerable в QueryProvider. Это и есть ваш «компилятор выражения». Посмотрите в его код (упрощенно):

EnumerableQuery.CreateQuery(Expression expression)
{
return new EnumerableQuery(expression);
}

EnumerableQuery.cctor(Expression expression)
{
this.expression = expression;
}

EnumerableQuery.Execute(Expression expression)
{
return new EnumerableExecutor(expression).Execute();
}

EnumerableExecutor.Execute(Of T)()
{
if (this.func == null)
{
EnumerableRewriter rewriter = new EnumerableRewriter();
this.func = Expression.Lambda(Of Func(Of T))(rewriter.Visit(this.expression)).Compile();
}
return this.func();
}

Рерайтер внутри себя, по сути, разворачивает стартовое выражение в енумерабл.

Для тех, кто в танке, да.
Собака зарыта в смешении ответственностей.

Определите, как минимум:
1) кто отвечает за трансляцию из одной модели в другую. Пусть эта сущность больше ничем не занимается, тогда у неё будет своё имя, а helper'ы больше не будут вызывать вопросов.
2) кто отвечает за работу с DataContext и как он это будет делать
2.1) будет ли он получать фильтр для данных (пользователей) извне или сам будет строить Expression<Func<T, bool>>
2.2) в каком виде он будет возвращать данные — готовые коллекции/списки или IQueryable

По примерам кода можно предположить, что нету стремления получать именно перечисления в GetUsers, но есть желание получать фильтрованный список пользователей. Тогда IEnumerable вычёркиваем.
IQueryable тоже вычёркиваем — извне не используете, тогда зачем его возвращать? Возвращайте готовую коллекцию/список.

Лишнее наследование UserRepository: MyProjectDataContext можно заменить разделением ответственностей по классам либо создав нужные методы DataContext'а в partial классе.
Вот уж согласен, DataContext, как минимум, должен быть внутри Repository, в виде переменной-источника данных. А чтобы избежать всех этих проекций в коде, пытаясь попутно исправить «impedance mismatch», лучше использовать Entity Framework или NHibernate, чтобы сгрузить маппинг на них. А проекции для ViewModel'ей можно легко создавать AutoMapper'ом.
В этом топике я не стремился показать то как нужно проектировать архитектуру, это совершенно отдельная тема, примеры были собраны за 5 минут, чтобы продемонстрировать суть проблемы и ее решения. Я лишь хотел обратить внимание разработчиков, на некоторые тонкости, которые явно не бросаются в глаза. перед тем как написать, я пообщался со знакомыми — разработчиками разного уровня. В результате получился следующий: для 5-ых это было реальной новостью, действительно об этом не нюансе знали. 2-е наиболее опытных ответили что хорошо знакомы с такой проблемой, и знают о чем говорят. 1 из них — ostapkoenig, дал очень дельное замечание по оптимизации, хотя и выходящее за рамки темы статьи.
В упор не могу понять, почему SQL-логику не оставить базе данных, т.е. почему бы не использовать хранимые процедуры для этой выборки, а в GetUsers() вызывать эту процедуру и потом возвращать коллекцию результатов? Мне интересно, почему автор не решил использовать этот способ, ведь L2S позволяет очень легко вызывать хранимые процедуры из кода программы.
Не холивара ради, но:
ormeter.net/

Мы выбрали для себя DataObjects.net
НЛО прилетело и опубликовало эту надпись здесь
Рассказывать очень долго, т.к. выбирали тоже долго :)
В двух словах:
очень удачно и удобно сочетается с DDD, хорошо поддерживает LINQ (ну или процедурный стиль), при этом
быстро работает, хороший саппорт.

Рекомендую почитать немного документацию, чтобы понять подход, а также
принципы сущностей. Разработчики считают, что Persistence Ignorance
не всегда удачная модель и использую т.н. Persistence Awareness.

Вот хорошая статья о принципиальной разнице между NHibernate и DataObjects.
blog.dataobjects.net/2010/11/dataobjectsnet-vs-nhibernate-conceptual.html

Да, и еще — разработчики с Екатеринбурга :)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории