2009-12-08 2 views
5

У меня есть необязательная часть запроса, которая должна выполняться в определенном состоянии. Вот пример кода:Какой ORM поддерживает этот

int cat = 1; 
int UserID = 12; 
string qry = "select * from articles"; 
if(cat > 0) 
    qry += " where categoryID = " + cat; 
if(UserID > 0) 
    qry += " AND userid = " + UserID; //The AND may be a WHERE if first condition is false 

Как вы можете видеть, у меня есть оператор if в запросе. В настоящее время я использую Entity Framework и не поддерживает такой сценарий. Есть ли там ORM, которые поддерживают это?

Редактировать Я попытался заглушить запрос. Но у меня около 20 утверждений «IF», ​​и запросы очень длинные.

В ORMs я смотрел были:

  • NHibernate
  • LLBLGen
  • Дозвуковые

Я открыт для любого ОРМ. Благодаря

+1

LLBLGen поддерживает это, даже 20 из них. Поскольку предложения where добавляются один или несколько за раз. – PostMan

+0

@PostMan у вас есть пример этого? – Luke101

+2

Вы можете сделать это в Entity Framework, см. Ниже ответ tt83. Его ответ заключается в Linq to SQL, но концепция такая же для Entity Framework. Не нужно выбрасывать ребенка с водой для ванны. –

ответ

2

Вы, вероятно, может сделать это с помощью любого провайдера LINQ, но я знаю, что LightSpeed ORM поддерживает его:

var query = UnitOfWork.Articles; 
if (cat > 0) 
    query = query.Where(a => a.CategoryId == cat); 
+0

Я попытался заглушить запрос. Но у меня около 20 утверждений «IF», ​​и запросы очень длинные. Невозможно ли связать операторы IF в самом запросе linq? – Luke101

+0

Если вы связываете операторы if в запросе LINQ, то они будут переведены на SQL, и будет ли это работать, будет зависеть от поставщика LINQ. Но вы можете цеплять. Спрятать вызовы под контролем клиентских команд if. – itowlson

+0

Кроме того, если ваш реальный прецедент значительно сложнее, вы можете отредактировать свой вопрос, чтобы дать вам немного больше удовольствия от вашего реального прецедента, так что вы не получите такой нагрузки, как мои, которые описывают только тривиальный случай! – itowlson

0

я такого рода вещи в NHibernate все время.

(я делал подобные вещи в Rails. Я отчасти удивлен, что есть ORMs, что сделать не поддержку этого.)

+0

У вас есть пример того, как это делается в nhibernate? – Luke101

+0

Нелегко - у нас есть собственная абстракция над NHibernate, которую я использую в 99,9% случаев. См. Ответ Кевина выше. – Ken

+0

Ответ Кевина прибил его. –

9

это может быть сделано с помощью LINQ к SQL ...

IQueryable<Article> query = yourDataContext.Articles; 

if (catId > 0) 
    query = query.Where(x => x.CategoryId == catId); 

return query.ToList(); 
+0

Я редактировал код. возможно ли добавить несколько операторов where в запрос linq? См. Код в вопросе – Luke101

+0

Да. IQueryable

в этом случае отложит выполнение SQL до тех пор, пока вы его не материализуете (вызовите ToList и т. Д.), Вы можете добавить столько условных выражений, сколько хотите. Только когда вы вызываете ToList, он фактически выполнит SQL для базы данных. –

+0

Могу ли я добавить еще одно условие: query = query.Where (x => x.CategoryId == catId); query + = query.Where (x => x.userid == UserID); – Luke101

0

Вы можете легко создавать запросы таким образом, используя HQL-адрес (Hibernate Query Language) NHibernate. Это была бы почти идентичная реализация, но я бы лично использовал параметры.

public List<Article> GetCat(int cat) 

    { 
     string qry = "select ap from Article a"; 
     if(cat > 0) 
      qry += " where a.categoryID = :cat"; 

     IQuery query = session.CreateQuery(qry).SetInt32("cat",cat); 

     return query.List<Article>(); 
    } 

Это возвращает список объектов <> объектов Статьи, готовых к использованию.

6

NHibernate поддерживает это с помощью Criteria API:

ICriteria criteria = session.CreateCriteria<Article>(); 

if (cat > 0) 
    criteria.Add(Expression.Eq("categoryID", cat)); 
+0

Я полностью согласен с этим. ICriteria была разработана специально для этого типа сценариев, динамических запросов, определенных во время выполнения. –

0

Вы можете использовать предикат Builder и LINQ к NHibernate для создания динамического запроса как это:

//using Predicate Builder 
     public List<Location> FindAllMatching(string[] filters) 
     { 
      var db = Session.Linq<Location>(); 
      var expr = PredicateBuilder.False<Location>(); //-OR- 
      foreach (var filter in filters) 
      { 
       string temp = filter; 
       expr = expr.Or(p => p.Name.Contains(temp)); 
      } 

      return db.Where(expr).ToList(); 
     } 

