可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm developing an application with NHibernate 3.0. I have developed a Repository hat accept a expression to do some filter with QueryOver. My method is something like this:
public IEnumerable<T> FindAll(Expression<Func<T, bool>> filter) {
return Session.QueryOver<T>().Where(filter).List();
}
It works fine. So, I have a Service layer as well, and My methods in this services accepts primitives types, like this:
public IEnumerable<Product> GetProducts(string name, int? stock, int? reserved) {
// how init the expression ?
Expression<Func<Product, bool>> expression = ???;
if (!string.IsNullOrEmpty(name)) {
//add AND condition for name field in expression
}
if (stock.HasValue) {
//add AND condition for stock field in expression
}
if (reserved.HasValue) {
//add AND condition for reserved field in expression
}
return _repository.FindAll(expression);
}
My doubts are:
Is it possible ? Ta add some conditions when necessary (when my parameters has value) ?
Thanks
/// my edits
public ActionResult Index(ProductFilter filter) {
if (!string.IsNullOrEmpty(filter.Name) {
return View(_service.GetProductsByName(filter.Name))
}
// others conditions
}
/// Almost a solution
Expression<Func<Product, bool>> filter = x => true;
if (!string.IsNullOrEmpty(name))
filter = x => filter.Compile().Invoke(x) && x.Name == name;
if (stock.HasValue)
filter = x => filter.Compile().Invoke(x) && x.Stock == stock.Value;
if (reserved.HasValue)
filter = x => filter.Compile().Invoke(x) && x.Reserved == reserved.Value;
return _repository.FindAll(filter);
回答1:
Here is a way to do this. I am not going to editorialize on what you are doing - it looks like query by example, which is almost always problematic. It is as the others here have best avoided. The expression thing is interesting though - so I thought it was worth a crack at it.
class MyClass
{
public string Name { get; set; }
public bool Hero { get; set; }
public int Age { get; set; }
}
And we want to query it like this:
string name = null;
int? age = 18;
Expression<Func<MyClass, bool>> myExpr =
x => (string.IsNullOrEmpty(name) || x.Name == name) &&
(!age.HasValue || x.Age > (age ?? 0));
myExpr = myExpr.RemoveCloture(); // this line here - removes the cloture -
// and replaces it with constant values - and shortcuts
// boolean evaluations that are no longer necessary.
// in effect this expression now becomes :
// x => x.Age > 18
bool result = myExpr.Compile()(
new MyClass {Name = "Rondon", Hero = true, Age = 92});
So all you have to do is write RemoveCloture();
- not a problem.
// using System;
// using System.Linq.Expressions;
public static class ClotureRemover
{
#region Public Methods
public static Expression<TExpressionType> RemoveCloture<TExpressionType>(
this Expression<TExpressionType> e)
{
var converter = new RemoveClotureVisitor();
var newBody = converter.Visit(e.Body);
return Expression.Lambda<TExpressionType>(newBody, e.Parameters);
}
#endregion
private class RemoveClotureVisitor : ExpressionVisitor
{
public RemoveClotureVisitor()
{
}
public override Expression Visit(Expression node)
{
if (!RequiresParameterVisitor.RequiresParameter(node))
{
Expression<Func<object>> funct = () => new object();
funct = Expression.Lambda<Func<object>>(Expression.Convert(node, typeof(object)), funct.Parameters);
object res = funct.Compile()();
return ConstantExpression.Constant(res, node.Type);
}
return base.Visit(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
if ((node.NodeType == ExpressionType.AndAlso) || (node.NodeType == ExpressionType.OrElse))
{
Expression newLeft = Visit(node.Left);
Expression newRight = Visit(node.Right);
bool isOr = (node.NodeType == ExpressionType.OrElse);
bool value;
if (IsBoolConst(newLeft, out value))
{
if (value ^ isOr)
{
return newRight;
}
else
{
return newLeft;
}
}
if (IsBoolConst(newRight, out value))
{
if (value ^ isOr)
{
return newLeft;
}
else
{
return newRight;
}
}
}
return base.VisitBinary(node);
}
protected override Expression VisitUnary(UnaryExpression node)
{
if (node.NodeType == ExpressionType.Convert || node.NodeType == ExpressionType.ConvertChecked)
{
Expression newOpperand = Visit(node.Operand);
if (newOpperand.Type == node.Type)
{
return newOpperand;
}
}
return base.VisitUnary(node);
}
private static bool IsBoolConst(Expression node, out bool value)
{
ConstantExpression asConst = node as ConstantExpression;
if (asConst != null)
{
if (asConst.Type == typeof(bool))
{
value = (bool)asConst.Value;
return true;
}
}
value = false;
return false;
}
}
private class RequiresParameterVisitor : ExpressionVisitor
{
protected RequiresParameterVisitor()
{
result = false;
}
public static bool RequiresParameter(Expression node)
{
RequiresParameterVisitor visitor = new RequiresParameterVisitor();
visitor.Visit(node);
return visitor.result;
}
protected override Expression VisitParameter(ParameterExpression node)
{
result = true;
return base.VisitParameter(node);
}
internal bool result;
}
}
回答2:
First, I'd solve your problem by avoiding it in the first place. I'd have different methods for this.
public IEnumerable<Product> GetProductsByName(string name)
public IEnumerable<Product> GetProudctsByNameAndStock(string name, int stock)
public IEnumerable<Product> GetProductsByNameAndReserved(
string name,
int reserved
)
public IEnumerable<Product> GetProducts(string name, int stock, int reserved)
These all have trivially easy implementations in terms of a lambda expression. For example:
public IEnumerable<Product> GetProductsByName(string name) {
return GetProductsByExpression(p => p.Name == name);
}
private IEnumerable<Product> GetProductsByExpression(
Expression<Func<Product, bool>> expression
) {
return _repository.FindAll(expression);
}
etc.
Is it possible ? Ta add some conditions when necessary (when my parameters has value) ?
Second, yes what you want to do is possible but it's not the way I'd solve the problem.
回答3:
Your repository method definition suggests that you see FindAll as something that you pass criteria in and get a completed result back. Why not instead just have the result be of type IQueryable and return Session.QueryOver?
Your service layer would then do something like this, chaining together the "wheres":
var query = _repository.FindAll();
if (!string.IsNullOrEmpty(name))
query = query.Where(x => x.Name == name);
if (stock.HasValue)
query = query.Where(x => x.Stock == stock);
etc...
return query.ToList();
回答4:
So here is how you could actually and lambdas together - it borrows most of it's code from this awesome answer from desco that deserves an up-vote.
public static class AddExpressions
{
public static Expression<Func<TFrom, TTo>> AndLambdas<TFrom, TTo>(this Expression<Func<TFrom, TTo>> first, Expression<Func<TFrom, TTo>> second)
{
ParameterExpression paramToUse = first.Parameters[0];
Expression bodyLeft = first.Body;
ConversionVisitor visitor = new ConversionVisitor(paramToUse, second.Parameters[0]);
Expression bodyRight = visitor.Visit(second.Body);
return Expression.Lambda<Func<TFrom, TTo>>(Expression.MakeBinary(ExpressionType.AndAlso, bodyLeft, bodyRight), first.Parameters);
}
class ConversionVisitor : ExpressionVisitor
{
private readonly ParameterExpression newParameter;
private readonly ParameterExpression oldParameter;
public ConversionVisitor(ParameterExpression newParameter, ParameterExpression oldParameter)
{
this.newParameter = newParameter;
this.oldParameter = oldParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return newParameter; // replace all old param references with new ones
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression != oldParameter) // if instance is not old parameter - do nothing
return base.VisitMember(node);
var newObj = Visit(node.Expression);
var newMember = newParameter.Type.GetMember(node.Member.Name).First();
return Expression.MakeMemberAccess(newObj, newMember);
}
}
}
Then calling the code is quite simple ....
class MyClass
{
public string Name { get; set; }
public bool Hero { get; set; }
public int Age { get; set; }
}
...
Expression<Func<MyClass, bool>> expression1 = x => x.Age > (age ?? 0);
Expression<Func<MyClass, bool>> expression2 = x => x.Name == name;
expression1 = expression1.AndLambdas(expression2);
result = expression1.Compile()(new MyClass {
Name = "Rondon",
Hero = true,
Age = 92 });