代码之家  ›  专栏  ›  技术社区  ›  Luke101

哪个ORM支持这个

  •  5
  • Luke101  · 技术社区  · 15 年前

    我有一个查询的可选部分,需要在特定条件下执行。下面是示例代码:

    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语句。我目前正在使用实体框架,它不支持这种场景。有没有ORM支持这一点?

    编辑 我试图把这个问题简化一下。但我有大约20个“如果”语句,查询很长。

    我看到的是:

    • 纤维酸盐
    • 亚音速

    我愿意接受任何形式的攻击。谢谢

    8 回复  |  直到 15 年前
        1
  •  10
  •   Alex Yakunin    15 年前

    正如这里已经提到的,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;
    

    ORMBattle.NET ).

    另一方面,许多ORM工具支持编译查询:

    • 你通过一个 IQueryable 实例 Compile

    但是如果我们在这里尝试使用这种方法,我们会立即注意到我们的查询实际上是动态的: 液体 我们执行的每一次都可能与前一次不同。查询部分的存在由外部参数的值决定。

    那么,我们是否可以在没有显式缓存的情况下执行编译后的查询呢?

    数据对象.Net4支持所谓的“布尔分支”特性。它意味着在查询编译期间对任何常量布尔表达式求值,并将其实际值作为真布尔常量(即不是作为参数值或作为使用参数的表达式)注入SQL查询。

    这个特性允许根据这些布尔表达式的值轻松地生成不同的查询计划。E、 g.本规范:

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

    将使用两个不同的SQL查询执行,因此-两个不同的查询计划:

    • 基于索引查找的查询计划(非常快),如果all==0
    • 基于索引扫描的查询计划(相当慢),如果有的话!=0

    =all Case,当为null时:

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

    当all==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!
    

    第二种情况(当所有!=null),查询计划:

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

    这个例子和你的不同,但它说明了这个想法。我使用不同的模型主要是为了能够测试这一点(我的例子是基于om Northwind模型)。

    此查询是:

    • 不是动态查询,因此可以安全地将其传递给 Query.Execute(...) 方法将其作为编译查询执行。
    • 尽管如此,每次执行都会导致相同的结果,就好像这是通过“附加”到 液体 .
        2
  •  9
  •   user226722 user226722    15 年前

    这可以使用linq到sql来完成。。。

    IQueryable<Article> query = yourDataContext.Articles;
    
    if (catId > 0)
      query = query.Where(x => x.CategoryId == catId);
    
    return query.ToList();
    
        3
  •  6
  •   Kevin Pang    15 年前

    NHibernate使用标准API支持这一点:

    ICriteria criteria = session.CreateCriteria<Article>();
    
    if (cat > 0)
        criteria.Add(Expression.Eq("categoryID", cat));
    
        4
  •  2
  •   itowlson    15 年前

    你可能可以用任何一个LINQ提供者来做这个,但是我知道 LightSpeed ORM支持:

    var query = UnitOfWork.Articles;
    if (cat > 0)
      query = query.Where(a => a.CategoryId == cat);
    
        5
  •  0
  •   Ken    15 年前

    我一直在尼伯内特做这种事。

    (我在Rails中也做过类似的事情。我有点惊讶有这么多人 不要 支持这一点。)

        6
  •  0
  •   Jay Tauqeer Ahmad    15 年前

    您可以使用NHibernate的HQL(Hibernate查询语言)以这种方式轻松地构建查询。这将是一个几乎相同的实现,但我个人会使用参数。

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

        7
  •  0
  •   ligos    15 年前

    不爱伊布尔根?它也能做到。

    使用“适配器”样式:

    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应该可以很容易地做到这一点。

        8
  •  0
  •   JOBG    15 年前

    您可以使用谓词生成器和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();
             }
    

    您可以利用类型保存查询和编译器检查的优势。

    编辑:添加示例。 它可能是这样的:获取与世界上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();
    

    谓词生成器源代码和示例位于 http://www.albahari.com/nutshell/predicatebuilder.aspx