【问题标题】:How to Perform ToString On Property of an Entity Framework Model Using Reflection如何使用反射对实体框架模型的属性执行 ToString
【发布时间】:2023-03-18 07:27:01
【问题描述】:

我正在尝试编写一些东西来对实体框架模型的所有属性进行“包含”查询。

例如,我可以毫无问题地执行以下操作:

var students = db.Students.AsQueryable();
var test = students.Where(x => x.FirstName.ToString().ToLower().Contains("1"));

但是使用反射时(如下代码所示),返回如下错误:

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

现在应该是supported

我已阅读此错误,但正如您在上面看到的,即使使用 IQueryable,ToString 也是完全有效的(在我的情况下这是必需的,因为我不想发布过滤数据)。

主要区别是我需要通过反射来调用它。

private static readonly MethodInfo StringContainsMethod =
    typeof(string).GetMethod(@"Contains",
        BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, null);

Type dbType = typeof(Student);

var dbFieldMemberInfo = dbType.GetMember("FirstName",
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).Single();          

// Create an "x" as TDbType
var dbTypeParameter = Expression.Parameter(dbType, @"x");

// Get at x.FirstName
var dbFieldMember = Expression.MakeMemberAccess(dbTypeParameter, dbFieldMemberInfo);         

// Create the criterion as a constant
var criterionConstant = new Expression[] { Expression.Constant(searchString) };          

var toStringMethod = typeof(Convert).GetMethod("ToString", Type.EmptyTypes);
var toStringCall = Expression.Call(dbFieldMember, toStringMethod);
var fancyContain = Expression.Call(toStringCall, StringContainsMethod, criterionConstant);

// Create a lambda like x => x.FirstName.ToString().Contains(criterion)
var lambda = Expression.Lambda(fancyContain, dbTypeParameter) as Expression<Func<Student, bool>>;

这会产生完全相同的 lambda x.FirstName.ToString().Contains(""),但它会返回无法使用 ToString 的错误。从第一个示例和它被添加到 EF 6.1 的事实可以清楚地看出,它可以使用。这是反射的限制吗?

【问题讨论】:

  • 我想说,如果您使用 EF 使用字符串匹配来查询您的数据库元数据,那么您就违背了 EF 的目的,应该寻求不同的解决方案。数据上下文不打算用于查询元数据。如果您对此一无所知,则将其向上或向下推一层,而不是尝试将其转换为查询。要么对你的模型属性做一些字符串魔术,然后编写一个表达式来只在你的查询中包含这些属性,或者向下一层创建一个存储过程,或者让上下文直接执行一个查询。
  • 这是一个通用的解决方案。基本上有人有一个数据网格......他们有一个搜索框......他们可以在搜索框中输入一个数字或一些文本,它会自动对他们标记为可搜索的实体中的所有字段进行服务器端搜索.如果该字段恰好是一个 int 字段,我需要在其上使用 ToString。它允许某人轻松查询所有字段,而无需执行任何设置或手动案例语句。
  • 我也可以轻松检测属性是否为字符串,但我还想在不使用等于的情况下支持整数字段。例如,如果我键入 10,则该数字 10 可以出现在数字字段中的任何位置,因此必须将其解析为字符串。例如,它还可以搜索日期字段等。它并不是一种快速的搜索解决方案,只是一种在数据网格上即插即用的搜索方式。
  • 这可能是Func&lt;T&gt;Expression&lt;Func&lt;T&gt;&gt; 的问题。
  • 我注意到您正在使用 Convert.ToString() 方法创建一个表达式,并让您的变量调用该方法。这与您发布的链接 (e.Id.ToString()) 中的用法有点不同,后者是 ToString() 的单个类型的实现。您可能需要获取参数的类型,然后调用 that ToString() 方法。

标签: c# linq entity-framework reflection


【解决方案1】:

看起来问题源于在表达式中使用 Convert.ToString() 方法,而不是类型的 ToString() 方法的单独实现,例如toStringMethod = type.GetMethod("ToString", Type.EmptyTypes);

【讨论】:

    【解决方案2】:

    看起来您尝试做的是基于所有列进行搜索,最简单和最快的方法是从 EF 执行查询并进行全文搜索。

     var students = db.Database.SqlQuery<Student>("SELECT * FROM Student WHERE CONTAINS((Name, ID), '12')");
    

    (只要确保你清除了没有 sql 注入的字符串)

    我认为,当您尝试构建表达式树并针对 SQL 执行它时,SQL 无法识别它,因为当您使用 Querable 时,您还没有执行它。

    另一种方法是使用SqlFunctions.StringConvert 而不是ToString

    【讨论】:

    • 谢谢,虽然这是其他人将使用的公共库。我认为sql注入的风险太高,不知道如何在没有参数查询的情况下100%消除它。基本上,我允许人们连接 datatables.net 网格并自动搜索他们正在使用的任何实体。
    • 我还阅读了有关 SqlFunctions.StringConvert 的信息,但我不确定这如何解释我的第一个代码示例的工作原理。尝试通过反射调用方法时似乎失败了。在这两种情况下,我都使用 IQueryable。第一个示例生成的 sql 代码执行转换为 nvarchar,然后是 ToLower 等,并且工作正常。
    • 我更新了我的代码示例和标题,使其更加清晰。
    • @KingOfHypocrites 你可以在这个方法中使用参数。
    • 谢谢,如果需要,我可能会走这条路,但我已经有不需要动态 sql 语句并且完全通用的代码。使用反射调用 ToString 是唯一不起作用的方法。
    【解决方案3】:

    对于应该相当简单的事情来说,这看起来很复杂。我建议在你的表中创建一个 textdata 计算列,它只是连接你感兴趣的所有字段。然后你可以像这样向你的模型添加一个属性:

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public string TextData { get; set; }
    

    最后,您可以对这个属性进行 LINQ 查询,以检查它是否为 .Contains(...) 任何您感兴趣的文本。只要您为该列编制索引,搜索应该不会太糟糕。您的主要处罚将在插入中。

    【讨论】:

    • 谢谢,虽然这是其他人使用的通用库,所以我无法控制他们的数据库结构。
    猜你喜欢
    • 2017-08-01
    • 2017-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-22
    • 2011-08-28
    • 1970-01-01
    • 2011-02-14
    相关资源
    最近更新 更多