EFCore 使用Expression实现动态查询

前言:

EFCore是.net目前使用量最广,社区活跃度最高的一款数据库ORM框架,是我们.net 程序员开发的利器。在我们日常编程中,不可避免就是可选参数查询,efcore的IQueryable 内部传参的Lamba Expression 往往不能够根据条件动态拼接,或手写SQL做法通常是通过StringBuilder判断条件拼接SQL,我们既不想写SQL,又想动态拼接查询条件,那可以使用今天的主角 Expression.

原理: 

1.注解方式在请求参数属性上标注condition,如Equal, NotEqual, Include, Exclude等

2. 反射拿到请求参数,以及数据库实体,找到Mapping的column和request param name ,然后根据condition 组装Expression.

以前写法如下图所示:我们where接受一个expression,常见写法是指定数据库实体的某一列的值等于我请求参数的值,但是会遇到一些问题,太多的if -else 去判断是否为空。

 

 

 

3. Expression官方文档



代码实现:

1.定义Attribute以及Condition Enum.

[AttributeUsage(AttributeTargets.Property)]
public class ExpressionAlias : Attribute
{
    public string EntityName { get; init; }

    public ConditionType Condition { get; init; }
    public bool IsIgnore { get; init; }

    public ExpressionAlias(string entityName, bool isIgnore =false, ConditionType condition = ConditionType.Equal)
    {
        EntityName = entityName;
        IsIgnore = isIgnore;
        Condition = condition;
    }
}

 

public enum ConditionType
{
    /// <summary>
    /// ==
    /// </summary>
    Equal,
    /// !=
    NotEqual,
    /// <summary>
    /// like, in
    /// </summary>
    Include,
    /// <summary>
    /// not like, not in
    /// </summary>
    Exclude
}

 

2. 构建表达式:


public static class BuildParameterHelper
{
    /// <summary>
    /// 构建请求为表达式,默认设置名字相同
    /// 1.对应的property name 设置Expression
    /// 2.返回这个expression 给efcore 查询
    /// </summary>
    /// <param name="t"></param>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="V"></typeparam>
    /// <returns></returns>
    public static Expression<Func<V, bool>> BuildExpression<T, V>(ref T t)
    {
        Expression<Func<V, bool>> expression = ExpressionExtension.True<V>();

        //request实体
        var propertyInfos = typeof(T).GetProperties();
        //数据库实体
        var entityDic = typeof(V).GetProperties().ToDictionary(p => p.Name.ToUpper(), p => p);
        if (!propertyInfos.Any())
            return expression;

        foreach (var propertyInfo in propertyInfos)
        {
            var value = propertyInfo.GetValue(t);
            var eA = (ExpressionAlias)Attribute.GetCustomAttribute(propertyInfo, typeof(ExpressionAlias)) ??
                     new ExpressionAlias(propertyInfo.Name);
            //判断是否为可空类型,
            if (!propertyInfo.PropertyType.IsNullableType())
            {
                //不是可空类型
                //if忽略就自动略过
                if (eA is { IsIgnore: true })
                    continue;

                //设置别名
                if (entityDic.TryGetValue(eA.EntityName.ToUpper(), out var vPropertyInfo))
                {
                    switch (eA.Condition)
                    {
                        case ConditionType.Equal:
                            expression = expression.And(CreateEqualExpression<V>(vPropertyInfo.Name, value));
                            break;
                        case ConditionType.NotEqual:
                            expression = expression.And(CreateNotEqualExpression<V>(vPropertyInfo.Name, value));
                            break;
                        case ConditionType.Include:
                            if (IsArrayOrCollection(propertyInfo))
                            {
                                //是数组才Include,
                                if (propertyInfo.PropertyType == typeof(string))
                                {
                                    //string单独处理
                                    expression = expression.And(CreateLikeExpression<V>(vPropertyInfo.Name, value));
                                    continue;
                                }

                                //处理数组 In
                                var elementType = propertyInfo.PropertyType.GetElementType();
                                var type = GetElementType<V>(elementType, vPropertyInfo.Name, value);
                                if (type is not null)
                                    expression = expression.And(type);
                            }

                            break;
                        case ConditionType.Exclude:
                            if (IsArrayOrCollection(propertyInfo))
                            {
                                //是数组才Include,
                                if (propertyInfo.PropertyType == typeof(string))
                                {
                                    //string单独处理
                                    var likeExpression = CreateLikeExpression<V>(vPropertyInfo.Name, value);
                                    expression =
                                        expression.And(Expression.Lambda<Func<V, bool>>(
                                            Expression.Not(likeExpression.Body), likeExpression.Parameters));
                                    continue;
                                }

                                //处理数组 not In
                                var elementType = propertyInfo.PropertyType.GetElementType();
                                var type = GetElementType<V>(elementType, vPropertyInfo.Name, value);
                                if (type is not null)
                                {
                                    //反转Expression.
                                    var notIn = Expression.Not(type.Body);
                                    expression =
                                        expression.And(Expression.Lambda<Func<V, bool>>(notIn, type.Parameters));
                                }
                            }

                            break;
                        default:
                            continue;
                    }
                }
            }
            else
            {
                //如果是可空类型且只值为空则跳过
                if (value is null)
                    continue;

                //可控类型不为空也可以直接设置lambda表达式
                //if忽略就自动略过
                if (eA is { IsIgnore: true })
                    continue;

                //设置别名
                if (entityDic.TryGetValue(eA.EntityName.ToUpper(), out var vPropertyInfo))
                {
                    switch (eA.Condition)
                    {
                        case ConditionType.Equal:
                            expression = expression.And(CreateEqualExpression<V>(vPropertyInfo.Name, value));
                            break;
                        case ConditionType.NotEqual:
                            expression = expression.And(CreateNotEqualExpression<V>(vPropertyInfo.Name, value));
                            break;
                        case ConditionType.Include:
                            if (IsArrayOrCollection(propertyInfo))
                            {
                                //是数组才Include,
                                if (propertyInfo.PropertyType == typeof(string))
                                {
                                    //string单独处理
                                    expression = expression.And(CreateLikeExpression<V>(vPropertyInfo.Name, value));
                                    continue;
                                }

                                //处理数组 In
                                var elementType =
                                    Nullable.GetUnderlyingType(propertyInfo.PropertyType.GetElementType() ??
                                                               typeof(String));

                                var type = GetElementType<V>(elementType, vPropertyInfo.Name, value);
                                if (type is not null)
                                    expression = expression.And(type);
                            }

                            break;

                        case ConditionType.Exclude:
                            if (IsArrayOrCollection(propertyInfo))
                            {
                                //是数组才Include,
                                if (propertyInfo.PropertyType == typeof(string))
                                {
                                    //string单独处理
                                    var likeExpression = CreateLikeExpression<V>(vPropertyInfo.Name, value);
                                    expression =
                                        expression.And(Expression.Lambda<Func<V, bool>>(
                                            Expression.Not(likeExpression.Body), likeExpression.Parameters));
                                    continue;
                                }

                                //处理数组 not In
                                var elementType =
                                    Nullable.GetUnderlyingType(propertyInfo.PropertyType.GetElementType() ??
                                                               typeof(String));
                                var type = GetElementType<V>(elementType, vPropertyInfo.Name, value);
                                if (type is not null)
                                {
                                    //反转Expression.
                                    var notIn = Expression.Not(type.Body);
                                    expression =
                                        expression.And(Expression.Lambda<Func<V, bool>>(notIn, type.Parameters));
                                }
                            }

                            break;
                        default:
                            continue;
                    }
                }
            }
        }

        //给其设置Is_deleted==false 的默认查询条件
        if (entityDic.TryGetValue("ISDELETED", out var isDel))
            expression = expression.And(CreateEqualExpression<V>(isDel.Name, false));

        return expression;
    }


