C# Refactor function to use generics and lambdas - c#

i'm trying to refactor some code that is going to be similar in several areas. This piece of code is from a validator using fluentvalidation. I'm trying to transform the HaveUniqueNumber routine to be generic and pass in lambdas that will be used in the db query.
public class AddRequestValidator : AbstractValidator<AddRequest>
{
public AddRequestValidator()
{
RuleFor(x => x)
.Must(x => HaveUniqueNumber(x.myNumber, x.parentId))
.WithMessage("myNumber '{0}' already exists for parentId '{1}'.", x => x.myNumber, x=>x.parentId);
}
private bool HaveUniqueNumber(string number, int parentId)
{
myModel existingNumber = null;
using (var context = new myContextDb())
{
existingNumber = context.myModel.SingleOrDefault(a => a.MyNumber == number && a.ParentId == parentId);
}
return existingNumber == null;
}
}
i tried implementing something like :
public class AddRequestValidator : AbstractValidator<AddRequest>
{
public AddRequestValidator()
{
RuleFor(x => x)
.Must(x => HaveUniqueNumber<myModel>(an => an.myNumber == x.MyNumber, an => an.parentId == x.parentId))
.WithMessage("myNumber '{0}' already exists for parentId '{1}'.", x => x.myNumber, x=>x.parentId);
}
private bool HaveUniqueNumber<T>(Expression<Func<T, bool>> numberExpression, Expression<Func<T, bool>> parentExpression)
{
T existingNumber = default(T);
using (var context = new myContextDb())
{
existingNumber = context.T.SingleOrDefault(numberExpression && parentExpression);
}
return existingNumber == null;
}
}
how can i get an something similar to the original to work.

Needed to fix the following:
context.T to context.DbSet<T>()
added the ExpressionVisitor Class as referenced by the link posted by Alexei Levenkov. 457316
Added where T: class to HaveUniqueMethod
ended up with this refactored class:
private bool HaveUniqueNumber<T>(Expression<Func<T, bool>> numberExpression, Expression<Func<T, bool>> parentExpression) where T : class
{
T existingNumber = default(T);
using (var context = new myContextDb())
{
existingNumber = context.Set<T>().SingleOrDefault(numberExpression.AndAlso(parentExpression));
}
return existingNumber == null;
}
and added this extension method:
public static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var parameter = Expression.Parameter(typeof (T));
var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
var left = leftVisitor.Visit(expr1.Body);
var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
var right = rightVisitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(left, right), parameter);
}
private class ReplaceExpressionVisitor
: ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;
public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == _oldValue)
return _newValue;
return base.Visit(node);
}
}

Related

How to make all fields search in ASP.Net Api

I have this Get method:
[HttpGet]
public async Task<ActionResult<IEnumerable<Customer>>> GetCustomers(string s, int page, int page_size)
{
IQueryable<Customer> query = _context.Customers;
if (!string.IsNullOrEmpty(s))
{
var stringProperties = typeof(Customer).GetProperties();
query = query.Where(c => stringProperties.Any(prop => prop.GetValue(c, null).ToString().Contains(s)));
}
return await query.Skip((page - 1) * page_size).Take(page_size).ToListAsync();
}
But my realization of search doesn't work, I get this error
.Where(m => __stringProperties_0 .Any(prop => prop.GetValue(obj: m, index: null).ToString().Contains(__8__locals1_s_1))) could not be
translated
How can I fix this?
Well, basic mistake that you are dealing with PropertyInfo.GetValue. On the SQL server side you do not have objects and cannot get values in that way. What you ca do it is instruct EF how compare properties.
I have created universal function, which may help to create such queries. Instead of just dealing with strings it can be adopted to any type.
Also consider to use extensions, if it is possible.
public static IQueryable<Customer> FilterCustomers(this IQueryable<Customer> query, string s)
{
if (!string.IsNullOrEmpty(s))
{
query = query.FilterByProperties(s, (prop, value) => prop.Contains(value), true)
}
return query;
}
public static Task<List<T>> PaginateAsync<T>(this IQueryable<T> query, int page, int page_size)
{
return query.Skip((page - 1) * page_size).Take(page_size).ToListAsync();
}
[HttpGet]
public Task<List<Customer>> GetCustomers(string s, int page, int page_size)
{
var query = _context.Customers.FilterCustomers(s);
return query.PaginateAsync(page, page_size);
}
And implementation:
public static class QueryableExtensions
{
public static Expression<Func<T, bool>> MakePropertiesPredicate<T, TValue>(Expression<Func<TValue, TValue, bool>> pattern, TValue searchValue, bool isOr)
{
var parameter = Expression.Parameter(typeof(T), "e");
var searchExpr = Expression.Constant(searchValue);
var predicateBody = typeof(T).GetProperties()
.Where(p => p.PropertyType == typeof(TValue))
.Select(p =>
ExpressionReplacer.GetBody(pattern, Expression.MakeMemberAccess(
parameter, p), searchExpr))
.Aggregate(isOr ? Expression.OrElse : Expression.AndAlso);
return Expression.Lambda<Func<T, bool>>(predicateBody, parameter);
}
public static IQueryable<T> FilterByProperties<T, TValue>(this IQueryable<T> query, TValue searchValue,
Expression<Func<TValue, TValue, bool>> pattern, bool isOr)
{
return query.Where(MakePropertiesPredicate<T, TValue>(pattern, searchValue, isOr));
}
class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression node)
{
if (node != null && _replaceMap.TryGetValue(node, out var replacement))
return replacement;
return base.Visit(node);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
.ToDictionary(i => (Expression)lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
}
}
}

