【问题标题】:Best Practices for mapping one object to another将一个对象映射到另一个对象的最佳实践
【发布时间】:2013-04-13 15:24:01
【问题描述】:

我的问题是,我可以以最可维护的方式将一个对象映射到另一个对象的最佳方式是什么。我无法更改我们获得的 Dto 对象的设置方式,使其更加规范化,因此我需要创建一种方法将其映射到我们对其对象的实现。

这是显示我需要发生的事情的示例代码:

class Program
{
    static void Main(string[] args)
    {
        var dto = new Dto();

        dto.Items = new object[] { 1.00m, true, "Three" };
        dto.ItemsNames = new[] { "One", "Two", "Three" };            

        var model = GetModel(dto);

        Console.WriteLine("One: {0}", model.One);
        Console.WriteLine("Two: {0}", model.Two);
        Console.WriteLine("Three: {0}", model.Three);
        Console.ReadLine();
    }

    private static Model GetModel(Dto dto)
    {
        var result = new Model();

        result.One = Convert.ToDecimal(dto.Items[Array.IndexOf(dto.ItemsNames, "One")]);
        result.Two = Convert.ToBoolean(dto.Items[Array.IndexOf(dto.ItemsNames, "Two")]);
        result.Three = dto.Items[Array.IndexOf(dto.ItemsNames, "Three")].ToString();

        return result;
    }
}

class Dto
{
    public object[] Items { get; set; }
    public string[] ItemsNames { get; set; }
}

class Model
{
    public decimal One { get; set; }
    public bool Two { get; set; }
    public string Three { get; set; }
}

我认为如果我有某种映射器类可以接收模型对象 propertyInfo、我想要转换为的类型以及我想要提取的“项目名称”,那将会很棒。有没有人有任何建议让这个更清洁?

谢谢!

【问题讨论】:

  • 不确定映射,但您绝对应该查看泛型并使用泛型集合:csharp-station.com/Tutorial/CSharp/Lesson20
  • 我会建议一个 Model 的构造函数,它需要一个 Dto 并相应地硬编码映射/转换/检查,因为当 dto 发生某些变化时会出现编译错误。反射并因此处理字符串并不能帮助您提高可维护性。

标签: c# design-patterns mapping


【解决方案1】:

我会选择AutoMapper,这是一个开源和免费的映射库,它允许根据约定将一种类型映射到另一种类型(即映射具有相同名称和相同/派生/可转换类型的公共属性,以及许多其他smart ones)。非常好用,会让你实现这样的:

Model model = Mapper.Map<Model>(dto);

不确定您的具体要求,但 AutoMapper 还支持 custom value resolvers,这应该可以帮助您编写特定映射器的单一通用实现。

【讨论】:

  • 我们以前使用过 automapper,但由于性能缓慢,我们感觉放弃了它。
  • 同意。做了同样的事情,最终放弃了自动映射器,然后首先编写了一个自定义的。 Automapper 的性能非常非常慢
  • 我们在性能方面遇到了几乎相同的问题,不会再使用它了。
  • 我们在我的雇主处使用 AutoMapper,是的,它很慢,而且可以通过 PITA 进行配置。
  • 尝试Mapster 以获得性能。 YMMV,但它说它要快得多。
【解决方案2】:

这是一个可能的通用实现,使用了一点反射(伪代码,现在没有 VS):

public class DtoMapper<DtoType>
{
    Dictionary<string,PropertyInfo> properties;

    public DtoMapper()
    {
        // Cache property infos
        var t = typeof(DtoType);
        properties = t.GetProperties().ToDictionary(p => p.Name, p => p);
     }

