【问题标题】:How can I make this query work in LINQ to Entities?如何使此查询在 LINQ to Entities 中工作?
【发布时间】:2012-02-02 09:24:40
【问题描述】:

我必须遵循以下代码:

private static bool DoesColValueExist<T>(IQueryable dataToSearchIn, string colName, string colValue)
{
    int noOfClients = 1;
    Type type = typeof(T);
    if (colValue != "" && colName != "")
    {
        var property = type.GetProperty(colName);
        var parameter = Expression.Parameter(type, "p");
        var propertyAccess = Expression.MakeMemberAccess(parameter, property);
        Expression left = Expression.Call(propertyAccess, typeof(object).GetMethod("ToString", System.Type.EmptyTypes));
        left = Expression.Call(left, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
        Expression right = Expression.Constant(colValue.ToLower(), typeof(string));
        MethodInfo method = typeof(string).GetMethod("Equals", new[] { typeof(string) });
        Expression searchExpression = Expression.Call(left, method, right);


        MethodCallExpression whereCallExpression = Expression.Call(
            typeof(Queryable),
            "Where",
            new Type[] { type },
            dataToSearchIn.Expression,
            Expression.Lambda<Func<T, bool>>(searchExpression, new ParameterExpression[] { parameter }));
        var searchedData = dataToSearchIn.Provider.CreateQuery(whereCallExpression);
        noOfClients = searchedData.Cast<T>().Count();

        if (noOfClients == 0)
            return false;
        else
            return true;
    }
    return true;
}

它适用于 LINQ to SQL,但使用 LINQ to Entities,我收到错误:

LINQ to Entities 无法识别方法“System.String ToString()”方法,并且该方法无法转换为存储表达式。

【问题讨论】:

标签: c# linq linq-to-entities expression-trees


【解决方案1】:

Linq to Entities 不支持 .ToString() 方法。我也不确定对非字符串类型使用字符串比较是否是个好主意。但并非全部丢失。我想出了以下解决方案:

public partial class MyEntity
{
    public int ID { get; set; }
    public int Type { get; set; }
    public string X { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<MyEntity> Entities { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyContext>());

        using (var ctx = new MyContext())
        {
            if (!ctx.Entities.Any())
            {
                ctx.Entities.Add(new MyEntity() { ID = 1, Type = 2, X = "ABC" });
                ctx.SaveChanges();
            }

            Console.WriteLine(DoesColValueExist(ctx.Entities, e => e.X, "aBc"));
            Console.WriteLine(DoesColValueExist(ctx.Entities, e => e.X, "aBcD"));
            Console.WriteLine(DoesColValueExist(ctx.Entities, e => e.Type, 2));
            Console.WriteLine(DoesColValueExist(ctx.Entities, e => e.Type, 5));

        }
    }

    private static bool DoesColValueExist<TEntity, TProperty>(IQueryable<TEntity> dataToSearchIn, Expression<Func<TEntity, TProperty>> property, TProperty colValue)
    {

        var memberExpression = property.Body as MemberExpression;
        if (memberExpression == null || !(memberExpression.Member is PropertyInfo))
        {
            throw new ArgumentException("Property expected", "property");
        }

        Expression left = property.Body;
        Expression right = Expression.Constant(colValue, typeof(TProperty));
        if (typeof(TProperty) == typeof(string))
        {
            MethodInfo toLower = typeof(string).GetMethod("ToLower", new Type[0]);
            left = Expression.Call(left, toLower);
            right = Expression.Call(right, toLower);
        }

        Expression searchExpression = Expression.Equal(left, right);
        var lambda = Expression.Lambda<Func<TEntity, bool>>(Expression.Equal(left, right), new ParameterExpression[] { property.Parameters.Single() });

        return dataToSearchIn.Where(lambda).Any();                
    }
}

它的好处是它比基于字符串的解决方案更安全 - 参数的值必须与属性的值相同。该属性又必须是作为第一个参数传递的 IQueryable'1 的通用类型的实体的成员。另一件有用的事情是,当您开始为第二个参数输入 lambda 表达式时,针对此方法进行编码时,intellisense 将向您显示实体的成员。在方法本身中,当我对属性值和请求的值调用 .ToLower() 以使比较不区分大小写时,我添加了字符串类型的异常。对于非字符串类型,值按“原样”进行比较,即不进行任何修改。 上面的示例是完整的 - 您可以将其复制并粘贴到控制台应用程序项目中(尽管您需要引用 EntityFramework.dll)。 希望这可以帮助。

【讨论】:

    【解决方案2】:

    试试这个:

    private static bool DoesColValueExist<T>(IQueryable dataToSearchIn, string colName, string colValue)
    {
        int noOfClients = 1;
        Type type = typeof(T);
        if (colValue != "" && colName != "")
        {
            var property = type.GetProperty(colName);
            var parameter = Expression.Parameter(type, "p");
            var propertyAccess = Expression.MakeMemberAccess(parameter, property);
            Expression left = property.PropertyType == typeof(string) ? propertyAccess : Expression.Call(propertyAccess, typeof(object).GetMethod("ToString", System.Type.EmptyTypes));
            left = Expression.Call(left, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
            Expression right = Expression.Constant(colValue.ToLower(), typeof(string));
            MethodInfo method = typeof(string).GetMethod("Equals", new[] { typeof(string) });
            Expression searchExpression = Expression.Call(left, method, right);
    
    
            MethodCallExpression whereCallExpression = Expression.Call(
                typeof(Queryable),
                "Where",
                new Type[] { type },
                dataToSearchIn.Expression,
                Expression.Lambda<Func<T, bool>>(searchExpression, new ParameterExpression[] { parameter }));
            var searchedData = dataToSearchIn.Provider.CreateQuery(whereCallExpression);
            noOfClients = searchedData.Cast<T>().Count();
    
            if (noOfClients == 0)
                return false;
            else
                return true;
        }
        return true;
    }
    

    基本上,如果属性是字符串,那么它不会调用ToString() 方法。

    希望对你有帮助。

    【讨论】:

    • 另一种选择是始终使用属性的类型。如果它是一个字符串,则将两者都设置为较低(要搜索的值和表达式中的属性)。如果不是,请尝试将字符串解析为属性类型。如果无法解析,则返回 false。如果您可以解析它,请在表达式中使用该值 ;)
    • 这就是我在我提出的解决方案中所做的。在非字符串列上调用 .ToString() 也将不起作用 - 您会看到相同的异常说 .ToString() 不受支持。
    • 但你的解决方案不是动态的,不一样
    • 我不确定你的意思。在我的解决方案中,我使用了类型化信息——因此它不将列名作为字符串,而是作为 IQueryable 实体上的属性。这样您就不能指定不存在的列名。此外,它不将值作为字符串 - 它采用您尝试搜索的属性类型的值。所以它在某种意义上不是动态的,你可以指定任何东西,因为它可以防止指定不正确的值。但它是动态的,因为它允许在实体上指定任何属性并使用该属性类型的任何值。
    • 是的,提议的 API 收到的是名称,而不是 lambda。我假设这是设计使然并且需要这种方式,这就是整个问题的重点
    猜你喜欢
    • 2012-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多