在实体框架中实现SqlFunctions.StringConvert的C#Generic .Contains()方法

时间:2016-02-06 16:52:13

标签: c# sql entity-framework lambda generic-method

我有一个在Entity Framework中动态创建查询的通用方法。 我将它用作数据表头的搜索功能。

如果Entity属性类型/ SQL数据类型是字符串,则该函数可以正常工作。这是因为.Contains()扩展。

当数据类型不是字符串时,问题就出现了。 这些数据类型没有.Contains()扩展名。

我希望能够在所有数据类型中使用此方法,并且发现我可以使用SqlFunctions.StringConvert。我也知道它没有整数选项,必须将基于整数的属性转换为double。

我不确定如何一般地实现SqlFunctions.StringConvert,请看下面的方法(你会看到我已经排除了没有.Contains()扩展名的数据类型):

    public static IQueryable<T> Filter<T>(this IQueryable<T> query, List<SearchFilterDto> filters)
        where T : BaseEntity
    {
        if (filters != null && filters.Count > 0 && !filters.Any(f => string.IsNullOrEmpty(f.Filter)))
        {
            Expression filterExpression = null;

            ParameterExpression parameter = Expression.Parameter(query.ElementType, "item");

            filterExpression = filters.Select(f =>
            {
                Expression selector = parameter;
                Expression pred = Expression.Constant(f.Filter);

                foreach (var member in f.Column.Split('.'))
                {
                    PropertyInfo mi = selector.Type.GetProperty(member, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
                    if (mi != null)
                    {
                        selector = Expression.Property(selector, mi);

                        if (selector.Type == typeof(Guid) ||
                        selector.Type == typeof(Guid?) ||
                        selector.Type == typeof(DateTime) ||
                        selector.Type == typeof(DateTime?) ||   
                        selector.Type == typeof(int) ||
                        selector.Type == typeof(int?)

                            )
                        {
                            return null;
                        }
                    }
                    else
                    {
                        return null;
                    }
                }

                Expression containsMethod = Expression.Call(selector, "Contains", null, pred);

                return containsMethod;
            }).Where(r => r != null).Aggregate(Expression.And);
            LambdaExpression where = Expression.Lambda(filterExpression, parameter);
            MethodInfo whereCall = (typeof(Queryable).GetMethods().First(mi => mi.Name == "Where" && mi.GetParameters().Length == 2).MakeGenericMethod(query.ElementType));
            MethodCallExpression call = Expression.Call(whereCall, new Expression[] { query.Expression, where });
            return query.Provider.CreateQuery<T>(call);
        }
        return query;
    }

1 个答案:

答案 0 :(得分:2)

  

如果Entity属性类型/ SQL数据类型是字符串,则该函数可以正常工作。这是因为.Contains()扩展。

我想提一下,Contains在这种情况下不是扩展名,而是常规string.Contains方法。

  

我希望能够在所有数据类型中使用此方法

这不是一个好主意,因为非字符串值可能有不同的字符串表示形式,因此您不太清楚您要搜索的内容。

但是假设你还是想要它。

  

并且发现我可以使用SqlFunctions.StringConvert

有两个缺点 - 首先,SqlFunctions是特定于SqlServer的(例如,不像DbFunctions),其次,StringConvert仅适用于double和{{1} }}。 IMO更好的选择是使用EF支持的decimal方法(至少在最新的EF6中)。

我将根据object.ToString为您提供解决方案。但在此之前,让我在处理表达式时给你一些提示。无论何时您想使用object.ToString()构建表达式并且不知道如何,您可以构建类似的样本类型表达式并在调试器Locals / Watch窗口中进行检查。例如:

System.Linq.Expressions

你可以设置一个断点并开始扩展public class Foo { public int Bar { get; set; } } Expression<Func<Foo, bool>> e = item => SqlFunctions.StringConvert((decimal?)item.Bar).Contains("1"); 成员,然后扩展他们的成员等等。你将看到编译器是如何构建表达式的,那么你所需要的只是找到相应的e方法。

最后,这是解决方案本身。我还提供了一些小技巧,可以避免在可能的情况下直接使用反射和字符串方法名称:

Expression