我有一个可选的查询部分需要在某种条件下执行。以下是示例代码:
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是:
我对任何ORM持开放态度。感谢
答案 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查询执行,因此 - 两个不同的查询计划:
所有== 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应该能够很容易地做到这一点。