Вы получаете преимущество типа Сохранить Проверка запросов и компилятора.

Вы также можете использовать тот же подход построителя предикатов с Linq to Sql и Entity Framework.

EDIT: Добавлен пример. Это может быть что-то вроде получения всех мест, соответствующих N регионам мира, где пользователь выбирает регионы, которые он хочет видеть, мы не знаем, сколько пользователей выберет, мы должны построить выражение (OR) на летать, вы можете сделать что-то вроде:

public ActionResult Action(string[] filters) 
{ 
    /*This values are provided by the user, maybe its better to use 
    an ID instead of the name, but for the example is OK. 
    filters will be something like : string[] filters = {"America", "Europe", "Africa"}; 
    */ 
    List<Location> LocationList = FindAllMatchingRegions(filters); 
    return View(LocationList); 
} 

public List<Location> FindAllMatchingRegions(string[] filters) 
     { 
      var db = Session.Linq<Location>(); 
      var expr = PredicateBuilder.False<Location>(); //-OR- 
      foreach (var filter in filters) 
      { 
       string temp = filter; 
       expr = expr.Or(p => p.Region.Name == filter); 
      } 

      return db.Where(expr).ToList(); 
     } 

вы можете Предикаты Nest за сложных сценариев, как это:

Если вы хотите сделать что-то вроде

p => p.Price > 99 && 
    p.Price < 999 && 
    (p.Description.Contains ("foo") || p.Description.Contains ("far")) 

можно построить:

var inner = PredicateBuilder.False<Product>(); 
inner = inner.Or (p => p.Description.Contains ("foo")); 
inner = inner.Or (p => p.Description.Contains ("far")); 

var outer = PredicateBuilder.True<Product>(); 
outer = outer.And (p => p.Price > 99); 
outer = outer.And (p => p.Price < 999); 
outer = outer.And (inner); 

И использовать это нравится:

var pr = db.Products.Where(outer).ToList(); 

предиката Builder Источник и примеры доступны на http://www.albahari.com/nutshell/predicatebuilder.aspx

+0

Мне любопытно этот подход. Можете ли вы привести пример данных, которые будет содержать переменная «Фильтры». Также, как бы вызывающий абонент использовал возвращаемое значение? – Luke101

+0

Я обновил свой пост с помощью примера, его довольно простой, но вы можете сделать почти что угодно с построителем предикатов, например, Nesting Predicates внутренним/внешним выражением. – JOBG

0

Нет любви для LLBLGen? Ну, он тоже может это сделать.

Используя стиль «адаптера»:

RelationPredicateBucket filters = new RelationPredicateBucket(); 
if (cat > 0) 
    filters.Predicate.Add(Article.Fields.CategoryID == cat); 
if (userId > 0) 
    filters.Predicate.Add(Article.Fields.UserID == userId); 
// And so on. 

var adapter = new DataAccessAdapter(); 
var results = new EntityCollection<Article>(new ArticleFactory()); 
adapter.FetchEntityCollection(results, filters); 

Я подозреваю, что большинство ORMs должны быть в состоянии сделать это довольно легко.

10

Как уже упоминалось здесь, LINQ позволяет расширить любой запрос, просто добавив к нему дополнительные критерии.

var query = 
    from x in xs 
    where x==1 
    select x; 

if (mustAddCriteria1) 
    query = 
    from x in query 
    where ... // criteria 1 
    select x; 

if (mustAddCriteria2) 
    query = 
    from x in query 
    where ... // criteria 2 
    select x; 

И так далее. Этот подход работает отлично. Но, скорее всего, вы знаете, что компиляция запросов LINQ довольно дорога: например. Entity Framework может скомпилировать около 500 относительно простых запросов в секунду (см., Например, ORMBattle.NET).

С другой стороны, многие инструментами ORM поддержки скомпилированных запросов:

  • Вы пройти IQueryable экземпляр некоторых Compile метода, и получить делегат, позволяющий выполнить ее намного быстрее позже, потому что перекомпиляция не будет происходить в Это дело.

Но если бы попытаться использовать этот подход здесь, мы сразу замечаем, что наш запрос на самом деле динамический: IQueryable мы выполняем каждый раз, может отличаться от предыдущей. Наличие частей запроса определяется значениями внешних параметров.

Итак, мы можем выполнить такие запросы, как скомпилированные без eg.g. явное кэширование?

DataObjects.Net 4 поддерживает так называемую функцию булевского разветвления. Это означает, что любое константное булево выражение оценивается во время компиляции запроса, и его фактическое значение вводится в SQL-запрос как истинная булева константа (т. Е. Не как значение параметра или как выражение, использующее параметры).

