我一直在玩和学习ASP。. Net MVC 3,我一直在使用AutoMapper来映射我的域的实体和我的视图模型。
我厌倦了为我实现的每个ViewModel单独创建一个地图。因此,我编写了一些代码来扫描程序集,并使用一些反射来创建每个所需的映射。然而,因为我不是很熟悉使用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上,以指示它映射到哪个域实体。
例如。
[AutoMap(typeof(Project))]
public class ProjectDetailsViewModel
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
关于双向映射,到目前为止,在我与MVC3的工作中,我发现我似乎经常需要从实体映射到HttpGet的ViewModel,从ViewModel映射到HttpPost的实体。
双向映射作为扩展方法实现,如下所示:
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来配置我的映射的好方法吗我的领域实体和视图模型之间?
- 是否有一些关于AutoMapper的东西,我可能会错过,将使这是一个糟糕的方法?
- 正在连接属性忽略反射和属性是好的主意吗?
- 是创建我的实体和ViewModel之间的双向映射一个好主意吗?
我更喜欢单向视图模型。换句话说,当我向用户呈现数据时,我使用一个视图模型,当我处理创建时,我使用另一个视图模型(另一个用于更新等等)。
为真,这样你会得到更多的对象。这样做的好处是可以避免不需要的视图模型属性(因此不需要忽略它们)。我认为视图模型应该总是尽可能简单(普通的get/set属性,如果你有一个需要初始化的列表,也许是一个构造函数)。如果您担心对象的"公共属性"的名称和数据类型,您可以(尽管我认为您不应该)在接口或基类中定义这些。
如果您想在两个视图模型中使用特定的域模型属性,则域模型中的AutoMapIgnore属性是有问题的。我认为这也适用于你的解决方案。最后,我看不出使用属性而不是像
这样的代码行有什么好处。Mapper.CreateMap<SourceType, DestinationType>();
还能更简单吗?当您从视图模型映射到模型时,使用一个扩展方法忽略"未映射属性"可能是一个好主意,允许您编写
Mapper.CreateMap<SourceType, DestinationType>().IgnoreUnmappedProperties();
在我看来这比使用AutoMapIgnore属性更容易。
我看不出你的方法有什么不妥,我来回答你的问题:
- 在我看来,我相信你设置这个的方式是一种很好的方式,可以在你的模型/dto和实体之间创建乏味的映射。
- 我不知道关于AutoMapper的任何事情会使这成为一个糟糕的方法。
- 使用属性来连接属性忽略是一个好主意,属性只是在MVC中使用。 双向映射在大多数情况下听起来是个好主意,但我很好奇它如何与自定义映射一起工作。
一些需要考虑的事情:
- 它如何处理嵌套映射?
- 它如何处理自定义映射?
- 可能属性呢?
- 双向映射vs用属性映射每一侧
- 哪个更清晰?
- 哪一个处理自定义/嵌套映射更好?
- 性能是否受到影响?
- 很可能不会,但在使用反射时可能需要注意。
如果你的模型和实体都有基类,你可以这样做:
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 eq. Branch to BranchModel
希望有帮助。请注意,这是一个真正的基本实现。如果不存在模型,则代码在第3行抛出错误。