代码改变世界

EmitMapper自动映射工具

2014-02-23 23:36  左眼微笑右眼泪  阅读(3453)  评论(6编辑  收藏  举报

         在实体与DTO之间,我们一般都需要进行映射。如果手动的来进行转换,实在是太麻烦。所以就产生了很多映射工具,比如AutoMapper,EmitMapper。而经过一些对比,EmitMapper比较快,接近于手工转换的速度。

          EmitMapper使用非常简单,对于那种属性名一样的,直接使用默认映射即可。如:

ObjectsMapper<From,To> mapper = ObjectMapperManager.DefaultInstance.GetMapper<From,To>();
to = mapper.Map(from);

        它就会自动的把From对象的值赋给To对象;

       对于有一定规则的,可以使用一些扩展的方法,如:

image

      通过使用ConvertUsing可以配置一些组合映射.比如这个字段可以是某两个字段拼接,或者其他一些计算.具体可以参见一些使用例子:

       http://www.cnblogs.com/aaa6818162/archive/2012/06/21/2557879.html

      http://www.cnblogs.com/wuhong/archive/2011/09/21/2184313.html

          但是在使用的过程中,发现了一个问题,就是本身默认是如果属性名相同就直接映射,而使用ConvertUsing是可以自定义映射条件的.但是如果使用了ConvertUsing,那么你必需为里面每个字段都指定映射的条件.而其实真正想用的是,在实体与DTO之间,其实大部分字段的属性名称是相同的,对于这些字段,我们是想直接默认映射,而对于其他一些需要自定义映射的字段,来自定义映射规则.但实际上却达不到这种效果,一般人也不想去把每个字段的映射规则配置一下,如果这样,还不如手动写呢.

          后来在网上找到了一个例子,可以使用FlatteringConfig这个自定义配置,这个类在EmitMapper的源码中是有的,但是不是在当前的解决方案下,而是在一个叫EMConfigurations的项目里.可以这样使用这个自定义配置.

public class User
{
  public Guid Id { get; set; }
  public Company Company { get; set; }
}    
 
public class Company
{
  public Guid Id { get; set; }
}
 
public class UserDTO
{
  public Guid Id { get; set; }
  public Guid CompanyId{ get; set; }
}
ObjectMapperManager.DefaultInstance.GetMapper<User, UserDTO>(
                new FlatteringConfig()
            );
var dto = mapper.Map(new User());

          这样,它就会自动的给DTO中的CompanyId赋值,也会给Id赋值.不过有一个规则就是CompanyId的命名要有一定的规则,一定要是那个实体的名称再加上这个实体里面属性的名称.这样才能进行自动的映射.(不过这时,如果那个实体为null,那也有可能会报错,没有试过,后面有时间试一下).

          在运行的过程中,发现了FlatteringConfig类的一个bug;这个类的原始代码如下:

public class FlatteringConfig : DefaultMapConfig 
    {
        protected Func<string, string, bool> nestedMembersMatcher;
 
        public FlatteringConfig()
        {
            nestedMembersMatcher = (m1, m2) => m1.StartsWith(m2);
        }
 
        public override IMappingOperation[] GetMappingOperations(Type from, Type to)
        {
            var destinationMembers = GetDestinationMemebers(to);
            var sourceMembers = GetSourceMemebers(from);
            var result = new List<IMappingOperation>();
            foreach (var dest in destinationMembers)
            {
                var matchedChain = GetMatchedChain(dest.Name, sourceMembers).ToArray();
                if (matchedChain == null || matchedChain.Length == 0)
                {
                    continue;
                }
                result.Add(
                    new ReadWriteSimple
                    {
                        Source = new MemberDescriptor(matchedChain),
                        Destination = new MemberDescriptor(new[] { dest })
                    }
                );
            }
            return result.ToArray();
        }
 
        public DefaultMapConfig MatchNestedMembers(Func<string, string, bool> nestedMembersMatcher)
        {
            this.nestedMembersMatcher = nestedMembersMatcher;
            return this;
        }
 
        private List<MemberInfo> GetMatchedChain(string destName, List<MemberInfo> sourceMembers)
        {
            var matches = sourceMembers.Where(s => MatchMembers(destName, s.Name) || nestedMembersMatcher(destName, s.Name));
            int len = 0;
            MemberInfo match = null;
            foreach (var m in matches)
            {
                if (m.Name.Length > len)
                {
                    len = m.Name.Length;
                    match = m;
                }
            }
            if (match == null)
            {
                return null;
            }
            var result = new List<MemberInfo> { match };
            if (!MatchMembers(destName, match.Name))
            {
                result.AddRange(
                    GetMatchedChain(destName.Substring(match.Name.Length), GetDestinationMemebers(match))
                );
            }
            return result;
        }
 
        private static List<MemberInfo> GetSourceMemebers(Type t)
        {
            return GetMemebers(t)
                .Where(
                    m => 
                        m.MemberType == MemberTypes.Field || 
                        m.MemberType == MemberTypes.Property ||
                        m.MemberType == MemberTypes.Method
                )
                .ToList();
        }
 
        private static List<MemberInfo> GetDestinationMemebers(MemberInfo mi)
        {
            Type t;
            if (mi.MemberType == MemberTypes.Field)
            {
                t = mi.DeclaringType.GetField(mi.Name).FieldType;
            }
            else
            {
                t = mi.DeclaringType.GetProperty(mi.Name).PropertyType;
            }
            return GetDestinationMemebers(t);
        }
 
        private static List<MemberInfo> GetDestinationMemebers(Type t)
        {
            return GetMemebers(t).Where(m => m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property).ToList();
        }
 
        private static List<MemberInfo> GetMemebers(Type t)
        {
            BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;
            return t.GetMembers(bindingFlags).ToList();
        }
    }

      在GetMappingOperations方法中,修改了以下代码:(原代码中是一个判断在一起,改成分作两个判断)

var matchedChainSingle = GetMatchedChain(dest.Name, sourceMembers);
               if (matchedChainSingle == null)
               {
                   continue;
               }
               var matchedChain = matchedChainSingle.ToArray();
               if (matchedChain.Length == 0)
               {
                   continue;
               }

 

      参考资料:

      http://emitmapper.codeplex.com/

      http://stackoverflow.com/questions/9619265/emit-mapper-flattering-and-property-name-mismatch

      http://emitmapper.codeplex.com/SourceControl/changeset/view/69894#1192663

      http://emitmapper.codeplex.com/SourceControl/changeset/view/42128#691132

     http://stackoverflow.com/questions/12542749/emitmapper-flattering-config-nullreferenceexception?rq=1