    private static Expression<Func<V, bool>> GetElementType<V>(Type? elementType, string propertyName, object? value)
    {
        Expression<Func<V, bool>> inExpression = null;
        switch (Type.GetTypeCode(elementType))
        {
            case TypeCode.String:
                // 如果元素类型是字符串,使用 string.Contains 方法
                var changeString = ((IEnumerable)value).Cast<string>();
                inExpression = CreateInExpression<V, string>(propertyName, changeString);
                break;
            case TypeCode.Int32:
                // 如果元素类型是整数,使用 IEnumerable.Contains 方法
                var changeInt = ((IEnumerable)value).Cast<Int32>();
                inExpression = CreateInExpression<V, Int32>(propertyName, changeInt);
                break;
        }

        return inExpression;
    }

    private static Expression<Func<V, bool>> CreateEqualExpression<V>(string columnName, object value)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(V), "e");
        MemberExpression memberExpression = Expression.PropertyOrField(parameter, columnName);
        ConstantExpression constantExpression = Expression.Constant(value);
        BinaryExpression binaryExpression = Expression.Equal(memberExpression, constantExpression);
        return Expression.Lambda<Func<V, bool>>(binaryExpression, parameter);
    }

    private static Expression<Func<V, bool>> CreateNotEqualExpression<V>(string columnName, object value)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(V), "e");
        MemberExpression memberExpression = Expression.PropertyOrField(parameter, columnName);
        ConstantExpression constantExpression = Expression.Constant(value);
        BinaryExpression binaryExpression = Expression.NotEqual(memberExpression, constantExpression);
        return Expression.Lambda<Func<V, bool>>(binaryExpression, parameter);
    }

    private static Expression<Func<V, bool>> CreateLikeExpression<V>(string columnName, object searchText)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(V), "l");
        MemberExpression memberExpression = Expression.PropertyOrField(parameter, columnName);
        ConstantExpression constantExpression = Expression.Constant(searchText);

        MethodCallExpression likeExpression = Expression.Call(
            memberExpression,
            typeof(string).GetMethod("Contains", [typeof(string)]),
            constantExpression
        );

        return Expression.Lambda<Func<V, bool>>(likeExpression, parameter);
    }

    private static Expression<Func<T, bool>> CreateInExpression<T, TValue>(string propertyName,
        IEnumerable<TValue>? values)
    {
        // 获取参数表达式
        var parameter = Expression.Parameter(typeof(T), "I");
        // 获取属性表达式
        var property = Expression.Property(parameter, propertyName);
        // 创建常量表达式
        var constant = Expression.Constant(values, typeof(IEnumerable<TValue>));
        // 创建 Contains 方法调用表达式
        var containsMethod = typeof(Enumerable).GetMethods()
            .First(m => m.Name == "Contains" && m.GetParameters().Length == 2)
            .MakeGenericMethod(typeof(TValue));
        var containsExpression = Expression.Call(containsMethod, constant, property);

        // 创建 lambda 表达式
        return Expression.Lambda<Func<T, bool>>(containsExpression, parameter);
    }


    private static bool IsNullableType(this Type type)
    {
        return !type.IsValueType || (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>));
    }


    private static bool IsArrayOrCollection(PropertyInfo propertyInfo)
    {
        Type propertyType = propertyInfo.PropertyType;

        // 检查是否为数组类型 检查是否实现了 IEnumerable 接口
        if (propertyType.IsArray || typeof(IEnumerable).IsAssignableFrom(propertyType))
            return true;

        // 检查是否为泛型集合类型
        if (propertyType.IsGenericType)
        {
            Type genericTypeDefinition = propertyType.GetGenericTypeDefinition();
            if (genericTypeDefinition == typeof(List<>) ||
                genericTypeDefinition == typeof(HashSet<>) ||
                genericTypeDefinition == typeof(ICollection<>) ||
                genericTypeDefinition == typeof(IList<>) ||
                genericTypeDefinition == typeof(ISet<>))
                return true;
        }

        return false;
    }
}

 3.添加表达式Extension:

