c# - 使用AutoMapper

时间:2018-06-05 00:34:47

标签: c# reflection enums automapper projection

我正在尝试从我的Order模型投射到我的OrderDTO模型。 Order有一个枚举。问题是,如果我尝试从Enum获取 Description 属性,则投影不起作用。这是我的代码:

  • OrderStatus.cs

    public enum OrderStatus {
        [Description("Paid")]
        Paid,
    
        [Description("Processing")]
        InProcess,
    
        [Description("Delivered")]
        Sent
    }
    
  • Order.cs

    public class Order {
        public int Id { get; set; }
        public List<OrderLine> OrderLines { get; set; }
        public OrderStatus Status { get; set; }
    }
    
  • OrderDTO.cs

    public class OrderDTO {
        public int Id { get; set; }
        public List<OrderLineDTO> OrderLines { get; set; }
        public string Status { get; set; }  
    }
    

AutoMapper.cs 中使用以下配置:

cfg.CreateMap<Order, OrderDTO>().ForMember(
    dest => dest.Status,
    opt => opt.MapFrom(src => src.Status.ToString())
);

投影有效,但我得到一个 OrderDTO 对象:

 - Id: 1
 - OrderLines: List<OrderLines>
 - Sent //I want "Delivered"!

我不希望Status属性为“已发送”,我希望它与其关联的描述属性相同,在本例中为“已发送”。

我尝试了两种解决方案,但没有一种方法有效:

  1. 使用 ResolveUsing AutoMapper函数,如here所述,但正如其所述here
  2.   

    投影不支持ResolveUsing,请参阅LINQ投影的wiki以获取支持的操作。

    1. 使用静态方法返回String中的 Description 属性。

      cfg.CreateMap<Order, OrderDTO>().ForMember(
          dest => dest.Status,
          opt => opt.MapFrom(src => EnumHelper<OrderStatus>.GetEnumDescription(src.Status.ToString()))
      );
      
    2. 但是这给了我以下错误:

        

      LINQ to Entities无法识别方法'System.String   GetEnumDescription(System.String)'方法,而且这个方法不能   翻译成商店表达。

      那么,我怎样才能做到这一点?

2 个答案:

答案 0 :(得分:1)

你可以添加像这样的扩展方法(借用this post中的逻辑):

public static class ExtensionMethods
{
    static public string GetDescription(this OrderStatus This)
    {
        var type = typeof(OrderStatus);
        var memInfo = type.GetMember(This.ToString());
        var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
        return ((DescriptionAttribute)attributes[0]).Description;
    }
}

然后在地图中访问它:

cfg => 
{
    cfg.CreateMap<Order, OrderDTO>()
    .ForMember
    (
        dest => dest.Status,
        opt => opt.MapFrom
        (
            src => src.Status.GetDescription()
        )
    );
}

这会导致您的要求:

Console.WriteLine(dto.Status);  //"Delivered", not "sent"

See a working example on DotNetFiddle

Edit1 :不要认为可以向LINQ实体添加本地查找功能。它只适用于LINQ对象。你应该追求的解决方案可能是数据库中的域表,它允许你加入它并返回你想要的列,这样你就不必对AutoMapper做任何事情了。

答案 1 :(得分:1)

您可以通过构建一个表达式来实现基于表达式的枚举描述映射,该表达式的计算结果为包含条件语句(例如,switch / if / case取决于提供者的实现方式)的字符串,并以枚举描述作为结果。

因为枚举描述可以提前提取,所以我们可以获取它们并将其用作条件表达式结果的常量。

注意::我已经使用了上述扩展方法 GetDescription(),但是您可以使用所需的任意类型的属性提取。

  public static Expression<Func<TEntity, string>> CreateEnumDescriptionExpression<TEntity, TEnum>(
    Expression<Func<TEntity, TEnum>> propertyExpression)
     where TEntity : class
     where TEnum : struct
  {
     // Get all of the possible enum values for the given enum type
     var enumValues = Enum.GetValues(typeof(TEnum)).Cast<Enum>();

     // Build up a condition expression based on each enum value
     Expression resultExpression = Expression.Constant(string.Empty);
     foreach (var enumValue in enumValues)
     {
        resultExpression = Expression.Condition(
           Expression.Equal(propertyExpression.Body, Expression.Constant(enumValue)),
           // GetDescription() can be replaced with whatever extension 
           // to get you the needed enum attribute.
           Expression.Constant(enumValue.GetDescription()),
           resultExpression);
     }

     return Expression.Lambda<Func<TEntity, string>>(
        resultExpression, propertyExpression.Parameters);
  }

然后您的Automapper映射将变为:

  cfg.CreateMap<Order, OrderDTO>().ForMember(
     dest => dest.Status, opts => opts.MapFrom(
             CreateEnumDescriptionExpression<Order, OrderStatus>(src => src.Status)));

在运行时使用带有SQL Server provider的Entity Framework对其进行评估时,生成的SQL将类似于:

SELECT 
   -- various fields such as Id
   CASE WHEN (2 = [Extent1].[Status]) THEN N'Delivered' 
        WHEN (1 = [Extent1].[Status]) THEN N'Processing' 
        WHEN (0 = [Extent1].[Status]) THEN N'Paid' ELSE N'' END AS [C1]
FROM [Orders] as [Extent1]

这也应适用于其他Entity Framework DB提供程序。