ASP.NET MVC 模型和数据对象映射实践

在使用 MVC 开发项目的过程中遇到了个问题,就是模型和数据实体之间的如何快捷的转换?是不是可以像 Entity Framework 的那样 EntityTypeConfiguration,或者只需要少量的代码就可以把数据实体对象转换成一个 Model 对象(当时还不知道有 AutoMapper 这种东西),所以自己尝试写了一个简单的实现。

1、初步尝试

EntityTypeConverter 类主要用把数据实体转换成 Model 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/// <summary>
/// 提供一组实体对象模型转换的方法。
/// </summary>
/// <typeparam name="TEntityInfo">指定的实体信息对象类型。</typeparam>
public static class EntityTypeConverter<TEntityInfo>
{
    /// <summary>
    /// 将指定的实体对象转换成 <see cref="BaseModel"/> 类型的对象模型。
    /// </summary>
    /// <typeparam name="BaseModel">指定类型的模型对象。</typeparam>
    /// <param name="tEntityInfo">指定的实体信息。</param>
    /// <returns><see cref="BaseModel"/> 类型的对象模型。</returns>
    public static BaseModel ConverterToModel<BaseModel>(TEntityInfo tEntityInfo) where BaseModel : new()
    {
        if (tEntityInfo == null)
            throw new ArgumentNullException("tEntityInfo");
 
        BaseModeltEntity = new BaseModel();
        foreach (var prop in typeof(BaseModel).GetProperties())
        {
 
            if (!prop.CanRead || !prop.CanWrite)
                continue;
 
            if (!CommonHelper.GetCustomTypeConverter(prop.PropertyType).CanConvertFrom(typeof(string)))
                continue;
            string fieldName = String.Empty;<br>         // 验证是否有别名
            var customAttribute = prop.CustomAttributes.Where(att => att.AttributeType == typeof(ModelFieldAliasAttribute)).FirstOrDefault();
            if (customAttribute != null)
            {
                var constructorArgument = customAttribute.ConstructorArguments[0];
                fieldName = constructorArgument.Value.ToString();
            }
            else
            {
                fieldName = prop.Name;
            }
            PropertyInfo property = typeof(TEntityInfo).GetProperty(fieldName);
            if (property != null)
            {
                dynamic value = property.GetValue(tEntityInfo, null);
                prop.SetValue(tEntity, value, null);
            }
        }
        return tEntity;
    }
 
    /// <summary>
    /// 将指定的实体对象转换成 <see cref="BaseModel"/> 类型的对象模型列表。
    /// </summary>
    /// <typeparam name="BaseModel">指定类型的模型对象。</typeparam>
    /// <param name="tEntityList">指定的实体信息列表。</param>
    /// <returns><see cref="BaseModel"/> 类型的对象模型列表。</returns>
    public static List<BaseModel> ConverterToList<BaseModel>(IList<TEntityInfo> tEntityList) where BaseModel: new()
    {
        List<BaseModel> modelList = new List<BaseModel>();
        if (tEntityList != null && tEntityList.Count > 0)
        {
            foreach (var item in tEntityList)
            {
                modelList.Add(ConverterToModel<BaseModel>(item));
            }
        }
        return modelList;
    }
}

辅助类基于 TypeConverter 实现的方法,用于泛型字段的转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class GenericListTypeConverter<T> : TypeConverter
{
    protected readonly TypeConverter typeConverter;
 
    public GenericListTypeConverter()
    {
        typeConverter = TypeDescriptor.GetConverter(typeof(T));
        if (typeConverter == null)
            throw new InvalidOperationException("No type converter exists for type " + typeof(T).FullName);
    }
 
    protected virtual string[] GetStringArray(string input)
    {
        if (!String.IsNullOrEmpty(input))
        {
            string[] result = input.Split(',');
            Array.ForEach(result, s => s.Trim());
            return result;
        }
        else
            return new string[0];
    }
 
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
 
        if (sourceType == typeof(string))
        {
            string[] items = GetStringArray(sourceType.ToString());
            return items.Any();
        }
 
        return base.CanConvertFrom(context, sourceType);
    }
 
    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string)
        {
            string[] items = GetStringArray((string)value);
            var result = new List<T>();
            Array.ForEach(items, s =>
            {
                object item = typeConverter.ConvertFromInvariantString(s);
                if (item != null)
                {
                    result.Add((T)item);
                }
            });
 
            return result;
        }
        return base.ConvertFrom(context, culture, value);
    }
 
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string))
        {
            string result = string.Empty;
            if (((IList<T>)value) != null)
            {
                //we don't use string.Join() because it doesn't support invariant culture
                for (int i = 0; i < ((IList<T>)value).Count; i++)
                {
                    var str1 = Convert.ToString(((IList<T>)value)[i], CultureInfo.InvariantCulture);
                    result += str1;
                    //don't add comma after the last element
                    if (i != ((IList<T>)value).Count - 1)
                        result += ",";
                }
            }
            return result;
        }
 
        return base.ConvertTo(context, culture, value, destinationType);
    }
}

自定义属性 ModelFieldAlias 类用于标识字段别名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/// <summary>
/// 表示一个模型转换对象字段类型的特性。
/// </summary>
[AttributeUsage(ValidTargets, AllowMultiple = false, Inherited = false)]
public class ModelFieldAliasAttribute : Attribute
{
    /// <summary>
    /// 指定此属性可以应用特性的应用程序元素。
    /// </summary>
    internal const AttributeTargets ValidTargets = AttributeTargets.Field | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Parameter;
 
