【发布时间】: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