【问题标题】:C# Put expression in parenthesesC#将表达式放在括号中
【发布时间】:2021-11-09 08:42:43
【问题描述】:

我正在尝试将属性转换为对象,然后转换为字符串(请不要问为什么。:D),然后调用方法,但原因很简单。

我们看一下代码:

var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
Expression<Func<EmployeeDto, object>> parameter = s => s.BirthDate;
var convert = Expression.Convert(Expression.Convert(parameter.Body, typeof(object)), typeof(string));
var condition = Expression.Call(
    convert,
    containsMethod!,
    Expression.Constant("bla bla", typeof(string))
);

我省略了一些不必要的代码。基本上,我试图将任何类型的属性转换为字符串,然后调用 Contains 方法。但是该表达式会生成以下表达式:

(string)(object) s.BirthDate.Contains("bla bla")

这绝对是不正确的语法。我想把 s.BirthDate 放在括号中。像这样:

((string)(object)s.BirthDate).Contains("bla bla")

【问题讨论】:

  • BirthDate 的类型是什么?另外,为什么你认为你必须这样做?
  • 那是日期时间。我正在尝试将任何属性转换为字符串,这在 EF 核心中是不可能的。所以我找到了解决方法。基本上,您可以将某些内容转换为对象,然后转换为可以正常工作的字符串。
  • 你能添加其余的代码来看看你是如何使用它的吗?它使调试和更新更容易
  • 你怎么知道它会产生“语法错误”的表达式?
  • 您可以将DateTime 转换为对象没问题,但您不能只是神奇地将其转换为字符串。 dotnetfiddle.net/nJ8yle - 你可以在对象上调用ToString(),但那是完全不同的事情(并且不需要转换为object

标签: c# linq


【解决方案1】:

这真的不会做你认为它会做的事情。 Expression.Convert 是类型转换操作,DateTimestring 之间没有类型转换。事实上,很少有类型定义为string

试试这个:

var str = (string)((object)DateTime.Now);

您将立即收到错误消息:“无法将 'System.DateTime' 类型的对象转换为 'System.String' 类型”。对于您自己的类型,您可以定义隐式或显式转换,它会起作用,但不能像 System.DateTime 这样的标准类型。

如果你想要一个字符串值,C# 中的每个对象都有一个ToString 方法。它是否返回有用的东西取决于实现,但它是获取任意对象的字符串表示最可靠的方法。

var toString = typeof(object).GetMethod("ToString");
var contains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var selector = (Expression<Func<EmployeeDto, object>>)(e => e.BirthDate);

var testStringParam = Expression.Parameter(typeof(string), "s");

var condition = Expression.Lambda<Func<EmployeeDto, string, bool>>
(
    Expression.Call
    (
        Expression.Call
        (
            selector.Body,
            toString        
        ),
        contains,
        new[] { testStringParam}
    ),
    selector.Parameters[0], testStringParam
);

这是使用字符串参数来查找要查找的部分,但您已经知道如何使用常量。


顺便说一句,您可以对表达式访问者做一些有趣的事情,让这变得更简单一些。我广泛使用以下内容:

sealed class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression _from;
    private readonly Expression _to;
    
    private ReplaceVisitor(Expression from, Expression to)
    {
        _from = from;
        _to = to;
    }

    public override Expression Visit(Expression e)
    {
        if (ReferenceEquals(e, _from))
            return _to;
        return base.Visit(e);
    }
    
    public static T Execute<T>(T expression, Expression from, Expression to)
        where T : Expression
    {
        var replacer = new ReplaceVisitor(from, to);
        return (T)replacer.Visit(expression);
    }
}

(为简单起见进行了缩写 - Execute 方法中有一些类型检查等,使其不易出错。)

你可以这样做:

Expression<Func<object, string, bool>> template =
    (o, s) => o == null ? null : o.ToString().Contains(s);
Expression<Func<EmployeeDto, object>> selector = e => e.BirthDate;

var pEmployee = selector.Parameters[0];
var pString = template.Parameters[1];

var condition = Expression.Lambda<Func<EmployeeDto, string, object>>
(
    ReplaceVisitor.Execute
    (
        template.Body,
        template.Parameters[0],
        selector.Body
    ),
    pEmployee, pString
);

在这种情况下,结果大部分是相同的(添加了空值检查),但您可以利用编译器检查模板的正确性,而不必依赖于在运行时解决问题。编译器会在您搞砸时通知您。

【讨论】:

    【解决方案2】:

    您的方法看起来正确,但您需要以不同的方式使用表达式参数:

    var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
    // this is parameter we pass to lambda
    // x => ...
    var parameter = Expression.Parameter(typeof(EmployeeDto), "x");                
    var convert = Expression.Convert(Expression.Convert(Expression.Property(parameter, "BirthDate"), typeof(object)), typeof(string));
    var condition = Expression.Call(
        convert,
        containsMethod!,
        Expression.Constant("bla bla", typeof(string))
    );
    // x => ((string) (object) x).Contains("bla bla");
    var lambda = Expression.Lambda<Func<EmployeeDto, bool>>(condition, parameter);
    

    现在您可以将该 lambda 传递给 EF Where 子句。

    对于那些说DateTime 不能转换为string 的人 - 在这种情况下它确实没关系。这个表达式不会被编译和执行——它会被传递给 Entity Framework 来分析并转换为 SQL。重要的是 Entity Framework 是否能够将此类表达式转换为 SQL - 它确实(至少 EF 版本 6)。假设我们有一个名为“Picture”的表,其中包含名为“CreationTime”的日期时间类型的列。然后,这个查询:

    // c.CreationTime is of .NET DateTime type here
    var picts = await ctx.Pictures.Where(c => ((string)(object)c.CreationTime).Contains("12")).ToArrayAsync();
    

    工作得很好,并产生以下形式的 sql 查询:

    select * from Picture where CAST(CreationTime as nvarchar(max)) LIKE '%12%'
    

    并且返回匹配的实体没有问题(在效率方面进行此类查询是否是个好主意是另一回事)。

    【讨论】:

    • 真棒,很好的例子。感谢您的澄清
    猜你喜欢
    • 2012-12-03
    • 1970-01-01
    • 2014-11-20
    • 1970-01-01
    • 1970-01-01
    • 2013-12-10
    • 1970-01-01
    • 1970-01-01
    • 2020-10-14
    相关资源
    最近更新 更多