Какой ORM поддерживает это - PullRequest
5 голосов
/ 08 декабря 2009

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

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 заявлений «ЕСЛИ», и запросы очень длинные.

ORM, на которые я смотрел, были:

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

Я открыт для любого ОРМ. Спасибо

Ответы [ 8 ]

10 голосов
/ 08 декабря 2009

Как уже упоминалось, 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, который мы выполняем каждый раз, может отличаться от предыдущего. Наличие частей запроса там определяется значениями внешних параметров.

Таким образом, мы можем выполнять такие запросы, как скомпилированные, например, без явное кеширование?

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 и, следовательно, двух разных планов запросов:

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

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

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

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

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

Второй случай (когда все! = Ноль), 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 Server (как и большинство баз данных) генерирует одну версию плана запроса для конкретного запроса, в этом случае у него есть единственная опция - создать план с проверкой индекса:

|--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.
9 голосов
/ 08 декабря 2009

это можно сделать с помощью linq to sql ...

IQueryable<Article> query = yourDataContext.Articles;

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

return query.ToList();
6 голосов
/ 08 декабря 2009

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

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

if (cat > 0)
    criteria.Add(Expression.Eq("categoryID", cat));
2 голосов
/ 08 декабря 2009

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

var query = UnitOfWork.Articles;
if (cat > 0)
  query = query.Where(a => a.CategoryId == cat);
0 голосов
/ 08 декабря 2009

Нет любви к 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);

Я подозреваю, что большинство ORM смогут сделать это довольно легко.

0 голосов
/ 08 декабря 2009

Вы можете использовать Predicate Builder и LINQ to 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.

РЕДАКТИРОВАТЬ: Добавлен пример. Это может быть что-то вроде получения всех местоположений, соответствующих N регионам мира, где пользователь выбирает регионы, которые он хочет видеть, мы не знаем, сколько пользователь выберет, мы должны построить выражение (ИЛИ) на лету , вы можете сделать что-то вроде:

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();
        }

Вы можете вкладывать предикаты для сложных сценариев, подобных этому:

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

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();

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

0 голосов
/ 08 декабря 2009

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

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>();
    }

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

0 голосов
/ 08 декабря 2009

Я делаю подобные вещи в NHibernate все время.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...