一点教育计划
我真的很喜欢Automapper,尤其是它的QueryableExtensions和ProjectTo <>方法 。 简而言之,此方法允许直接在SQL查询中投影类型。 它实际上允许从数据库接收dto。 即 无需从数据库中获取所有实体,将它们加载到内存中,使用Automapper.Map<>
,这会导致大量消耗和内存流量。
投影类型
要在linq中进行投影,您需要编写如下代码:
from user in dbContext.Users where user.IsActive select new { Name = user.Name, Status = user.IsConnected ? "Connected" : "Disconnected" }
使用QueryableExtensions,可以将以下代码替换为以下代码(当然,前提是已经描述了转换规则User-> UserInfo)
dbContext.Users.Where(x => x.IsActive).ProjectTo<UserInfo>();
枚举及其问题
投影具有一个需要考虑的缺点。 这是对执行的操作的限制。 并非所有内容都可以转换为SQL查询 。 特别地,不可能通过枚举类型获得信息。 例如,有以下枚举
public enum FooEnum { [Display(Name = "")] Any, [Display(Name = "")] Open, [Display(Name = "")] Closed }
有一个实体,其中声明了FooEnum类型的属性。 在dto中,您不需要获取Enum本身,而是获取DisplayAttribute属性的Name属性的值。 通过投影实现这一点是行不通的,因为 要获取属性值,需要反射,而SQL对此“一无所知”。
结果,您要么必须使用通常的Map<>
,将所有实体加载到内存中,要么就启动一个包含Enum值和外键的附加表。
有一个解决方案-表达式
但是“老妇将受到抨击”。 毕竟,Enum的所有值都是预先知道的。 SQL具有可在形成投影时插入的switch
实现。 有待了解如何执行此操作。 HashTag:“我们所有的表达树”。
自动映射器在投影类型时可以将表达式转换为在实体框架之后转换为相应的SQL查询的表达式。
乍一看,在运行时创建表达式树的语法非常不便。 但是在解决了一些小问题之后,一切都变得显而易见。 为了解决Enum的问题,您需要创建一个嵌套的条件表达式树,这些条件表达式根据源数据返回值。 像这样
IF enum=Any THEN RETURN "" ELSE IF enum=Open THEN RETURN "" ELSE enum=Closed THEN RETURN "" ELSE RETURN ""
确定方法签名。
public class FooEntity { public int Id { get; set; } public FooEnum Enum { get; set; } } public class FooDto { public int Id { get; set; } public string Name { get; set; } } // Automapper CreateMap<FooEntity, FooDto>() .ForMember(x => x.Enum, options => options.MapFrom(GetExpression())); private Expression<Func<FooEntity, string>> GetExpression() { }
GetExpression()
方法应生成一个表达式,该表达式接收FooEntity的实例并返回Enum
属性的字符串表示形式。
首先,定义输入参数并获取属性值本身
ParameterExpression value = Expression.Parameter(typeof(FooEntity), "x"); var propertyExpression = Expression.Property(value, "Enum");
您可以使用nameof(FooEntity.Enum)
编译器nameof(FooEntity.Enum)
代替属性名称字符串,甚至可以获取有关System.Reflection.PropertyInfo
属性或System.Reflection.MethodInfo
获取器的数据。 但为了举例,我们足以明确设置属性名称。
要返回特定值,我们使用Expression.Constant
方法。 我们形成默认值
Expression resultExpression = Expression.Constant(string.Empty);
之后,我们先后将结果“包装”为条件。
resultExpression = Expression.Condition( Expression.Equal(propertyExpression, Expression.Constant(FooEnum.Any)), Expression.Constant(EnumHelper.GetShortName(FooEnum.Any)), resultExpression); resultExpression = Expression.Condition( Expression.Equal(propertyExpression, Expression.Constant(FooEnum.Open)), Expression.Constant(EnumHelper.GetShortName(FooEnum.Open)), resultExpression); resultExpression = Expression.Condition( Expression.Equal(propertyExpression, Expression.Constant(FooEnum.Closed)), Expression.Constant(EnumHelper.GetShortName(FooEnum.Closed)), resultExpression);
public static class EnumHelper { public static string GetShortName(this Enum enumeration) { return (enumeration .GetType() .GetMember(enumeration.ToString())? .FirstOrDefault()? .GetCustomAttributes(typeof(DisplayAttribute), false)? .FirstOrDefault() as DisplayAttribute)? .ShortName ?? enumeration.ToString(); } }
剩下的就是草拟结果
return Expression.Lambda<Func<TEntity, string>>(resultExpression, value);
多一点反思
复制所有Enum值非常不便。 修复它
var enumValues = Enum.GetValues(typeof(FooEnum)).Cast<Enum>(); Expression resultExpression = Expression.Constant(string.Empty); foreach (var enumValue in enumValues) { resultExpression = Expression.Condition( Expression.Equal(propertyExpression, Expression.Constant(enumValue)), Expression.Constant(EnumHelper.GetShortName(enumValue)), resultExpression); }
让我们改善获得房地产的价值
上面代码的缺点是所使用实体类型的紧密绑定。 如果需要解决与另一个类有关的类似问题,则必须想出一种方法来获取类型枚举的属性的值。 因此,让表达为我们做吧。 作为方法的参数,我们将传递一个表达式,该表达式接收属性的值以及代码本身-我们仅为此属性可能的结果形成一组结果。 帮助我们的模板
public static Expression<Func<TEntity, string>> CreateEnumShortNameExpression<TEntity, TEnum>(Expression<Func<TEntity, TEnum>> propertyExpression) where TEntity : class where TEnum : struct { var enumValues = Enum.GetValues(typeof(TEnum)).Cast<Enum>(); Expression resultExpression = Expression.Constant(string.Empty); foreach (var enumValue in enumValues) { resultExpression = Expression.Condition( Expression.Equal(propertyExpression.Body, Expression.Constant(enumValue)), Expression.Constant(EnumHelper.GetShortName(enumValue)), resultExpression); } return Expression.Lambda<Func<TEntity, string>>(resultExpression, propertyExpression.Parameters); }
一些澄清。 因为 我们通过另一个表达式获取输入值,那么我们就不需要通过Expression.Parameter
声明参数。 我们从输入表达式的属性中获取此参数,然后使用表达式的主体获取属性的值。
然后,您可以使用这种新方法:
CreateMap<FooEntity, FooDto>() .ForMember(x => x.Enum, options => options.MapFrom(GetExpression<FooEntity, FooEnum>(x => x.Enum)));
表达式树的所有成功开发。
我强烈建议阅读Maxim Arshinov的文章。 特别是关于企业发展中的表达树 。