.net 8实现带层级 DataTable自动Orm

前言:

承接上篇只实现了单实体的,不带层级的Orm,本篇文章小编带您实现带层级的Orm,不需要自定义Attribute,不需要sql强依赖,但是仍然写法有一点小限制(为了解决多条数据过滤问题)。

原理:

1. 将需要映射的对象的属性层级,以及type 存放在字典中;

2. for循环查询到的 DataRow ,然后将每一列数据映射到实体上,涉及到层级本质是递归。

Tip: 此扩展方法涉及到大量的反射的运用,如果不太了解,可以先去微软反射文档学习一下。

代码实现:

1.将对象映射成 Dictionary<string, Type>
 

#region GetEntityTiers

    private static Dictionary<string, Type> GetPropertiesWithHierarchy(this Type type, string parentPrefix = "")
    {
        var properties = new Dictionary<string, Type>();
        var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (var prop in props)
        {
            string propertyName = string.IsNullOrEmpty(parentPrefix) ? prop.Name : $"{parentPrefix}.{prop.Name}";
            properties[propertyName] = prop.PropertyType;

            if (IsGenericList(prop.PropertyType))
            {
                var genericArgument = prop.PropertyType.GetGenericArguments().FirstOrDefault();
                if (genericArgument != null && !genericArgument.IsPrimitive && genericArgument != typeof(string))
                {
                    var nestedProperties = genericArgument.GetPropertiesWithHierarchy(propertyName);
                    foreach (var nestedProperty in nestedProperties)
                    {
                        properties[nestedProperty.Key] = nestedProperty.Value;
                    }
                }
            }
            else if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string))
            {
                var nestedProperties = prop.PropertyType.GetPropertiesWithHierarchy(propertyName);
                foreach (var nestedProperty in nestedProperties)
                {
                    properties[nestedProperty.Key] = nestedProperty.Value;
                }
            }
        }

        return properties;
    }

    private static bool IsGenericList(Type type)
    {
        return type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(List<>));
    }

    #endregion

2. 将DataRow映射到实体层级上:
 

public static T MapToEntity<T>(this DataTable table) where T : new()
    {
        if (table == null || table.Rows.Count == 0)
            return default(T);
        
        //实例化对象
        var entity = new T();
        //拿到属性带层级的名称以及对应的type.
        var properties = typeof(T).GetPropertiesWithHierarchy();

        foreach (DataRow row in table.Rows)
        {
            MapRowToEntity(row, entity, properties);
        }

        return entity;
    }

    private static void MapRowToEntity(DataRow row, object entity, Dictionary<string, Type> properties)
    {
        //分层级,先拿到最外层的
        var groupedProperties = properties.GroupBy(p => p.Key.Split('.')[0]);
        foreach (var group in groupedProperties)
        {
            var immediatePropName = group.Key;
            var nestedProperties = group.ToDictionary(p => string.Join('.', p.Key.Split('.').Skip(1)), p => p.Value);
            var propertyInfo = entity.GetType().GetProperty(immediatePropName);

            if (propertyInfo != null)
            {
                if (IsGenericList(propertyInfo.PropertyType))
                {
                    var list = (IList)propertyInfo.GetValue(entity) ?? (IList)Activator.CreateInstance(propertyInfo.PropertyType)!;
                    var nestedEntityType = propertyInfo.PropertyType.GetGenericArguments().First();

                    var keyProperty = nestedEntityType.GetProperty(
                        nestedProperties.Keys.FirstOrDefault(k => k == nestedEntityType.Name + "Id" || k.EndsWith("Id")));
                    var keyValue = row[keyProperty.Name];
                    //判断是否存在该条记录了,这里就是为什么需要查询主键了,包括efcore的include都是必须得返回主键得。
                    var existingEntity =
                        list!.Cast<object>().FirstOrDefault(e => keyProperty.GetValue(e).Equals(keyValue));

                    if (existingEntity == null)
                    {
                        var nestedEntity = Activator.CreateInstance(nestedEntityType);
                        MapRowToEntity(row, nestedEntity, nestedProperties);
                        list!.Add(nestedEntity);
                    }
                    else
                    {
                        MapRowToEntity(row, existingEntity, nestedProperties);
                    }

                    propertyInfo.SetValue(entity, list);
                }
                else if (propertyInfo.PropertyType.IsClass && propertyInfo.PropertyType != typeof(string))
                {
                    var nestedEntity = propertyInfo.GetValue(entity) ??
                                       Activator.CreateInstance(propertyInfo.PropertyType);

                    MapRowToEntity(row, nestedEntity, nestedProperties);
                    propertyInfo.SetValue(entity, nestedEntity);
                }
                else
                {
                    MapValueToProperty(row, entity, immediatePropName);
                }
            }
        }
    }

    private static void MapValueToProperty(DataRow row, object entity, string propertyName)
    {
        if (row.Table.Columns.Contains(propertyName))
        {
            var value = row[propertyName];
            if (value != DBNull.Value)
            {
                var propertyInfo = entity.GetType().GetProperty(propertyName);
                if (propertyInfo != null)
                {
                    propertyInfo.SetValue(entity, Convert.ChangeType(value, propertyInfo.PropertyType));
                }
            }
        }
    }

 

使用:

 string sql =
                @"select i.IncidentId,i.IncidenttName, tl.TimeLineId,tl.Detail,ns.NextStepId,ns.Step from Incident i
join  TimeLine tl on tl.IncidentId=i.IncidentId
join NextStep ns on ns.IncidentId= i.IncidentId
where i.IncidentId=@IncidentId";
            var dataTable = await _wrapper.ExecuteSqlWithAdo(sql, [new("@IncidentId", "2")]);
            var mapToEntity = dataTable.MapToEntity<IncidentTest>();




public class IncidentTest
{
    public int IncidentId { get; set; }
    public string IncidenttName { get; set; }
    
    public List<TimeLineTest> TimeLineTests { get; set; }
    public List<NextStepTest> NextStepTests { get; set; }
    
}

public class TimeLineTest
{
    public int TimeLineId { get; set; }
    
    public string Detail { get; set; }
}

public class NextStepTest
{
    public int NextStepId { get; set; }
    
    public int Step { get; set; }
}

上面需要注意实体的属性名必须得和sql查询出来的列名一致,大家也可以自定义扩展attribute添加别名。

经测试,上面方法执行效率还行,是一个基础的实现,欢迎对其扩展,有更好的idea可以在评论区留言,互相交流学习。 

以上就是文章的全部。

Austin.

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值