【问题标题】:assign null value to ifFalse parameter of Expression.Condition throws type mismatch exception将空值分配给 Expression.Condition 的 ifFalse 参数引发类型不匹配异常
【发布时间】:2020-01-27 11:57:02
【问题描述】:

我想创建一个静态方法,它可以与 automapper 一起使用,将字符串值(从 MS SQL 数据库中检索)映射到枚举并获取存储在注释中的枚举的描述(使用实体框架核心) .我让代码使用 Expression.Condition 工作(导致 CASE WHEN THEN ELSE),除非有超过 9 个选项导致 SQL 上出现错误“CASE 语句不能嵌套超过 10 个级别”。

我尝试通过用 Expression.Coalesce 包装 Expression.Condition 来绕过错误。如果最后一个 CASE 语句有一个返回空字符串的 ELSE,我可以生成代码。问题是这当然永远不会触发合并的正确部分。如果我尝试将“NULL”值传递给 Expression.Condition 的最后一个 ifFalse,则会收到类型不匹配错误。

我试过值:null, Expression.Constant(null), Expression.Constant(null,typeof(string)), Expression.Convert(Expression.Constant(null,typeof(string)),typeof(string)) 一切都会抛出类型不匹配错误。

我已经尽我所能在谷歌上搜索,但我没有遇到与我正在尝试的情况相同的情况

将 NULL 传递给 Expression.Condition 的正确方法是什么?以下是我正在使用的所有代码:

public static class EnumExtensions
{
    /// <summary>
    /// Get's the description from an enum which is provided in a string form.
    /// </summary>
    /// <typeparam name="TSource"></typeparam>
    /// <typeparam name="TEnum"></typeparam>
    /// <typeparam name="TMember"></typeparam>
    /// <param name="memberAccess"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    public static Expression<Func<TSource, String>> GetDescriptionFromEnumAsString<TSource, TEnum, TMember>(
   Expression<Func<TSource, TEnum, TMember>> memberAccess)
    {
        var type = typeof(TEnum);
        if (!type.IsEnum)
        {
            throw new InvalidOperationException("TEnum must be an Enum type");
        }

        var enumDescriptions = GetEnumDescriptions<TEnum>();
        var enumNames = Enum.GetNames(type);
        var enumValues = (TEnum[])Enum.GetValues(type);

        var parameter = memberAccess.Parameters[0];

        //Expression.Condition is translated to CASE WHEN THEN ELSE statement in SQL. Below logic will create a nested level for each enum property. The problem is only 10 nested levels are allowed, so an enum with 11 options will throw an SQL error.
        //this is bypassed by adding coalesce. When 9 nested levels are reached, the last one will return NULL and this will be wrapped in the left part of a coalesce. the right part will contain new nested levels and this again can be nested.
        var index = 0;

        var levelsOfCoalesceRequired = Math.Ceiling(enumValues.Length / (double)8);

        //last iteration must assign 'null' so coalesce goes to the right part of the expression
        string defaultValue = Expression.Constant(null,typeof(string));


        var inner = (Expression)Expression.Constant(defaultValue);

        Expression expression = null;

        Queue<Expression> expressionQueue = new Queue<Expression>();

        for (int l = 0; l < levelsOfCoalesceRequired; l++)
        {          
            for (int i = 0; i < 9; i++)
            {
                if (index > (enumValues.Length - 1))
                {                     
                    break;
                }

                Expression

                Expression toTest;
                Expression test;
                Expression ifTrue;
                Expression ifFalse;

                string enumDescription;
                enumDescriptions.TryGetValue(enumNames[index], out enumDescription);

                toTest = Expression.Constant(enumNames[index]);
                test = Expression.Equal(memberAccess.Body, toTest);
                ifTrue = Expression.Constant(enumDescription);
                ifFalse = inner;                                   

                //this is the line that trhows the exception              
                inner = Expression.Condition(test, ifTrue, ifFalse);                   

                index++;
            }

            expressionQueue.Enqueue(inner);
            inner = (Expression)Expression.Constant(defaultValue);
        }          

        foreach (var exp in expressionQueue.ToArray())
        {
            if(expressionQueue.Count == 0)
            {
                break;
            }

            Expression leftExpression = null;
            Expression rightExpression = null;

            //if a coalesce already exists, use it as left argument
            if (expression != null)
            {
                leftExpression = expression;
            }
            else
            {
                expressionQueue.TryDequeue(out leftExpression);
            }

            expressionQueue.TryDequeue(out rightExpression);

            if (rightExpression == null)
                rightExpression = (Expression)Expression.Constant(defaultValue);

            expression = Expression.Coalesce(leftExpression, rightExpression);
        }

        var test1 = Expression.Lambda<Func<TSource, String>>(expression, parameter);

        return test1;
    }

    public static Dictionary<string, string> GetEnumDescriptions<TEnum>()
    {
        var type = typeof(TEnum);
        if (!type.IsEnum)
        {
            throw new InvalidOperationException("TEnum must be an Enum type");
        }

        var output = new Dictionary<string, string>();
        var fieldNames = Enum.GetNames(type);
        var fieldValues = (TEnum[])Enum.GetValues(type);

        for (int i = 0; i < fieldNames.Length; i++)
        {
            string description = string.Empty;
            string fieldName = fieldNames[i];
            FieldInfo fieldInfo = fieldValues[i].GetType().GetField(fieldName);
            EnumDescriptionAttribute[] attributes = (EnumDescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(EnumDescriptionAttribute), false);

            if (attributes != null && attributes.Length > 0)
            {
                description = attributes[0].Description;
            }

            output.Add(fieldName, description);
        }

        return output;
    }
}