public static class ExpressionExtension
{
    /// <summary>
    /// 初始化一个逻辑值为true的表达式
    /// </summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <returns>新的表达式</returns>
    public static Expression<Func<TEntity, bool>> True<TEntity>()
    {
        return t => true;
    }

    /// <summary>
    /// 初始化一个逻辑值为false的表达式
    /// </summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <returns>新的表达式</returns>
    public static Expression<Func<TEntity, bool>> False<TEntity>()
    {
        return t => false;
    }

    //生成逻辑与表达式
    public static Expression<Func<TEntity, bool>> And<TEntity>(this Expression<Func<TEntity, bool>> first,
        Expression<Func<TEntity, bool>> second)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(TEntity), "t");
        ParameterRebinder reBinder = new ParameterRebinder(parameter);
        Expression left = reBinder.RebindParameter(first.Body);
        Expression right = reBinder.RebindParameter(second.Body);
        Expression body = Expression.AndAlso(left, right);
        Expression<Func<TEntity, bool>> expression = Expression.Lambda<Func<TEntity, bool>>(body, parameter);
        return expression;
    }

    /// <summary>
    /// 生成逻辑或表达式
    /// </summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <param name="first">第一个表达式</param>
    /// <param name="second">第二个表达式</param>
    /// <returns>新的表达式</returns>
    public static Expression<Func<TEntity, bool>> Or<TEntity>(Expression<Func<TEntity, bool>> first,
        Expression<Func<TEntity, bool>> second)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(TEntity), "t");
        ParameterRebinder reBinder = new ParameterRebinder(parameter);
        Expression left = reBinder.RebindParameter(first.Body);
        Expression right = reBinder.RebindParameter(second.Body);
        Expression body = Expression.OrElse(left, right);
        Expression<Func<TEntity, bool>> expression = Expression.Lambda<Func<TEntity, bool>>(body, parameter);
        return expression;
    }
}

public class ParameterRebinder: ExpressionVisitor
{
    /// <summary>
    /// 类型参数
    /// </summary>
    private ParameterExpression parameter;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="parameter">类型参数</param>
    public ParameterRebinder(ParameterExpression parameter)
    {
        this.parameter = parameter;
    }

    /// <summary>
    /// 替换类型参数
    /// </summary>
    /// <param name="expression"></param>
    /// <returns></returns>
    public Expression RebindParameter(Expression expression)
    {
        return Visit(expression);
    }

    /// <summary>
    /// 重写VisitParameter
    /// </summary>
    /// <param name="node"></param>
    /// <returns></returns>
    protected override Expression VisitParameter(ParameterExpression node)
    {
        return this.parameter;
    }
}

实例:

1.参数写法 

 

2.Manager写法:

以上就可以拿到结果了,如果涉及到比较负责的条件查询,比如参数某个字段需要查询单表,可以将其标注为Ignore 为true,然后复用这个Expression即可。

以上就是简单的分享. 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值