使用反射在MVC3中双向自动映射所有领域实体到视图模型

3
随着我对ASP.Net MVC 3的学习和使用,我开始尝试使用AutoMapper来映射领域实体和视图模型。
在单独为每个实现的视图模型创建映射的过程中,我感到有些厌烦。因此,我编写了一些代码来扫描我的程序集,并使用一些反射来创建所需的每个映射。然而,由于我并不是很熟悉使用AutoMappers的最佳实践,所以我想展示给大家我做的事情,并问问我的方法是否会带来问题。
本质上,我有一个名为AutoMappingConfigurator的类(用于Global.asax.cs),如下所示:
public static class AutoMappingConfigurator
    {
        public static void Configure(Assembly assembly)
        {
            var autoMappingTypePairingList = new List<AutoMappingTypePairing>();

            foreach (Type t in assembly.GetTypes())
            {
                var autoMapAttribute = t
                    .GetCustomAttributes(typeof(AutoMapAttribute), true)
                    .OfType<AutoMapAttribute>()
                    .FirstOrDefault();

                if (autoMapAttribute != null)
                {
                    autoMappingTypePairingList
                .Add(new AutoMappingTypePairing(autoMapAttribute.SourceType, t));
                }
            }

            autoMappingTypePairingList
               .ForEach(mappingPair => mappingPair.CreateBidirectionalMap());
        }
    }

基本上,它会扫描程序集中所有标记了AutoMapAttribute的类型,并为找到的每个类型创建双向映射。
AutoMapAttribute是我创建的一个简单属性(基于我在网上找到的示例),我将其附加到我的ViewModel上,以指示它映射到哪个Domain Entity。
例如。
[AutoMap(typeof(Project))]
public class ProjectDetailsViewModel
{
    public int ProjectId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

就双向映射而言,在我使用MVC3的工作中,我发现在HttpGet时经常需要从实体映射到ViewModel,而在HttpPost时需要从ViewModel映射到实体。

双向映射是通过以下扩展方法实现的:

public static void CreateBidirectionalMap(this AutoMappingTypePairing mappingPair)
{
    Mapper.CreateMap(mappingPair.SourceType, mappingPair.DestinationType)
          .IgnoreProperties(mappingPair.DestinationType);

    Mapper.CreateMap(mappingPair.DestinationType, mappingPair.SourceType)
          .IgnoreProperties(mappingPair.SourceType);
}

关于 IgnoreProperties 扩展方法,我发现每当我有一个视图模型包含一个我想要忽略的属性(例如当我的视图模型有一个下拉列表,而该下拉列表不是基础域实体的一部分时),我似乎必须手动创建忽略项,通过使用 ForMember AutoMapper 方法。因此,我创建了另一个属性来指示哪些属性应该被忽略,这样我的 AutoMappingConfigurator 中的反射代码就可以自动为我执行此操作。
IgnoreProperties 扩展方法是作为扩展方法实现的,如下所示:
public static IMappingExpression IgnoreProperties(this IMappingExpression expression
                                                  , Type mappingType)
{
    var propertiesWithAutoMapIgnoreAttribute =
        mappingType.GetProperties()
            .Where(p => p.GetCustomAttributes(typeof(AutoMapIgnoreAttribute), true)
                         .OfType<AutoMapIgnoreAttribute>()
                         .Count() > 0);
    foreach (var property in propertiesWithAutoMapIgnoreAttribute)
    {
        expression.ForMember(property.Name, opt => opt.Ignore());
    }
    return expression;
}

所有这些都让我可以编写以下的ViewModel并自动映射它:
[AutoMap(typeof(EntityClass))]
private class ViewModelClass
{
    public int EntityClassId { get; set; }

    [AutoMapIgnore]
    public IEnumerable<SelectListItem> DropDownItems { get; set; }
}

private class EntityClass
{
    public int EntityClassId { get; set; }
}

虽然目前这种方法对我来说有效,但由于我在AutoMapper方面的经验较少,我担心它可能会给我带来麻烦。

所以我的问题是:

  • 这种设置AutoMapper来配置领域实体和ViewModel之间映射的方式是否好?
  • 有没有关于AutoMapper的内容我可能会忽略而使这种方法变得不好?
  • 通过反射和属性连接Ignore属性是否是一个好主意?
  • 创建实体和ViewModel之间双向映射是否是个好主意?

你尝试过使用/查看 http://automapper.org/ 吗? - MikeSW
是的,我已经阅读了所有的文档页面,查看了各种博客和浏览了大量的stackoverflow问题,但我还没有看到有人做过我正在做的事情...所以我想知道这是为什么... - mezoid
@mezoid 你如何处理自定义映射? - shuniar
@shuniar 在这个阶段,我的项目没有任何自定义映射,所以我还没有探索会有什么影响。在这个阶段,我只是假定如果最终需要自定义映射,那么我将手动将它们添加到 Configure 方法的底部...如果它们变得太多,就要考虑使用反射来减轻痛苦... - mezoid
3个回答

2

我更喜欢单向视图模型。换句话说,当我向用户展示数据时,我使用一个视图模型,当我处理创建时使用另一个视图模型(更新等情况也是如此)。

当然,这样你会得到更多的对象。优点是避免了不需要的视图模型属性(因此不需要忽略它们)。我认为视图模型应该尽可能简单(仅具有普通的get/set属性和可能需要初始化列表的构造函数)。如果您担心对象的“常见属性”的名称和数据类型,您可以(虽然我认为您不应该)在接口或基类中定义这些属性。

如果您想在两个视图模型中使用某个领域模型属性,则域模型中的AutoMapIgnore属性存在问题。我认为这也适用于您的解决方案。

最后,我不认为使用属性比使用代码行有很大好处,例如:

Mapper.CreateMap<SourceType, DestinationType>();

还有更简单的方式吗?不过,当你从视图模型映射到模型时,拥有一个忽略“未映射属性”的扩展方法可能是个好主意,这样你可以编写

Mapper.CreateMap<SourceType, DestinationType>().IgnoreUnmappedProperties();

我认为这比使用AutoMapIgnore属性更容易。


1

我认为你的方法没有问题,并且回答你的问题:

  • 我认为你这样设置是创建模型/DTO和实体之间繁琐映射的好方法。
  • 我不知道 AutoMapper 有什么不好的地方。
  • 使用属性来连接忽略属性是一个好主意,因为在 MVC 中也是这样使用属性。
  • 双向映射在大多数情况下听起来都是个好主意,但我想知道如何处理自定义映射。

需要考虑一些事情:

  1. 如何处理嵌套映射?
  2. 如何处理自定义映射?
    • 可能使用属性吗?
  3. 双向映射 vs 使用属性映射每个侧面
    • 哪种提供更清晰的说明?
    • 哪一种更好地处理了自定义/嵌套映射?
  4. 性能是否受影响?
    • 很可能不会,但在使用反射时可能成为一个问题。

0
如果您的模型和实体都有基类,您可以执行以下操作:
        var entityAssembly = typeof(BaseEntity).Assembly;
        var modelAssembly = typeof(BaseModel).Assembly;
        var modelNamespace = modelAssembly.GetTypes().Where(a => a.BaseType == typeof(BaseModel)).FirstOrDefault().Namespace;

        foreach (var entity in entityAssembly.GetTypes().Where(a=> a.BaseType == typeof(BaseEntity)))
        {
            var model = modelAssembly.GetType(String.Format("{0}.{1}{2}", modelNamespace, entity.Name, "Model"));
            if (model != null)
            {
                Mapper.CreateMap(entity, model);
                Mapper.CreateMap(model, entity);
            }
        }

这是一个双向约定配置实现,如果相应的模型不存在,则跳过到下一个实体。它非常简单,但是是手动映射的替代方案。

NB. 这假设模型和实体名称遵循某种常规命名方式。例如:

{EntityName}Model 等同于 Branch to BranchModel

希望能有所帮助。请注意,这是一个真正基本的实现。如果没有模型存在,则代码在第3行抛出错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接