Apply Where conditionally (OR between them) LINQ

I have filters in my asp.net project and want to add expression to this list with condition:
Expression<Func<MyModel, bool>> func;
var list = new List<Expression<Func<MyModel, bool>>>();
I want to conditionally apply Where (OR between them). for example:
if(sth){
func = p => p.a <= b;
list.Add(func);
}
if (sth else){
func = p => p.c >= d;
list.Add(func);
}
var fq = Session.Query<MyModel>();
fq = list.Aggregate(fq, (current, expression) => current.Where(expression));
How can I do this?
You can build an extension method to merge two condition expressions using OR relationship like this:
public static class Extensions
{
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another)
{
var parameter = one.Parameters[0];
var visitor = new ReplaceParameterVisitor(parameter);
another = (Expression<Func<T, bool>>)visitor.Visit(another);
var body = Expression.Or(one.Body, another.Body);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
}
class ReplaceParameterVisitor : ExpressionVisitor
{
public ParameterExpression NewParameter { get; private set; }
public ReplaceParameterVisitor(ParameterExpression newParameter)
{
this.NewParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return this.NewParameter;
}
}
Usage and test code:
Expression<Func<int, bool>> condition1 = x => x > 8;
Expression<Func<int, bool>> condition2 = y => y < 3;
var condition = condition1.Or(condition2);
var result = Enumerable
.Range(1, 10)
.Where(condition.Compile())
.ToList(); //1,2,9,10
Looks like you could do this easily with an extension method
public static class EnumerableExtensions
{
public static IEnumerable<T> ConditionalWhere<T>(this IEnumerable<T> list,
bool condition, Func<T,bool> predicate)
{
if(!condition)
return list;
return list.Where(predicate);
}
}
Usage would be:
var fq = Session.Query<MyModel>();
var result = fq.ConditionalWhere(sth, p => p.a <= b)
.ConditionalWhere(sth_else, p => p.c >= d);
I built a bit of code to showcase Predicate<> while trying to stick to your program structure:
using System;
using System.Collections.Generic;
using System.Linq;
namespace SOTests
{
public class MyModel
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
private static int ControlId;
private static string ControlName;
static void Main(string[] args)
{
var idPred = new Predicate<MyModel>(m => m.Id > ControlId);
var namePred = new Predicate<MyModel>(m => m.Name == ControlName);
var list = new List<MyModel>();
if (true) // TODO: do id check?
{
list = list.Where(m => idPred.Invoke(m)).ToList();
}
if (true) // TODO: do name check?
{
list = list.Where(m => namePred.Invoke(m)).ToList();
}
//var fq = Session.Query<MyModel>();
//fq = list;
}
}
}
I commented out the Session bit not knowing what kind of storage abstraction it represents (and the code wouldn't compile).
The code should explain itself and it's not tested.
It can be much more elegant, but you should state more clearly what your requirements are for that.
Thanks all, I found the solution : OrElse
Expression<Func<MyModel, bool>> OrExpressionFunction(Expression<Func<MyModel, bool>> exp1, Expression<Func<MyModel, bool>> exp2)
{
ParameterExpression p = exp1.Parameters.Single();
return Expression.Lambda<Func<MyModel, bool>>(
Expression.OrElse(exp1.Body, exp2.Body), p);
}
then:
Expression<Func<MyModel, bool>> MyExp = null;
if(sth){
func = p => p.a <= b;
MyExp= OrExpressionFunction(func, MyExp);
}
if (sth else){
func = p => p.c >= d;
MyExp= OrExpressionFunction(func, MyExp);
}
list.Add(MyExp);

How can I compose a LINQ query with the help of a predicate builder

I actually wanted to share this piece of code as I think it is of great use.
So, think that you want to programtically create a linq query.
It would be very nice to have that in a readable format, would it not?
We could create code like this:
PredicateBuilder<Document> filter = new PredicateBuilder<Document>();
filter.Add(x => x.IsDeleted == false);
filter.Add(x => x.IsArchive == false);
if (ucSearch.SearchText != "")
filter.Add(x => x.DocumentName.Contains(ucSearch.SearchText));
repository.GetAll().Where(filter.GetLambda()).ToList();
The code can be achieved with the help of a helper class like the one below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
namespace Extensions
{
public class PredicateBuilder<T> : List<Expression<Func<T, bool>>>
{
private class ParameterReplacer : ExpressionVisitor
{
private readonly ParameterExpression _parameter;
public ParameterReplacer(ParameterExpression parameter)
{
_parameter = parameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return base.VisitParameter(_parameter);
}
}
public Expression<Func<T, bool>> GetLambda()
{
if (this.Count > 0)
{
var type = Expression.Parameter(typeof(T));
if (this.Count > 1)
{
BinaryExpression binaryExpression = Expression.MakeBinary(ExpressionType.And,
new ParameterReplacer(type).Visit(this.First().Body),
new ParameterReplacer(type).Visit(this.Skip(1).First().Body));
if (this.Count > 2)
{
foreach (var ex in this.ToList().Skip(2))
binaryExpression = Expression.And(binaryExpression, new ParameterReplacer(type).Visit(ex.Body));
}
return Expression.Lambda<Func<T, bool>>(binaryExpression, type);
}
else
return Expression.Lambda<Func<T, bool>>(new ParameterReplacer(type).Visit(this.First().Body), type);
}
return null;
}
public Func<T, bool> GetPredicate()
{
if (this.Count > 0)
{
var type = Expression.Parameter(typeof(T));
if (this.Count > 1)
{
BinaryExpression binaryExpression = Expression.MakeBinary(ExpressionType.And,
new ParameterReplacer(type).Visit(this.First().Body),
new ParameterReplacer(type).Visit(this.Skip(1).First().Body));
if (this.Count > 2)
{
foreach (var ex in this.ToList().Skip(2))
binaryExpression = Expression.And(binaryExpression, new ParameterReplacer(type).Visit(ex.Body));
}
return Expression.Lambda<Func<T, bool>>(binaryExpression, type).Compile();
}
else
return Expression.Lambda<Func<T, bool>>(new ParameterReplacer(type).Visit(this.First().Body), type).Compile();
}
return null;
}
}
}

How to correct replace type of Expression?

I have two classes:
public class DalMembershipUser
{
public string UserName { get; set; }
//other members
}
public class MembershipUser
{
public string UserName { get; set; }
//other members
}
I have function:
public IEnumerable<DalMembershipUser> GetMany(Expression<Func<DalMembershipUser, bool>> predicate)
{
//but here i can use only Func<MembershipUser, bool>
//so i made transformation
query = query.Where(ExpressionTransformer<DalMembershipUser,MembershipUser>.Tranform(predicate));
}
Current implementation:
public static class ExpressionTransformer<TFrom, TTo>
{
public class Visitor : ExpressionVisitor
{
private ParameterExpression _targetParameterExpression;
public Visitor(ParameterExpression parameter)
{
_targetParameterExpression = parameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return _targetParameterExpression;
}
}
public static Expression<Func<TTo, bool>> Tranform(Expression<Func<TFrom, bool>> expression)
{
ParameterExpression parameter = Expression.Parameter(typeof(TTo), expression.Parameters[0].Name);
Expression body = expression.Body;
new Visitor(parameter).Visit(expression.Body);
return Expression.Lambda<Func<TTo, bool>>(body, parameter);
}
}
//Somewhere: .GetMany(u => u.UserName == "username");
Exception: Property 'System.String UserName' is not defined for type 'MembershipUser'
at line: new Visitor(parameter).Visit(expression.Body);
Finally it work. But still don't understand why clear parameters creation:
Expression.Parameter(typeof(TTo), from.Parameters[i].Name); not work and need to extract.
public static class ExpressionHelper
{
public static Expression<Func<TTo, bool>> TypeConvert<TFrom, TTo>(
this Expression<Func<TFrom, bool>> from)
{
if (from == null) return null;
return ConvertImpl<Func<TFrom, bool>, Func<TTo, bool>>(from);
}
private static Expression<TTo> ConvertImpl<TFrom, TTo>(Expression<TFrom> from)
where TFrom : class
where TTo : class
{
// figure out which types are different in the function-signature
var fromTypes = from.Type.GetGenericArguments();
var toTypes = typeof(TTo).GetGenericArguments();
if (fromTypes.Length != toTypes.Length)
throw new NotSupportedException("Incompatible lambda function-type signatures");
Dictionary<Type, Type> typeMap = new Dictionary<Type, Type>();
for (int i = 0; i < fromTypes.Length; i++)
{
if (fromTypes[i] != toTypes[i])
typeMap[fromTypes[i]] = toTypes[i];
}
// re-map all parameters that involve different types
Dictionary<Expression, Expression> parameterMap = new Dictionary<Expression, Expression>();
ParameterExpression[] newParams = GenerateParameterMap<TFrom>(from, typeMap, parameterMap);
// rebuild the lambda
var body = new TypeConversionVisitor<TTo>(parameterMap).Visit(from.Body);
return Expression.Lambda<TTo>(body, newParams);
}
private static ParameterExpression[] GenerateParameterMap<TFrom>(
Expression<TFrom> from,
Dictionary<Type, Type> typeMap,
Dictionary<Expression, Expression> parameterMap
)
where TFrom : class
{
var newParams = new ParameterExpression[from.Parameters.Count];
for (int i = 0; i < newParams.Length; i++)
{
Type newType;
if (typeMap.TryGetValue(from.Parameters[i].Type, out newType))
{
parameterMap[from.Parameters[i]] = newParams[i] = Expression.Parameter(newType, from.Parameters[i].Name);
}
}
return newParams;
}
class TypeConversionVisitor<T> : ExpressionVisitor
{
private readonly Dictionary<Expression, Expression> parameterMap;
public TypeConversionVisitor(Dictionary<Expression, Expression> parameterMap)
{
this.parameterMap = parameterMap;
}
protected override Expression VisitParameter(ParameterExpression node)
{
// re-map the parameter
Expression found;
if (!parameterMap.TryGetValue(node, out found))
found = base.VisitParameter(node);
return found;
}
public override Expression Visit(Expression node)
{
LambdaExpression lambda = node as LambdaExpression;
if (lambda != null && !parameterMap.ContainsKey(lambda.Parameters.First()))
{
return new TypeConversionVisitor<T>(parameterMap).Visit(lambda.Body);
}
return base.Visit(node);
}
protected override Expression VisitMember(MemberExpression node)
{
// re-perform any member-binding
var expr = Visit(node.Expression);
if (expr.Type != node.Type)
{
if (expr.Type.GetMember(node.Member.Name).Any())
{
MemberInfo newMember = expr.Type.GetMember(node.Member.Name).Single();
return Expression.MakeMemberAccess(expr, newMember);
}
}
return base.VisitMember(node);
}
}
}
You need to use the result expression returned by the Visit method.
Just change:
Expression body = expression.Body;
new Visitor(parameter).Visit(expression.Body);
by
Expression body = new Visitor(parameter).Visit(expression.Body);

Expression tree condition conversion with selector

I have an entity class like
public class BookPage {
public int PageIndex { get; set; }
}
then I have an expression:
Expression<Func<int, bool>> pageIndexCondition = idx => idx == 1;
and the expression I want:
Expression<Func<BookPage, bool>> pageCondition = bookPage => bookPage.PageIndex == 1;
The question: How do I use pageIndexCondition to do LINQ-to-SQL query, or how can I convert pageIndexCondition into pageCondition?
Edit: Another solution that would be less elegant, but still meet my requirement is:
Expression<Func<T, bool>> GetPageIndexCondition(Expression<Func<T, int>> selector) {
return (T item) => selector(item) < 10; // This won't work because selector is Expression, so how to implement this correctly?
}
...
var pageCondition = GetPageIndexCondition(page => page.PageIndex);
I like doing these things, though as others have said, there's probably more efficient and better ways to do it:
void Main()
{
Expression<Func<int, bool>> pageIndexCondition = idx => idx == 1;
Expression<Func<BookPage, bool>> converted = ExpressionConverter.Convert(pageIndexCondition);
}
public class ExpressionConverter : ExpressionVisitor
{
public static Expression<Func<BookPage, bool>> Convert(Expression<Func<int, bool>> e)
{
var oldParameter = e.Parameters.First();
var newParameter = Expression.Parameter(typeof(BookPage), "bp");
Expression<Func<BookPage, int>> x = (BookPage bp) => bp.PageIndex;
var property = ((x.Body as MemberExpression).Member as PropertyInfo);
var memberAccess = Expression.Property(newParameter, property);
var converter = new ExpressionConverter(oldParameter, memberAccess);
return (Expression<Func<BookPage, bool>>)Expression.Lambda(converter.Visit(e.Body), newParameter);
}
private ParameterExpression pe;
private Expression replacement;
public ExpressionConverter(ParameterExpression pe, Expression replacement)
{
this.pe = pe;
this.replacement = replacement;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if(node == pe)
return replacement;
return base.VisitParameter(node);
}
}
var pages = new List<BookPage>
{
new BookPage { PageIndex = 1 },
new BookPage { PageIndex = 2 }
};
Expression<Func<BookPage, bool>> pageCondition = bookPage => bookPage.PageIndex == 1;
BookPage result = pages.AsQueryable().Single(pageCondition);
If you want a generic select by id you will have to do something like;
public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null)
{
if (filter != null)
{
query = query.Where(filter);
}
}
This goes in your generic repository.

Categories

Resources