Я хочу создать метод 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();
}