    public DtoType Map(Dto dto)
    {
        var instance = Activator.CreateInstance(typeOf(DtoType));

        foreach(var p in properties)
        {
            p.SetProperty(
                instance, 
                Convert.Type(
                    p.PropertyType, 
                    dto.Items[Array.IndexOf(dto.ItemsNames, p.Name)]);

            return instance;
        }
    }

用法:

var mapper = new DtoMapper<Model>();
var modelInstance = mapper.Map(dto);

当您创建映射器实例时这会很慢,但稍后会更快。

【讨论】:

  • 不幸的是,这里的需求并不像我希望的那样直接,并且项目名称不需要与模型上的属性名称相关,所以我认为这不会起作用.
【解决方案3】:

Efran Cobisi 关于使用 Auto Mapper 的建议是一个很好的建议。我使用 Auto Mapper 已经有一段时间了,它运行良好,直到我找到了更快的替代方案,Mapster

鉴于列表或 IEnumerable 很大,Mapster 的性能优于 Auto Mapper。我在某处找到了一个基准,显示 Mapster 的速度是原来的 6 倍,但我再也找不到它了。您可以查找它,然后,如果它适合您,请使用 Mapster。

【讨论】:

    【解决方案4】:

    映射两个对象的最快方法inline-mapping,但可能需要一些时间才能使用MappingGenerator

    此外,您还可以查看 Jason Bock 的基准进行比较,这在下面更好:

    Full video on youtube

    【讨论】:

      【解决方案5】:
      /// <summary>
      /// map properties
      /// </summary>
      /// <param name="sourceObj"></param>
      /// <param name="targetObj"></param>
      private void MapProp(object sourceObj, object targetObj)
      {
          Type T1 = sourceObj.GetType();
          Type T2 = targetObj.GetType();
      
          PropertyInfo[] sourceProprties = T1.GetProperties(BindingFlags.Instance | BindingFlags.Public);
          PropertyInfo[] targetProprties = T2.GetProperties(BindingFlags.Instance | BindingFlags.Public);
      
         foreach (var sourceProp in sourceProprties)
         {
             object osourceVal = sourceProp.GetValue(sourceObj, null);
             int entIndex = Array.IndexOf(targetProprties, sourceProp);
             if (entIndex >= 0)
             {
                 var targetProp = targetProprties[entIndex];
                 targetProp.SetValue(targetObj, osourceVal);
             }
         }
      }
      

      【讨论】:

      • -1 :这是一种糟糕的映射器方式,因为它使用属性的顺序来映射它们(而不是名称、类型等)。
      【解决方案6】:

      我创建了一个受 DKM 回答启发的通用方法。

      public static class DbHelper
      {
          public static T FillWith<T>(this T targetObj, T sourceObj)
          {
              Type T1 = sourceObj.GetType();
              Type T2 = targetObj.GetType();
      
              PropertyInfo[] sourceProprties = T1.GetProperties(BindingFlags.Instance | BindingFlags.Public);
              PropertyInfo[] targetProprties = T2.GetProperties(BindingFlags.Instance | BindingFlags.Public);
      
              foreach (var sourceProp in sourceProprties)
              {
                  object osourceVal = sourceProp.GetValue(sourceObj, null);
                  int entIndex = Array.IndexOf(targetProprties, sourceProp);
                  if (entIndex >= 0)
                  {
                      var targetProp = targetProprties[entIndex];
                      targetProp.SetValue(targetObj, osourceVal);
                  }
              }
              return targetObj;
          }
      }
      

      用法:

      var oldUser = new User();
      oldUser.FillWith(updatedUser);
      

      【讨论】:

        【解决方案7】:

        使用反射

            public interface IModelBase
            {
                int Id { get; set; }
            }
            public interface IDtoBase  
            {
                int Id { get; set; }
            }
            public class Client : IModelBase
            {
                public int Id { get; set; }
                public string Name { get; set; }
                public ICollection<SomeType> ListOfSomeType { get; set; }
            }
            public class ClientDto : IDtoBase
            {
                public int Id { get; set; }
                public string Name { get; set; }
            }
            
            public static class Extensions
            {
                public static TDto AsDto<T, TDto>(this T item)
                    where TDto : class, IDtoBase
                    where T : class, IModelBase
                {
                    var list = item.GetType().GetProperties();
                    var inst = Activator.CreateInstance(typeof(TDto));
                    foreach (var i in list)
                    {
                        if (((TDto)inst).GetType().GetProperty(i.Name) == null)
                            continue;
                        var valor = i.GetValue(item, null);
                        ((TDto)inst).GetType().GetProperty(i.Name).SetValue((TDto)inst, valor);
                    }
                    return (TDto)inst;
                }
            }
        
        How to use it:
        
            Client client = new { id = 1, Name = "Jay", ListOfSomeType = new List<SomeType>() };
            ClientDto cdto = client.AsDto<Client, ClientDto>();
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-07-01
          • 2010-12-04
          • 2018-02-10
          • 1970-01-01
          • 2019-04-27
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多