【问题标题】:c# - Enum description to string with AutoMapperc# - 使用 AutoMapper 对字符串进行枚举描述
【发布时间】:2018-06-05 00:34:46
【问题描述】:

我正在尝试从我的 Order 模型投影到我的 OrderDTO 模型。 Order 有一个枚举。问题是如果我尝试从枚举中获取 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 属性为“已发送”,我希望它作为其关联的 Description 属性,在本例中为“已交付”。

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

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

ResolveUsing 不支持投影,请参阅关于 LINQ 投影的 wiki 了解支持的操作。

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

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

但这给了我以下错误:

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

那么,我该如何实现呢?

【问题讨论】:

  • 只是跳出框框思考,你不能像描述一样将你的枚举重命名SentDelivered 吗?此外,区分 SentDelivered 会很好,因为它们是两种不同的状态。
  • @jmesolomon 没关系。这只是我认为的一个例子
  • @Sergio 看到这个答案,这将帮助你stackoverflow.com/a/50434045/3901530
  • ProjectTo 没有简单的方法可以做到这一点,但您可以查看NeinLinq

标签: c# reflection enums automapper projection


【解决方案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 to 实体中添加这样的本地查找功能。它只适用于 LINQ to 对象。您应该寻求的解决方案可能是数据库中的一个域表,它允许您加入它并返回您想要的列,这样您就不必对 AutoMapper 做任何事情。

【讨论】:

  • 我也有类似的问题但是需要访问自定义属性的参数
  • 这个例子展示了如何实现和访问一个名为Description的参数。您应该能够使用自己的参数使其适应您自己的属性。
  • 谢谢没想到
  • 感谢您的回答@JohnWu。我已经实现了你的扩展方法,将它应用到我的 AutoMapper.cs,但我一直收到这个错误:LINQ to Entities does not recognize the method 'System.String GetOrderStatusDescription(...Enums.OrderStatus)' method, and this method cannot be translated into a store expression。我已经用我的代码here 上传了一张图片,以便您查看。谢谢
  • 不要以为可以在 LINQ to 实体中添加这样的本地查找功能。它只适用于 LINQ to 对象。您应该寻求的解决方案可能是数据库中的一个域表,它允许您加入它并返回您想要的列,这样您就不必对 AutoMapper 做任何事情。
【解决方案2】:

您可以通过构建一个表达式来实现基于表达式的枚举描述映射,该表达式的计算结果是一个包含条件语句(例如 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 服务器提供程序的实体框架进行评估时,生成的 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]

这也应该适用于其他实体框架数据库提供程序。

【讨论】:

  • 如果您愿意,您可以将其与 AM 更紧密地集成。见this
  • 虽然从Enumstring 的地图可能更合适。
  • @LucianBargaoanu 感谢您对 AutoMapper cfg.Advanced.QueryableBinders 的深入了解 - 我会看看。最好不要为每个属性指定表达式转换器。
  • @LucianBargaoanu 不确定“枚举到字符串可能更适合”的意思?
  • 映射将适用于任何地方,因为每个Enum 都转换为字符串,因此您无需为每个属性指定它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-14
  • 1970-01-01
  • 1970-01-01
  • 2018-10-24
  • 1970-01-01
相关资源
最近更新 更多