用法如下:

自动映射器

CreateMap<SourceEntity, theDTO>()
                .ForMember(m => m.EventTypeDescription,
                f => f.MapFrom(EnumExtensions.GetDescriptionFromEnumAsString<SourceEntity**strong text**, theEnumToGetDescriptionFrom, string>((l, e) => l.EventType)));

存储库

 public Task<List<theDTO>> GetSomeDTO(string sourceId)
        {
          return Context.SourceEntity.Where(x => x.Id == sourceId).OrderBy(x => x.CreatedOn).ProjectTo<theDTO>(_mapper.ConfigurationProvider).ToListAsync();
        }

【问题讨论】:

  • 发布的代码无法编译(例如string defaultValue = Expression.Constant(null,typeof(string)); 无效)。而TMember 泛型类型的用法还不清楚。除此之外,Expression.Constant(null, typeof(string)) 应该按照您的要求去做。
  • @Ivan Stoev 你是正确的 string defaultValue = Expression.Constant(null,typeof(string));应该是 var defaultValue。 TMember已经不需要了,我是从一个代码sn -p开始的,需要调整很多。 Expression.Constant(null, typeof(string)) 不起作用,这就是给我类型不匹配异常的原因
  • 它并没有给我例外,例如。 Expression.Condition(Expression.Constant(false), Expression.Constant("Foo"), Expression.Constant(null, typeof(string))) 编译得很好。可能还有另一个问题,确保ifTrue.Type == typeof(string) 评估为true。或者使用minimal reproducible example 更新帖子,以便我们运行它并查看发生了什么。
  • 我在 2005 年左右编写了合并表达式构造函数,据我回忆,这不应该产生异常。但那是很久以前的事了,我的记忆可能有问题。您忽略了在异常中给出调用堆栈;我们可以看看吗?

标签: c# entity-framework-core automapper linq-expressions


【解决方案1】:

所以我根据@Ivan Stoev 的回复找到了答案。

我没有将Expression.Constant(null,typeof(string)) 作为默认值传递(因此是第一个ifFalse 参数),而是传入Expression.Condition(Expression.Constant(false), defaultValue, defaultValue);

这很好地变成了NULL

""SELECT  COALESCE(CASE
    WHEN [c].[EventType] = N'MSD' THEN N'Mail Released'
    ELSE CASE
        WHEN [c].[EventType] = N'MTR' THEN N'Mail Treated'
        ELSE CASE
            WHEN [c].[EventType] = N'MTK' THEN N'Mail Taken'
            ELSE CASE
                WHEN [c].[EventType] = N'INM' THEN N'Incoming Mail'
                ELSE CASE
                    WHEN [c].[EventType] = N'DRP' THEN N'End of Call'
                    ELSE CASE
                        WHEN [c].[EventType] = N'RAW' THEN N'Remote Answer'
                        ELSE CASE
                            WHEN [c].[EventType] = N'OUC' THEN N'Out Call'
                            ELSE CASE
                                WHEN [c].[EventType] = N'LAW' THEN N'Local Answer'
                                ELSE CASE
                                    WHEN [c].[EventType] = N'INC' THEN N'Incoming Call'
                                    ELSE NULL
                                END
                            END
                        END
                    END
                END
            END
        END
    END
END, CASE
    WHEN [c].[EventType] = N'Unknown' THEN N'Unknown'
    ELSE CASE
        WHEN [c].[EventType] = N'TTR' THEN N'Task Treated'
        ELSE CASE
            WHEN [c].[EventType] = N'MRQ' THEN N'Mail Requeue'
            ELSE NULL
        END
    END
END) AS [EventTypeDescription], [c].[Id], [c].[Info]
FROM [XXX].[XXX] AS [c]
WHERE [c].[XXX] = @__mailId_0

【讨论】:

    猜你喜欢
    • 2017-11-17
    • 2014-05-23
    • 2018-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-24
    • 2021-06-14
    相关资源
    最近更新 更多