Комментарии 19
Браво, изобрели генерацию запросов на лету :)
0
У ORM может быть такая фишка как предопределённые части запросов, рассматривали такую возможность?
Что то типа того что автоматом (через вызов специально написанного метода совместимого с ORM) добавлять к запросу «is_hidden = 0» или «user.active = 1», и при формировании SQL ORM дубли отсечёт и нужные альясы подставит.
Что то типа того что автоматом (через вызов специально написанного метода совместимого с ORM) добавлять к запросу «is_hidden = 0» или «user.active = 1», и при формировании SQL ORM дубли отсечёт и нужные альясы подставит.
0
пока не понял о чем речь, можно пример для любой orm,
0
www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/filters.html
Я думаю в самой Докритн ОРМ побогаче этот функционал, не знаю почему тут так скромно описано.
И есть же LINQ to SQL, делаете метод, который принимает формируемое условие, что то в это условие добавляет и выдаёт что получилось — прямо так и задумано использовать LINQ, потом из сфомированного выражения LINQ генериться SQL, при этом строиться дерево выражений и конечно оно упрощается, ещё в далеком 2013 оно это умело и умело хорошо (по мнению некоторых более чем я прошаренных товарищей).
Я думаю в самой Докритн ОРМ побогаче этот функционал, не знаю почему тут так скромно описано.
И есть же LINQ to SQL, делаете метод, который принимает формируемое условие, что то в это условие добавляет и выдаёт что получилось — прямо так и задумано использовать LINQ, потом из сфомированного выражения LINQ генериться SQL, при этом строиться дерево выражений и конечно оно упрощается, ещё в далеком 2013 оно это умело и умело хорошо (по мнению некоторых более чем я прошаренных товарищей).
0
ага, более-менее понял.
ну по сути да, спецификация — изначально это как раз что-то похожее на фильтры.
Мы определяем объекты, которые соотвествуют нашим бизнес правилам, можем в последующем их объединять в более сложные правила.
зачем нужен еще один слой абстракции — я писал в статье и в комменатриях — что бы отделить уровень домена от конкретной реализации orm. в конечном счете (по крайне мере сейчас) внутри executor'a все опирается на linq, который через провайдер для orm и строит запросы, отвечая за все alias'ы и т.д
ну по сути да, спецификация — изначально это как раз что-то похожее на фильтры.
Мы определяем объекты, которые соотвествуют нашим бизнес правилам, можем в последующем их объединять в более сложные правила.
зачем нужен еще один слой абстракции — я писал в статье и в комменатриях — что бы отделить уровень домена от конкретной реализации orm. в конечном счете (по крайне мере сейчас) внутри executor'a все опирается на linq, который через провайдер для orm и строит запросы, отвечая за все alias'ы и т.д
0
А зачем что-то отделять от ORM?
0
а для чего мы вообще вводим абстракции, разбиваем приложение на слои?
ну и еще в качестве примера одна плюшка — в одном приложении у нас использовалась nosql (mongo) и sql (psql) субд. Было приятно писать код в едином стиле
ну и еще в качестве примера одна плюшка — в одном приложении у нас использовалась nosql (mongo) и sql (psql) субд. Было приятно писать код в едином стиле
0
а для чего мы вообще… разбиваем приложение на слои?Я не знаю, для чего вы разбиваете приложения на слои? Я не разбиваю. Я разбиваю приложения на куски (как торт — slices). Каждый такой кусок может иметь какой ему вздумается доступ к данным.
«в одном приложении» у нас использовалось целых
— mysql — легаси кусок от CMS на PHP
— ms sql — для хранения реляционных данных
— cosomsdb (mongo) — для хранения данных, которые не совсем реляционные
— firestore — подготовленная копия данных для клиента
Так вот, никакого желания и смысла, запихивать это под единые абстракции, не было. Но, в некоторых кусках, находились любители засунуть всё в репозиторий, но они только отвлекали от главного, и мешали разработке.
0
хотелось бы более развернутого комменатрия. Причем тут FSM?
+1
Ну просто вот этот «паттерн Спецификация» выглядит как прекрасная иллюстрация десятого правила Гринспуна.
is an ad-hoc, informally-specified, bug-ridden, slow implementation of half of [FSM].
Можно просто взять (или сделать) работоспособную имплементацию FSM и построить полнофункциональный query builder с в разы меньшими затратами. Возможно, я просто чего-то недопонимаю, впрочем.
0
Это отличный проект, поддерживающий много фич, которые мне нравятся, и которые я пока не реализовал (сериализация и сравнения — две первоочередные). Но в тоже время есть несколько НО:
1) для того, что бы использовать фичи ORM, придется добавить референс на соотвествующую сборку из слоя домена. что не всегда возможно, если у вас домен пошарен между несколькими проектами, под разные платформы
2) возможно я ошибаюсь, но она поддерживает только склейку условий, состоящих из условий фильтрации(where), в моей реализации есть возможнолсть склейки произвольных правил, соотвественно я могу определить, к примеру, правило на фильтрацию и правило для пагинации, а потмо получить правило на пагинацию и фильтарцию
1) для того, что бы использовать фичи ORM, придется добавить референс на соотвествующую сборку из слоя домена. что не всегда возможно, если у вас домен пошарен между несколькими проектами, под разные платформы
2) возможно я ошибаюсь, но она поддерживает только склейку условий, состоящих из условий фильтрации(where), в моей реализации есть возможнолсть склейки произвольных правил, соотвественно я могу определить, к примеру, правило на фильтрацию и правило для пагинации, а потмо получить правило на пагинацию и фильтарцию
0
1) Ничего не мешает портировать этот код прямо в домен (в любом случае, вашу сборку либо так же референсить, либо эмбеддить). Код этой сборки очень прост.
2) Там есть склейка через && и || плюс инвертирование через !spec.
Использовал данное решение для динамического построения предикатов в проектах, где использовался ORM. Было очень удобно.
0
1) вы меня не совсем поняли. предположим, я хочу использовать Fetch (Include в ef.core). Что бы использовтаь его с LinqSpec (если это вообще возможно, я не уверен) — мне придется сослаться на библиотеку orm из слоя домена.
2) операторы склейки — это удобно, не спорю, только, все таки через & и |. Но судя по коду библиотеки (который на самом деле очень простой, как вы и отметили) склеить правила, которые включают в себя сортировку не получится.
я не в коем случае не говорю о том, что мое решение — серебрянная пуля. одной из целей, которую я преследовал, написав эту статью и опубликовав исходники — получить фидбек, что бы понять, «а не фигню ли я делаю» (с).
определенно, из LinqSpec есть что почерпунть!
2) операторы склейки — это удобно, не спорю, только, все таки через & и |. Но судя по коду библиотеки (который на самом деле очень простой, как вы и отметили) склеить правила, которые включают в себя сортировку не получится.
я не в коем случае не говорю о том, что мое решение — серебрянная пуля. одной из целей, которую я преследовал, написав эту статью и опубликовав исходники — получить фидбек, что бы понять, «а не фигню ли я делаю» (с).
определенно, из LinqSpec есть что почерпунть!
0
Я, чесно, пока не понимаю зачем вы так усложняете себе жизнь. За час на коленке я написал простенькое решение, которое умеет комбинировать фильтры. Все просто, вы пишете IQueryable расширения и комбинируете как угодно.
К примеру используя одни и те же методы:
Здесь помогают интерфейсы, которые вы зададите своим сущностям:
Ну и сама реализация (использован метод Transform из билиотеки CodeJam)
Как на меня просто, наглядно и легко понимаемо. Да тут шаманство с деревьями выражений, но я это сделал за вас.
Все проверяется компилятором и никаких дополнительных абстракций.
К примеру используя одни и те же методы:
class Program
{
static void Main(string[] args)
{
var query = new[]
{
new SomeClass { City = "NY", Years = 20 },
new SomeClass { City = "Seattle", Years = 16 },
}.AsQueryable();
var semi = query.CombineOr(q => q.IsReadyToDrink(), q => q.LiveIn("NY", "Seattle"))
.ToArray();
var strict = query
.IsReadyToDrink()
.LiveIn("NY", "Seattle")
.ToArray();
var semi2 = query.CombineOr(q => q.CityAndDrinkability("NY", "Seattle"), q => q.IsReadyToDrink())
.ToArray();
var strict2 = query
.CityAndDrinkability("NY", "Seattle")
.ToArray();
}
}
Здесь помогают интерфейсы, которые вы зададите своим сущностям:
public interface IAge
{
int Years { get; }
}
public interface ICity
{
string City { get; }
}
public class SomeClass : IAge, ICity
{
public string SomeValue { get; set; }
public int Years { get; set; }
public string City { get; set; }
}
public static class BusinessRules
{
public static IQueryable<T> IsReadyToDrink<T>(this IQueryable<T> source)
where T: IAge
{
return source.Where(s => s.Years >= 18).Where(s => s.Years <= 70);
}
public static IQueryable<T> LiveIn<T>(this IQueryable<T> source,
params string[] cities)
where T: ICity
{
return source.Where(s => cities.Contains(s.City));
}
public static IQueryable<T> CityAndDrinkability<T>(this IQueryable<T> source, params string[] cities)
where T: ICity, IAge
{
return source.LiveIn(cities).IsReadyToDrink();
}
}
Ну и сама реализация (использован метод Transform из билиотеки CodeJam)
public static class SpecsExtensions
{
private static Expression Unwrap(Expression expr)
{
if (expr.NodeType == ExpressionType.Quote)
return Unwrap(((UnaryExpression) expr).Operand);
return expr;
}
private static IEnumerable<Expression> CollectCondition(Expression query, ParameterExpression obj)
{
if (query.NodeType == ExpressionType.Call)
{
var mc = (MethodCallExpression) query;
if (mc.Method.IsGenericMethod && mc.Method.GetGenericMethodDefinition() == _whereMethodInfo)
{
var unwrapped = (LambdaExpression)Unwrap(mc.Arguments[1]);
foreach (var cond in CollectCondition(mc.Arguments[0], obj))
{
yield return cond;
}
var corrected = unwrapped.Body.Transform(e => e == unwrapped.Parameters[0] ? obj : e);
yield return corrected;
}
else
{
var canProcess = mc.Method.DeclaringType != typeof(Queryable) && mc.Arguments.Count > 0;
if (canProcess)
{
canProcess = mc.Arguments[0].Type.IsGenericType;
if (canProcess)
{
canProcess = mc.Arguments[0].Type.GetGenericTypeDefinition() == typeof(IQueryable<>);
}
}
if (!canProcess)
{
throw new NotImplementedException();
}
// processing user defined functions, so filters can be reused in other filters
var innerExpression = ((IQueryable) Expression.Lambda(mc).Compile().DynamicInvoke()).Expression;
foreach (var cond in CollectCondition(innerExpression, obj))
{
yield return cond;
}
}
}
}
public static MethodInfo GetMethodInfo<T>(Expression<Action<T>> expression)
{
var member = expression.Body as MethodCallExpression;
if (member != null)
return member.Method;
throw new ArgumentException("Expression is not a method", "expression");
}
private static readonly MethodInfo _whereMethodInfo = GetMethodInfo<IQueryable<int>>(q => q.Where((Expression<Func<int, bool>>)null)).GetGenericMethodDefinition();
public static IQueryable<T> CombineOr<T>(this IQueryable<T> source, params Func<IQueryable<T>, IQueryable<T>>[] queries)
{
Expression condition = null;
var param = Expression.Parameter(typeof(T), "q");
var fake = Enumerable.Empty<T>().AsQueryable();
foreach (var query in queries)
{
var filter = query(fake);
var strict = CollectCondition(filter.Expression, param)
.Aggregate(Expression.AndAlso);
condition = condition == null ? strict : Expression.OrElse(condition, strict);
}
if (condition == null)
return source;
var filterBody = Expression.Lambda(condition, param);
var result = (IQueryable<T>) _whereMethodInfo.MakeGenericMethod(typeof(T))
.Invoke(null, new object[] { source, filterBody });
return result;
}
}
Как на меня просто, наглядно и легко понимаемо. Да тут шаманство с деревьями выражений, но я это сделал за вас.
Все проверяется компилятором и никаких дополнительных абстракций.
0
1) это справедливо для базового понятия спецификации, но, как я рассказывал в статье, мы хотели получить расширенные правила, которые не ограничивались бы фильтрами
2) если остановиться на iqueryable, то возвращаемся к проблеме, о которой я уже не однократно упоминал и в статье и в комментариях (возможно, она специфична для конкретного проекта/решения) — предположим, у вас домен пошарен между бэкенд частью веб приложения и богатым мобильным приложением. на бэке вы используете полновесную орм, а на мобилке у вас просто in memory хранилище. что бы на основе iqueryable использовать правила, включающие в себя оптимизации для загрузки придется сослаться на сборки орм, которых на мобильной платформе может не оказаться. поэтому и появляется этот слой абстракции…
кроме того, иногда linq не хватает. в практике был пример, когда спецификации пришлось переписать на использование icriteria. но благодаря этому слою не пришлось перелапачивать весь код приложения, а все правки остались в слое доступа.
p.s. код лучше было оформить в виде ссылки на codebin или что-то вроде того
2) если остановиться на iqueryable, то возвращаемся к проблеме, о которой я уже не однократно упоминал и в статье и в комментариях (возможно, она специфична для конкретного проекта/решения) — предположим, у вас домен пошарен между бэкенд частью веб приложения и богатым мобильным приложением. на бэке вы используете полновесную орм, а на мобилке у вас просто in memory хранилище. что бы на основе iqueryable использовать правила, включающие в себя оптимизации для загрузки придется сослаться на сборки орм, которых на мобильной платформе может не оказаться. поэтому и появляется этот слой абстракции…
кроме того, иногда linq не хватает. в практике был пример, когда спецификации пришлось переписать на использование icriteria. но благодаря этому слою не пришлось перелапачивать весь код приложения, а все правки остались в слое доступа.
p.s. код лучше было оформить в виде ссылки на codebin или что-то вроде того
0
1) Возможно, специфика есть специфика
2) Так и делают. Тянут ORM на клиент, хотя в случае EF Core это толпа зависимостей. По этому часто используют Remote LINQ или что-то в этом роде.
О случае когда вам IQueryable не хватило можно поподробнее?
Возможно, но так как писано на коленке, тогда и не задумывался.
2) Так и делают. Тянут ORM на клиент, хотя в случае EF Core это толпа зависимостей. По этому часто используют Remote LINQ или что-то в этом роде.
О случае когда вам IQueryable не хватило можно поподробнее?
p.s. код лучше было оформить в виде ссылки на codebin или что-то вроде того
Возможно, но так как писано на коленке, тогда и не задумывался.
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Спецификации на стероидах