简体   繁体   中英

Convert type in Expression<Func<T1, bool>> to Expression<Func<T2, bool>> (LINQ to SQL)

Note:

  1. My app (asp.net mvc) is separated on layers for loose coupling purposes.
  2. Entity krt_Naftan and ScrollLineDTO have same property and use in different layers. I use Automapper to convert between them.
  3. I would like use some predicate ( x => x.DTBUHOTCHET == '01.01.2016' ) througout LINQ to SQL (Expression tree).

It works for func:

Func<ScrollLineDTO, bool> predicate = x => x.DTBUHOTCHET == '01.01.2016';
Func<krt_Naftan, bool> func = x => predicate(Mapper.Map<ScrollLineDTO>(x));

Because I can't wrap func to Expression tree (LINQ to SQL) //doesn't work for EF6

Expression<Func<krt_Naftan, bool>> filter = x => func(x);

I try convert type in expressions (compile error)

Expression<Func<ScrollLineDTO, bool>> predicate = x => x.DTBUHOTCHET == '01.01.2016';

Expression<Func<krt_Naftan, bool>> func = x =>predicate(Mapper.Map<ScrollLineDTO>(x));

Question: How can i use convert functionality for expression tree statements? or maybe a need something else;)

1) Index method (UI layer)

public ActionResult Index(DateTime? period = null, int page = 1, bool asService = false, ushort initialSizeItem = 15) {
    if (Request.IsAjaxRequest()) {
        long recordCount;

        //default
        Expression<Func<ScrollLineDTO, bool>> predicate = x => x.DTBUHOTCHET == (period == null ? new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1) : period);

        var result = new IndexMV() {
            ListKrtNaftan = _bussinesEngage.SkipTable(page, initialSizeItem, out recordCount, predicate),
                ...
            }
        };
  ....

2)(BLL layer)

 public IEnumerable<T> SkipTable<T>(int page, int initialSizeItem, out long recordCount, Expression<Func<T, bool>> predicate = null) {

            if (predicate == null) {
                //convert func types

                Expression<Func<krt_Naftan, bool>> func = x => predicate(Mapper.Map<ScrollLineDTO>(x));
                //wrap in func to expression (is not impossible, maybe if pass method...)
                //Expression<Func<krt_Naftan, bool>> filter = x => func(x);

                return (IEnumerable<T>)Mapper.Map<IEnumerable<ScrollLineDTO>>(Engage.GetSkipRows(page, initialSizeItem, out recordCount, x => x.KEYKRT, func));
            }

            return (IEnumerable<T>)Mapper.Map<IEnumerable<ScrollLineDTO>>(Engage.GetSkipRows<krt_Naftan, long>(page, initialSizeItem, out recordCount, x => x.KEYKRT));
        }

/// <summary>
/// Return pagging part of table and general count of rows
/// </summary>
/// <typeparam name="T">Current enity</typeparam>
/// <typeparam name="TKey">Type for ordering</typeparam>
/// <param name="page">Number page</param>
/// <param name="size">Count row per one page</param>
/// <param name="recordCount"></param>
/// <param name="orderPredicate">Condition for ordering</param>
/// <param name="filterPredicate">Condition for filtering</param>
/// <param name="caсhe"></param>
/// <returns>Return definition count rows of specific entity</returns>
public IEnumerable<T> GetSkipRows<T, TKey>(int page, int size, out long recordCount, Expression<Func<T, TKey>> orderPredicate, Expression<Func<T, bool>> filterPredicate = null, bool caсhe = false) where T : class {
    recordCount = GetCountRows(filterPredicate);
    using (Uow = new UnitOfWork()) {
        return Uow.Repository<T>().Get_all(filterPredicate, caсhe).OrderByDescending(orderPredicate).Skip((page - 1) * size).Take(size).ToList();
    }
}

3) (DLL layer) get data from db

/// <summary>
            /// Get lazy data set (with cashing or not (attr MergeOption )
            /// </summary>
            /// <param name="predicate">filter condition for retrieving data from source(database)</param>
            /// <param name="enableDetectChanges">Compare two snapshot of data (one when retrieve data from database other when call method saveChanges(). If exists some diffrences => generate avaible SQL command</param>
            /// <param name="enableTracking"></param>
            /// <returns></returns>
            public IQueryable<T> Get_all(Expression<Func<T, bool>> predicate = null, bool enableDetectChanges = true, bool enableTracking = true) {
                /*//sync data in Db & EF (if change not tracking for EF)
                    ((IObjectContextAdapter)_context).ObjectContext.Refresh(RefreshMode.StoreWins, _dbSet.Where(predicate));
                    _context.Entry(_dbSet.Where(predicate)).Reload(); EF 4.1+*/
                ActiveContext.Configuration.AutoDetectChangesEnabled = enableDetectChanges;
                if (predicate == null) return (enableTracking) ? _dbSet : _dbSet.AsNoTracking();
                var result = (enableTracking) ? _dbSet.Where(predicate) : _dbSet.AsNoTracking().Where(predicate);

                return result;
            }

thx)

I've found what i needed!

  1. Replace a type in an expression tree
  2. How to change a type in an expression tree?

