【问题标题】:Which ORM Supports this哪个 ORM 支持这个
【发布时间】:2010-12-24 05:56:56
【问题描述】:

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

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 条“IF”语句,而且查询很长。

我查看的 ORM 是:

  • NHibernate
  • LLBLGen
  • 亚音速

我对任何 ORM 持开放态度。谢谢

【问题讨论】:

  • LLBLGen 支持这一点,甚至其中的 20 个。由于 where 子句一次添加一个或多个。
  • @PostMan 你有这个例子吗?
  • 您可以在实体框架中执行此操作,请参阅下面的 tt83 答案。他的回答是针对 Linq to SQL,但实体框架的概念是相同的。不用把婴儿和洗澡水一起扔掉。

标签: c# sql orm


【解决方案1】:

您可以使用任何 LINQ 提供程序执行此操作,但我知道 LightSpeed ORM 支持它:

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

【讨论】:

  • 我试图隐藏查询。但我有大约 20 个“IF”语句,而且查询很长。是否不能在 linq 查询本身中链接 IF 语句?
  • 如果您在 LINQ 查询中链接 if 语句,那么它们将被转换为 SQL,这是否有效取决于 LINQ 提供程序。但是您可以在客户端 if 语句的控制下链接 .Where 调用。
  • 另外,如果您的实际用例要复杂得多,您可能需要编辑您的问题以提供更多真实用例的味道,这样您就不会遇到像我这样的答案只描述了微不足道的案例!
【解决方案2】:

我一直在 NHibernate 中做这种事情。

(我在 Rails 中做过类似的事情。有些 ORM支持这一点,我有点惊讶。)

【讨论】:

  • 不容易——我们有自己的 NHibernate 抽象,我 99.9% 的时间都在使用它。见上面凯文的回答。
【解决方案3】:

这可以使用 linq to sql 来完成...

IQueryable<Article> query = yourDataContext.Articles;

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

return query.ToList();

【讨论】:

  • 我已经编辑了代码。是否可以在 linq 查询中添加多个 where 语句?查看问题中的代码
  • 是的。在这种情况下,IQueryable
    将推迟 SQL 执行,直到您实现它(调用 ToList 等)。您可以添加任意数量的条件。只有当您调用 ToList 时,它才会真正对数据库执行 SQL。
  • 我会添加另一个这样的条件:query = query.Where(x => x.CategoryId == catId); query += query.Where(x => x.userid == UserID);
  • 我们也用这个,效果很好。我们也有很多 Where 语句。甚至构建与 Or 和 And 一起使用的表达式。效果很好:) @Luke101:你不使用+=。只需=
  • 请注意,这样不仅可以在 Lniq-To-Sql 中使用,而且通常可以在任何支持 LINQ 的 ORM 中使用。
【解决方案4】:

您可以使用 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>();
    }

这会返回可供使用的文章对象列表。

【讨论】:

    【解决方案5】:

    NHibernate 使用 Criteria API 支持这一点:

    ICriteria criteria = session.CreateCriteria<Article>();
    
    if (cat > 0)
        criteria.Add(Expression.Eq("categoryID", cat));
    

    【讨论】:

    • 我完全同意这一点。 ICriteria 专为此类场景设计,即在运行时定义的动态查询。
    【解决方案6】:

    您可以使用 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();
             }
    

    您将获得 Type Save Query 和 Compiler check 的优势。

    您也可以使用与 Linq to Sql 和实体框架相同的谓词构建器方法。

    编辑:添加示例。 可能类似于获取与世界上 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();
            }
    

    您可以为这样的复杂场景嵌套谓词:

    如果你想做类似的事情

    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 获得

    【讨论】:

    • 我对这种方法很好奇。您能否举例说明变量“过滤器”将包含的数据类型。另外,调用者将如何使用返回值?
    • 我用一个例子更新了我的帖子,它非常简单,但你可以用谓词构建器做几乎任何事情,比如嵌套谓词内部/外部表达式。
    【解决方案7】:

    不喜欢 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 应该能够很容易地做到这一点。

    【讨论】:

      【解决方案8】:

      正如这里已经提到的,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 查询执行,因此 - 两个不同的查询计划:

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

      all==null 时的情况,SQL 查询:

      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)
      

      第二种情况(当 all!=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!
      

      第二种情况(当 all!=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(...) 方法,使其作为已编译查询执行。
      • 尽管如此,每次执行都会导致与“附加”到IQueryable 相同的结果。

      【讨论】:

      • 我忘了补充为什么这样方便:这样的查询可以用作 DO4 中的编译查询。显然,DO4 会关心使用适当的 SQL 查询。如果您没有此功能,但有必要预编译这样的查询,您应该只使用一组“if”和一组编译查询来实现相同的功能。 2 个条件 = 4 个编译查询。 3 个条件 = 8 个编译查询,依此类推。
      • @Alex,这很好——但你能指出这在你的产品文档中的显示位置吗?我要说的一点是,如果某个功能不可发现 - 它就不存在。对于像 DataObjects.net 这样的伟大产品来说,这是一种耻辱。
      • 是的。实际上,文档本身(手册)现在正在编写中,并且还没有描述这个特性(我们还有一组更重要的特性要描述......)。它必须很快出现(几天...一周)。它的最新版本总是在这里:dataobjectsdotnet.googlecode.com/hg/Manual/index.htm
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-07
      • 2021-03-26
      相关资源
      最近更新 更多