    /// <summary>
    /// 模型的字段别名。
    /// </summary>
    private string _fieldAlias;
 
    /// <summary>
    /// 初始化 <see cref="ModelFieldAliasAttribute"/> 类的新实例。
    /// </summary>
    public ModelFieldAliasAttribute()
    {
 
    }
 
    /// <summary>
    /// 使用指定的筛选类型初始化 <see cref="ModelFieldAliasAttribute"/> 类的新实例。
    /// </summary>
    /// <param name="fieldAlias">指定模型的字段别名。</param>
    public ModelFieldAliasAttribute(string fieldAlias)
    {
        _fieldAlias = fieldAlias;
    }
 
    /// <summary>
    /// 获取或设置模型的字段别名。
    /// </summary>
    public string FieldAlias
    {
        get { return _fieldAlias; }
        set { _fieldAlias = value; }
    }
 
}

使用方法:

1
2
3
4
5
6
7
8
9
// 集合对象
List<ChannelNavigationInfo> channelNavigationList = new List<ChannelNavigationInfo>();
List<ChannelNavigationModel> channelNavigationModelList = new List<ChannelNavigationModel>();
channelNavigationModelList = EntityTypeConverter<ChannelNavigationInfo>.ConverterToList<ChannelNavigationModel>(channelNavigationList);
 
// 单独对象
ChannelNavigationInfo channelNavigation = new ChannelNavigationInfo();
ChannelNavigationModel channelNavigationModel = new ChannelNavigationModel();
channelNavigationModel = EntityTypeConverter<ChannelNavigationInfo>.ConverterToModel<ChannelNavigationModel>(channelNavigation);

由于系统中大多数情况下都是对数据查询的业务,很少有插入和更新,所以没有发现这种实现的弊端,后来开发管理系统的时候,需要大量的数据插入和更新操作,发现这种方法没法做反向映射和转换。

2、AutoMapper 

AutoMapper 是用来解决对象之间映射转换的类库。对于我们开发人员来说,写对象之间互相转换的代码是一件极其浪费生命的事情,AutoMapper 能够帮助我们节省不少时间。

知道这个类库是在研究 nopCommerce 这个项目的时候看到的,使用 AutoMapper 创建映射非常简单。

1
2
Mapper.CreateMap<Order, OrderDto>();//创建映射关系Order –> OrderDto
OrderDto dto = Mapper.Map<OrderDto>(order);//使用Map方法,直接将order对象装换成OrderDto对象

AutoMapper能够自动识别和匹配大部分对象属性:

如果源类和目标类的属性名称相同,直接匹配,目标类型的 CustomerName 可以匹配源类型的 Customer.Name,目标类型的 TotalRecords 可以匹配源类型的 GetTotalRecords() 方法。

AutoMapper 还支持自定义匹配规则:

1
2
3
4
5
6
Mapper.CreateMap<CalendarEvent, CalendarEventForm>()
    // 属性匹配,匹配源类中 WorkEvent.Date 到 EventDate
    .ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.WorkEvent.Date))
    .ForMember(dest => dest.SomeValue, opt => opt.Ignore())//忽略目标类中的属性
    .ForMember(dest => dest.TotalAmount, opt => opt.MapFrom(src => src.TotalAmount ?? 0))//复杂的匹配
    .ForMember(dest => dest.OrderDate, opt => opt.UserValue<DateTime>(DateTime.Now)); //固定值匹配

为了方便使用,可以把扩展方法统一在一个类中实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/// <summary>
/// 提供一组对象映射相关的扩展方法。
/// </summary>
public static class MappingExtensions
{
    #region City...
 
    /// <summary>
    /// 执行从 <see cref="City"/> 对象到 <see cref="CityModel"/> 对象的映射。
    /// </summary>
    /// <param name="entity">指定的 <see cref="City"/> 对象。</param>
    /// <returns><see cref="CityModel"/> 对象。</returns>
    public static CityModel ToModel(this City entity)
    {
        return Mapper.Map<City, CityModel>(entity);
    }
 
    /// <summary>
    /// 执行从 <see cref="CityModel"/> 对象到 <see cref="City"/> 对象的映射。
    /// </summary>
    /// <param name="model">指定的 <see cref="CityModel"/> 对象。</param>
    /// <returns><see cref="City"/> 对象。</returns>
    public static City ToEntity(this CityModel model)
    {
        return Mapper.Map<CityModel, City>(model);
    }
 
    /// <summary>
    /// 执行从 <see cref="CityModel"/> 对象到 <see cref="City"/> 对象的映射。
    /// </summary>
    /// <param name="model">指定的 <see cref="CityModel"/> 模型对象。</param>
    /// <param name="destination">指定的 <see cref="City"/> 实体对象。</param>
    /// <returns><see cref="City"/> 对象。</returns>
    public static City ToEntity(this CityModel model, City destination)
    {
        return Mapper.Map(model, destination);
    }
 
    #endregion
}

最后,在 Global.cs 文件中程序启动前,调用该方法。

1
AutoMapperConfiguration.Configuration();

 

posted @   Charles Zhang  阅读(2796)  评论(1编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示