Solusion is the Visitor pattern (lINQ have Build-in ExpressionVisitor implementaion (С# 4.0)).

In my case implementaion is:

  public static Expression<Func<OutT, bool>> ConvertTypeExpression<inT, OutT>(Expression expression) where OutT : class {

            var param = Expression.Parameter(typeof(OutT), "x");

            var result = new CustomExpVisitor<OutT>(param).Visit(expression); 

            Expression<Func<OutT, bool>> lambda = Expression.Lambda<Func<OutT, bool>>(result, new[] { param });

            return lambda;
        }

   private class CustomExpVisitor<T> : ExpressionVisitor {
            ParameterExpression _param;

            public CustomExpVisitor(ParameterExpression param) {
                _param = param;
            }

            protected override Expression VisitParameter(ParameterExpression node) {
                return _param;
            }

            protected override Expression VisitMember(MemberExpression node) {
                if (node.Member.MemberType == MemberTypes.Property) {
                    MemberExpression memberExpression = null;

                    var memberName = node.Member.Name;
                    var otherMember = typeof(T).GetProperty(memberName);

                    memberExpression = Expression.Property(Visit(node.Expression), otherMember);

                    return memberExpression;
                } else {
                    return base.VisitMember(node);
                }
            }
        }

and in the end i got functionaly that i searched)

var filterPredicate = PredicateExtensions.ConvertTypeExpression<ScrollLineDTO, krt_Naftan>(predicate.Body);

This solution works for single expressions (x=>x.TypeId=="MyType") but it works in production for some time at this point.

T1 and T2 do not need to be assignable to eachother.

The steps are as follows: 1. Turn the expression into a dictionary 2. Generate an Equals Expression (you can implement other comparisons)

I have pieced these libraries together from various sources over time and cannot remember where they all come from, but here is the primary methods.

public class ExpressionHelper
{

    public static IDictionary<string, object> GetMethodParams<T>(Expression<Func<T, bool>> fromExpression)
    {
        if (fromExpression == null) return null;


        var body = fromExpression.Body as BinaryExpression;
        if (body == null) return new Dictionary<string, object>();
        var rVal = new Dictionary<string, object>();

        var leftLambda = body.Left as BinaryExpression;
        if (leftLambda != null)
        {
            var params1 = GetExpressionParams(leftLambda);
            foreach (var o in params1) rVal.Add(o.Key, o.Value);
        }
        var rightLambda = body.Right as BinaryExpression;
        if (rightLambda != null)
        {
            var params1 = GetExpressionParams(rightLambda);
            foreach (var o in params1) rVal.Add(o.Key, o.Value);
        }
        else
        {
            var params1 = GetExpressionParams(body);
            foreach (var o in params1) rVal.Add(o.Key, o.Value);
        }

        return rVal;
    }

    /// <summary>
    ///     Get Expression Parameters Recursively
    /// </summary>
    /// <param name="body"></param>
    /// <returns></returns>
    private static IDictionary<string, object> GetExpressionParams(BinaryExpression body)
    {
        if (body == null) return new Dictionary<string, object>();

        var rVal = new Dictionary<string, object>();

        var leftLambda = body.Left as BinaryExpression;
        while (leftLambda != null)
        {
            var params1 = GetExpressionParams(leftLambda);
            foreach (var o in params1) if (!rVal.ContainsKey(o.Key)) rVal.Add(o.Key, o.Value);
            leftLambda = body.Left as BinaryExpression;
        }
        var rightLambda = body.Right as BinaryExpression;
        while (rightLambda != null)
        {
            var params1 = GetExpressionParams(rightLambda);
            foreach (var o in params1) if (!rVal.ContainsKey(o.Key)) rVal.Add(o.Key, o.Value);
            rightLambda = body.Right as BinaryExpression;
        }

        var rightValue = GetValue(body.Right);
        if (rightValue == null)
        {
            var rightSide = body.Right as ConstantExpression;
            if (rightSide != null) rightValue = rightSide.Value;
        }

        var leftSideName = GetMemberName(body.Left);
        if (string.IsNullOrEmpty(leftSideName)) return rVal;
        if (!rVal.ContainsKey(leftSideName)) rVal.Add(leftSideName, rightValue);

        return rVal;
    }

    /// <summary>
    ///     Get an Equals Expression from the name and value
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="propertyName"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public static Expression<Func<T, bool>> GetEqualExpression<T>(string propertyName, object value)
    {
        var p = Expression.Parameter(typeof(T));
        var property = Expression.Property(p, propertyName);

        Expression propertyExpression = Expression.Call(property, property.Type.GetMethod("ToString", Type.EmptyTypes));

        var equalsExpression = Expression.Equal(propertyExpression, Expression.Constant(value?.ToString()));

        var lambda = Expression.Lambda<Func<T, bool>>(equalsExpression, p);
        return lambda;
    }
}

Usage with Items that have the same property StatusId.

public class DataReader
{

    /// <summary>
    ///     Get your list via data manager or something
    /// </summary>
    public List<T> ListItems<T>()
        where T : IStatusIdProperty
    {
        return new List<T>();
    }

    public List<T> GetPubItems<T, TView>()
        where T:IStatusIdProperty
        where TView : IStatusIdProperty
    {
        var expression = ConvertExpression<T, TView>(x => x.StatusId == "Pub");
        return ListItems<T>().Where(expression.Compile()).ToList();
    }


    public Expression<Func<T, bool>> ConvertExpression<T, TView>(Expression<Func<TView, bool>> predicate)
        where T : IStatusIdProperty
        where TView : IStatusIdProperty
    {
        var paramDictionary = predicate.GetParamsDictionary().FirstOrDefault();
        var expression = ExpressionHelper.GetEqualExpression<T>(paramDictionary.Key, paramDictionary.Value);
        return expression;
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM