哪个ORM支持这个

时间:2009-12-08 00:55:38

标签: c# sql 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语句。我目前正在使用实体框架,它不支持这种情况。那里有ORM支持吗?

修改 我试图假设查询。但我有大约20个“IF”语句,而且查询很长。

我看到的ORM是:

  • NHibernate的
  • LLBLGEN
  • 亚音速

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

8 个答案:

答案 0 :(得分: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查询的编译非常昂贵:例如实体框架每秒可以编译大约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

所有== 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!

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

这个例子与你的不同,但它说明了这个想法。我主要使用不同的模型来测试这个(我的例子是基于Northwind模型)。

此查询为:

  • 不是动态查询,因此您可以安全地将其传递给Query.Execute(...)方法,以便将其作为编译查询执行。
  • 尽管如此,每次执行都会产生相同的结果,就好像通过“附加”到IQueryable来完成。

答案 1 :(得分:9)

这可以使用linq to sql ...

完成
IQueryable<Article> query = yourDataContext.Articles;

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

return query.ToList();

答案 2 :(得分:6)

NHibernate使用Criteria API支持这一点:

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

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

答案 3 :(得分:2)

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

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

答案 4 :(得分:0)

我一直在NHibernate做这件事。

(我在Rails中做了类似的事情。我很惊讶有支持这个的ORM。)

答案 5 :(得分:0)

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

这会返回一个List&lt;&gt;准备使用的物品对象。

答案 6 :(得分:0)

您可以使用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个区域,用户选择他想要看到的区域,我们不知道用户将选择多少,我们必须动态构建(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();

Predicate Builder来源和示例可在http://www.albahari.com/nutshell/predicatebuilder.aspx

获得

答案 7 :(得分: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);

我怀疑大多数ORM应该能够很容易地做到这一点。