присваивает нулевое значение параметру ifFalse Expression. Условие выдает исключение несоответствия типов - PullRequest
0 голосов
/ 27 января 2020

Я хочу создать метод stati c, который можно использовать с automapper для сопоставления строкового значения (полученного из базы данных MS SQL) с перечислением и получения описания этого перечисления, которое хранится в аннотации ( используя ядро ​​Entity Framework). Я получаю код для работы с использованием Expression.Condition (в результате получается CASE WHEN THEN ELSE), за исключением случаев, когда существует более 9 опций, что приводит к ошибке «не может вложить более 10 уровней для оператора CASE» в SQL.

Я пытаюсь обойти ошибку, заключая Expression.Condition в Expression.Coalesce. Я могу получить сгенерированный код, если последний оператор CASE имеет ELSE, который возвращает пустую строку. Проблема в том, что это, конечно, никогда не вызовет правильную часть слияния. Если я пытаюсь передать значение «NULL» последнему ifFalse в Expression.Condition, я получаю ошибку несоответствия типов.

Я пробовал значения: 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;
    }
}

Используется как:

automapper

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();
        }

1 Ответ

0 голосов
/ 28 января 2020

Так что я понял это, основываясь на ответе @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
...