Эта функция позволяет с легкостью генерировать различные планы запросов в зависимости от значений таких булевых выражений. Например.этот код:

int all = new Random().Next(2); 
    var query = 
    from c in Query<Customer>.All 
    where all!=0 || c.Id=="ALFKI" 
    select c; 

будет выполняться с использованием двух различных запросов SQL, и таким образом - два различных плана запроса:

  • Query план, основанный на индексе Seek (довольно быстро), если все == 0
  • план запроса на основе сканирования индекса (довольно медленно), если все = 0

Случай, когда все == NULL, SQL-запрос:

SELECT 
    [a].[CustomerId], 
    111 AS [TypeId] , 
    [a].[CompanyName] 
FROM 
    [dbo].[Customers] [a] 
WHERE((CAST(0 AS bit) <> 0) OR([a].[CustomerId] = 'ALFKI')); 

Случай, когда все == NULL, план запроса:

|--Compute Scalar(DEFINE:([Expr1002]=(111))) 
    |--Clustered Index Seek(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a]), SEEK:([a].[CustomerId]=N'ALFKI') ORDERED FORWARD) 

Второй случай (когда все! = NULL), SQL-запрос: (! Когда все = нуль)

SELECT 
    [a].[CustomerId], 
    111 AS [TypeId] , 
    [a].[CompanyName] 
FROM 
    [dbo].[Customers] [a] 
WHERE((CAST(1 AS bit) <> 0) OR([a].[CustomerId] = 'ALFKI')); 
-- Notice the^value is changed! 

Второй случай , план запроса:

|--Compute Scalar(DEFINE:([Expr1002]=(111))) 
    |--Clustered Index Scan(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a])) 
-- There is index scan instead of index seek! 

Обратите внимание, что почти любой другой ORM будет скомпилировать это запрос с использованием целочисленного параметра:

SELECT 
    [a].[CustomerId], 
    111 AS [TypeId] , 
    [a].[CompanyName] 
FROM 
    [dbo].[Customers] [a] 
WHERE((@p <> 0) OR ([a].[CustomerId] = 'ALFKI')); 
--  ^^ parameter is used here 

Так как SQL-сервер (а также большинство баз данных) генерирует один вариант плана запроса для конкретного запроса, он имеет единственный вариант в данном случае - генерировать план с индексом сканирования:

|--Compute Scalar(DEFINE:([Expr1002]=(111))) 
    |--Clustered Index Scan(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a]), WHERE:(CONVERT(bit,[@p],0)<>(0) OR [DO40-Tests].[dbo].[Customers].[CustomerId] as [a].[CustomerId]=N'ALFKI')) 

Хорошо, это было «быстрое» объяснение полезности этой функции. Вернемся к вашему делу.

Логическое ветвление позволяет реализовать очень простым способом:

var categoryId = 1; 
var userId = 1; 

var query = 
    from product in Query<Product>.All 
    let skipCategoryCriteria = !(categoryId > 0) 
    let skipUserCriteria = !(userId > 0) 
    where skipCategoryCriteria ? true : product.Category.Id==categoryId 
    where skipUserCriteria ? true : 
    (
    from order in Query<Order>.All 
    from detail in order.OrderDetails 
    where detail.Product==product 
    select true 
).Any() 
    select product; 

пример отличается от вашего, но она иллюстрирует идею. В основном я использовал другую модель, чтобы проверить ее (мой пример основан на модели Northwind).

Этот запрос:

  • Не динамический запрос, так что вы можете безопасно передать его Query.Execute(...) метод, чтобы получить его выполнен в виде скомпилированного запроса.
  • Тем не менее каждое его выполнение приведет к такому же результату, как если бы это было сделано с «добавлением» к IQueryable.
+0

Я забыл добавить, почему это удобно: такой запрос может использоваться как скомпилированный запрос в DO4. Очевидно, DO4 будет заботиться об использовании соответствующего SQL-запроса. Если у вас нет этой функции, но необходимо предварительно скомпилировать такой запрос, вы должны добиться того же только с помощью набора «if» и набора скомпилированных запросов. 2 условия = 4 скомпилированных запроса. 3 условия = 8 скомпилированных запросов и т. Д. –

+1

@Alex, это приятно - но можете ли вы указать, где это показано в документации вашего продукта? То, что я делаю, заключается в том, что если функция недоступна для обнаружения - ее не существует. Какой позор для отличного продукта, такого как DataObjects.net. – Aryeh

+0

Правда. На самом деле сама документация (руководство) написана прямо сейчас, и эта функция еще не описана (у нас еще есть набор гораздо более важных для описания ...). Он должен появиться довольно скоро (дни ... неделя). Его последняя редакция всегда доступна здесь: http://dataobjectsdotnet.googlecode.com/hg/Manual/index.htm –

Смежные вопросы