IQueryable LINQ vs. Builder pattern
2: ты как-то писал, что LINQ это и есть Builder, так вот, нифига это не Builder. Классический Builder гораздо гибче в применении, чем Expression-ы.в каком контексте я это говорил?
в каком контексте я это говорил?
http://forumbgz.ru/ashowthreaded.php?Cat=&Board=prog&...спасибо, вспомнил.
имелось ввиду, что linq хороший builder для обработки коллекций в "длину", а в "ширину"(по полям) - он да, плохо билдится.
имелось ввиду, что linq хороший builder для обработки коллекций в "длину", а в "ширину"(по полям) - он да, плохо билдится.Я с большой вероятностью предполагаю, что синтаксического сахара, приведенного ниже, хватило бы для реализации нормального Builder-а. Причем синтаксис запросов в точности совпадал бы с текущим LINQ.
И еще, можно было бы делать свой билдер прям в SQL, а не выстраивать какой-то универсальный язык запросов. Это, на мой взгляд, плюс, ибо с SQL Server-ом приятнее работать на его родном языке SQL.
1. C# индексер может иметь generic параметр.
2. C# индексер может быть оформлен как extension метод.
3. Выражение _ => _.LaborSubtotal может быть записано в краткой форме @LaborSubtotal
4. Перегрузка оператора может быть оформлена как extension метод.
5. Если у типа MyType есть индексер, принимающий параметр Expression<Func<TEntity, TProperty>> propertyExpression, и у типа TEntity есть свойство Property1, то выражение myType[@Property1] можно записать в краткой форме myType.Property1. В этой записи свойство Property1 можно рассматривать как extension свойство, аналогично extention методам.
От Expression-ов можно полносттью отказаться. Упомянутые Expression-ы в параметре индексера можно (и даже нужно) заменить на тип, который предоставлял бы доступ к имени свойства, типу, геттору и сеттору.
Почему нельзя было немного подумать, посмотреть на других, и сделать решение на основе паттерна Builder?Можно код?
Можно код?для кода требуется 1-2 месяца разработки. Но концептуально он будет во много похож на Reactive Relation. Последняя версия находится в ветке \ReactiveRelation\DevBranches\MethodsWrappedEntityProperties
Хочется увидить кусок псевдо кода по которому можно понять суть выделенного утверждения.
void Test
{
db.Customer.Where(c => c.FirstName == "Test1" || SecondCriterion(c
}
IValue<bool> SecondCriterion(IValue<Customer> c)
{
return c.LastName == "Test2";
}
Единственную проблему которую я здесь вижу это получение Expression по MethodInfo, об этом речь?
подробнее распиши код, который ты имеешь ввиду (в моем примере експрешенов нет).
Expression -> IQueryable
Анализатор смотрит на ast и видет в нем вызов функции SecondCriterion, единственно что он может, это получить MethodInfo этой функции, соответственно если бы у него была возможность получить Expression, он дальше смог бы производить разбор, а так приходится кидать ексепшен.
Анализатор смотрит на ast и видет в нем вызов функции SecondCriterion, единственно что он может, это получить MethodInfo этой функции, соответственно если бы у него была возможность получить Expression, он дальше смог бы производить разбор, а так приходится кидать ексепшен.в этом то и проблема. В методе SecondCriterion может быть что угодно — условные операторы, циклы, присвоение/изменение переменных и т.д. Имхо, в языке с состоянием анализатору слабо это все обработать. А если опираться не на анализатор AST, а на билдер, то проблемы вообще нет.
Как выглядит код который решает данную проблему при помощи билдера?
Как выглядит код который решает данную проблему при помощи билдера?Код использования:
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class MainTest
{
private IRelation<Customer> Customers
{
get { throw new NotImplementedException; }
}
[Test]
public void Test
{
var query = Customers.Where(
c => c._(_ => _.FirstName).Equal("Test1".Const.Or(SecondCriterion(c
);
}
private IValue<bool> SecondCriterion(ITuple<Customer> c)
{
return c._(_ => _.LastName).Equal("Test1".Const;
}
}
Скелет реализации билдера:
public static class RelationExtensions
{
public static IQuery<TEntity> Where<TEntity>(
this IRelation<TEntity> relation,
Func<ITuple<TEntity>, IValue<bool>> predicate)
{
throw new NotImplementedException;
}
}
public static class ValueExtensions
{
public static IValue<T> Const<T>(this T arg)
{
throw new NotImplementedException;
}
}
public static class StringValue
{
public static IValue<bool> Equal(
this IValue<string> value1, IValue<string> value2)
{
throw new NotImplementedException;
}
}
public static class BoolValue
{
public static IValue<bool> Or(
this IValue<bool> value1, IValue<bool> value2)
{
throw new NotImplementedException;
}
}
public interface IRelation<TEntity>
{
}
public interface ITuple<TEntity>
{
IValue<TProperty> _<TProperty>(
Expression<Func<TEntity, TProperty>> propertyExpression);
}
public interface IValue<T>
{
}
public interface IQuery<TEntity>
{
}
Код удобнее смотреть под Visual Studio. Код можно скачать тут . Файл солюшета в папке \TypedSqlBuilder\Trunk\Source\TypedSqlBuilder.sln
в reactive такого не будет.
но в Linq одно из интересных фич, что можно добавить вспомогательные поля при запросе, а потом с ними прозрачно работать в полученном результатеты про анонимные классы? Они, естественно, будут работать также.
ты про анонимные классы? Они, естественно, будут работать также.совместно с builder-ом?
да
дапокажи пример использования
А какие еще проблемы решаются кроме or?
покажи пример использованияДа, с проекциями оказалось сложнее, чем я предполагал. Существующий синтаксис анонимных классов не очень подходит для билдера. Для не анонимного типа синтаксис вот такой:
var query = Customers
.Where(
c => c._(_ => _.FirstName).Equal("Test1".Const.Or(SecondCriterion(c
)
.Select(
c => Tuple.New<CustomerProjection>
.Bind(_ => _.FirstName, c._(_ => _.FirstName
.Bind(_ => _.FullName, c._(_ => _.FirstName).Add(c._(_ => _.LastName
);
Но ведь у разработчиков языка, всегда есть возможность добавить синтаксис . Хотя да увлекаться расширением синтаксиса не стоит...
А какие еще проблемы решаются кроме or?в первой ссылке из первого поста рассмотрены три проблемы.
У нас еще была проблема локализации запросов, т.е. выводить, вместо английского столбца, столбец на выбранном языке.
Вообще, я начинаю собирать коллекцию хороших примеров, где LINQ плохо строит запросы. Буду рад, если вы поделитесь своими примерами .
А билдер может наружу выдавать Expression? Если его еще научить кроме статика принимать стринги, возможно получилась бы неплахая альтернатива dynamic linq.
А билдер может наружу выдавать Expression?Думаю, да. Билдеру все равно чего строить, а API для построения Expression-ов во фрейворке есть. Только вот, я думаю, нафига конкурировать с LINQ, примеров, демонстрирующих явное преимущество билдера пока нет. Хотя при большом количестве запросов в приложении преимущества билдера проявят себя. Я думаю, лучше позиционировать билдер, как инструмент для построения напрямую TSQL. С нормальной поддержкой LEFT OUTER JOIN и т.п. Можно даже такие вещи как ROW_NUMBER поддержать.
ROW_NUMBER OVER(ORDER BY SalesYTD DESC) AS 'Row Number'
Если его еще научить кроме статика принимать стринги
Так ведь, весь этот огород ради того, чтобы избежать стрингов. SQL в виде стринга всегда можно написать .
SQL в виде стринга всегда можно написатьДля этого как мининмум нужно написать код который будет этот sql собирать на основе мапинга, тоесть написать то что уже реализовано, зачем?
Для этого как мининмум нужно написать код который будет этот sql собирать на основе мапинга, тоесть написать то что уже реализовано, зачем?какой нафиг мепинг? LINQ to SQL исповедует правильную идеологию — база данных главней. Поэтому не понимаю о чем ты, пишем просто SQL на основе табличек.
или ты про про убожество под названием EF? Которое уже годами в таком состоянии что вот еще немного и ее можно будет использовать
Допустим метаданные описывают классы а не таблицы, например Data Annotation. Следовательно о таблицах я узнаю только через мапинг.
или ты про про убожество под названием EF?Из того что я читал, к следующей версии они провели неплохую работу над ошибками.
Да, с проекциями оказалось сложнее, чем я предполагал. Существующий синтаксис анонимных классов не очень подходит для билдера. Для не анонимного типа синтаксис вот такой:на самом деле, ситуация гораздо лучше , я просто не воспользовался для сеттора индексера.
Но ведь у разработчиков языка, всегда есть возможность добавить синтаксис . Хотя да увлекаться расширением синтаксиса не стоит...
var query = Customers
.Where(
c => c._(_ => _.FirstName).Equal("Test1".Const.Or(SecondCriterion(c
)
.Select(
c =>
{
var tuple = _.New<CustomerProjection>
tuple._(_ => _.FirstName, c._(_ => _.FirstName;
tuple._(_ => _.FullName, c._(_ => _.FirstName).Add(c._(_ => _.LastName;
return tuple;
}
);
Если бы эти сетторы собирались бы в краткую запись так же как в Object Initializer, то запись бы отличалась только "_.New<CustomerProjection>" вместо "new CustomerProjection". А для этого уже можно и синтаксический сахар сделать. Анонимные классы через эту запись тоже вводятся.
Допустим метаданные описывают классы а не таблицы, например Data Annotation. Следовательно о таблицах я узнаю только через мапинг.Тк тебе SQL в итоге на таблицах надо выполнить. Посмотри в базу имена таблиц и пиши свой SQL. Единственная проблема это проинстанцировать коллекцию объектов по резал сету, но для этого в LINQ to SQL есть незатейливое правило — имена полей объектов должны совпадать с именами полей в резал сете.
Есть DisplayColumnAttribute по которому я могу понять какая свойство отвечает за название сущности, и есть Type, для которого нужно получить все пары вида ключ, название. Все данные завязаны на объекте, следовательно sql специфику нужно вытягивать через мапинг.
Есть DisplayColumnAttribute по которому я могу понять какая свойство отвечает за название сущностиа нафига мне дисплейное название сущности для составления SQL запроса?
..и есть Type, для которого нужно получить все пары вида ключ, названиене понял о чем и к чему это ты
class Product {int ID; [DisplayColumnAttribute ] string Name; int Price;}
List<NamedID> GetAllEntityNamedIDs(Type type)
var result = GetAllEntityNamedIDs(typeof(Product;
Оставить комментарий
6yrop
Как-то херово с архитектурной точки зрения выглядит LINQ для IQueryable. Проявляется эта архитектурная лажа вот на таких задачах.http://tomasp.net/articles/dynamic-linq-queries.aspx
http://www.albahari.com/nutshell/predicatebuilder.aspx
В статьях приведены воркэраунды, но это всего лишь воркэраунды. Такие вещи должны быть доступны по умолчанию и в красивом виде.
Майкросовтовцы сделали много красивых шагов, но все же слажали. Почему нельзя было немного подумать, посмотреть на других, и сделать решение на основе паттерна Builder?
2: ты как-то писал, что LINQ это и есть Builder, так вот, нифига это не Builder. Классический Builder гораздо гибче в применении, чем Expression-ы.