首页 > 解决方案 > 如何使用反射将具有集合的复杂类复制到其他类和不同的专有名称

问题描述

更新:我们熟悉 Automapper,但我们有将数据表映射到模型的代码,并且很容易添加 SqlTable 属性。因此,我们希望能够通过简单的扩展来做到这一点,而不是通过 Automapper 来做到这一点。

我们的旧数据库有一些不好的命名习惯。在创建我们的 DateModels 时,我们去掉了很多前缀和扩展。

我们正在研究一种从数据库中获取 EF 实体并使用反射将属性值复制到我们的 DataModel 中的方法。

对于一个简单的类,我们已经完成了所有工作。我们还没有弄清楚的问题是如何处理集合。

我们的 sql 表的示例是。

客户表

Cust_Id
Cust_Name
Cust_ProductId -  FK to Product.Id

Product Table
Product_Id
Product_Name

那么我们的数据模型将是

public class CustomerModel : BaseCustomerModel
{
    [SLSqlTable("Cust_Id")]
    public int Id { get; set; }

    [SLSqlTable("Cust_Name")]
    public string Name { get; set; }

    [SLSqlTable("Cust_ProductId")]
    public string ProductId { get; set; }

    [SLSqlTable("Products")]
    public IList<BaseProduct> Products { get; set; }
}

public class BaseProductModel 
{
    [SLSqlTable("Product_Id")]        
    public int? Id { get; set; }

    [SLSqlTable("Product_Name")]
    public string Name { get; set; }
}

我们正在使用SLSqlTableAttribute我们创建的映射名称。

然后从互联网上,我们使用以下代码在属性之间复制数据。它现在适用于除了我们的收藏之外的所有东西,这就是我们想要弄清楚的。我们认为,我们检测到一个集合,然后一些如何递归地回调到CopyToModelProperties.

/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static T CopyToModelProperties<T>(this object source) where T : class, new()
{
    T destination = new T();

    Type typeDest = destination.GetType();
    Type typeSrc = source.GetType();

    // Collect all the valid properties to map
    var results = from srcProp in typeSrc.GetProperties()
                  let targetProperty = typeDest.GetProperty(GetModelPropertyNameFromSqlAttribute(typeDest, srcProp.Name))
                  where srcProp.CanRead
                  && targetProperty != null
                  && (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
                  && (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
                  select new { sourceProperty = srcProp, targetProperty = targetProperty };

    //map the properties
    foreach (var props in results)
    {
        if (props.targetProperty.PropertyType.IsGenericType)
        {       
            var _targetType = props.targetProperty.PropertyType.GetGenericArguments()[0];

        //    props.sourceProperty.PropertyType.MakeGenericType(_targetType).CopyToModelProperties((dynamic)List<>);
        }
        else
        {
            props.targetProperty.SetValue(destination, props.sourceProperty.GetValue(source, null), null);
        }
    }

    return destination;
}

public static string GetModelPropertyNameFromSqlAttribute(Type model, string sqlName)
{
    string _ret = "";

    var _property = model.GetProperties().Where(w => w.GetCustomAttributes<SLSqlTableAttribute>().Where(w1 => w1.FieldName == sqlName).Any()).FirstOrDefault();

    if(_property != null)
    {
        _ret = _property.Name;
    }

    return _ret;
}

这是一个让客户了解他们所有产品的代码示例。

using (var _dc = new BulkTestEntities())
{
    var _custs = _dc.Customers.Include("Products").ToList();

    foreach(var cust in _custs)
    {
        _resp.Add(cust.CopyToModelProperties<CustomerModel>());
    }
}

这工作正常,并且 if 条件检查 IsGenericType 工作正常,我们只需要弄清楚这里有什么代码在让客户回来时处理产品集合。

我们认为这将是对 CopyToModelProperties 的递归回调,因为 Products 内部也可以有一个集合,并且它可能有一个集合,因此我们不想对级别进行硬编码。

所以问题是如何从上面的 if 条件中获取 props.sourceProperty 并将 SQL 实体的集合复制到 DataModel 的集合中?

标签: c#reflectiondeep-copy

解决方案


我已经很长时间没有使用反射了,但是没有出现类似这样的震惊或没有反应。

在互联网上挖掘,找到了一些我拼凑起来的代码片段。它需要一些调整,但它可以处理我们打算做的任务。此外,不会处理最有可能的所有场景,但足以让我们开始和构建。也不是所有的 PITA 配置都带有 AutoMapper 之类的东西。我们编写了一个程序来获取我们的 sql 表,生成类和属性,我们可以轻松地将属性添加到每个属性,现在我们所做的就是调用下面的函数来映射数据。

我们必须使用我们想出的代码。

 using (var _dc = new BulkTestEntities())
                {
                    var _custs = _dc.Customers.Include("Products").Include("CustomerType").ToList();


                   // CopyPropertyData.CopyObjectPropertyData(_custs, _resp);
                    //foreach(var cust in _custs)
                    //{
                    //    _resp.Add(cust.CopyToModelProperties<CustomerModel>());
                    //}

                    foreach (var cust in _custs)
                    {
                        CustomerModel _newCust = new CustomerModel();

                        SQLExtensions.CopyToModelProperties(cust, _newCust);
                      //  CopyData.CopyObjectData(cust, _newCust);

                        _resp.Add(_newCust);
                    }
                }

下面是处理 IEnumerable 和类的静态类。

     public static void CopyToModelProperties(object source, object target)
            {
                Type _typeTarget = target.GetType();
                Type _typeSrc = source.GetType();

if (_typeSrc.GetInterfaces().Any(a => a.Name == "IEnumerable") && _typeSrc.IsGenericType && _typeTarget.IsGenericType)
            {
                // Dynamic allows us to loop through a collection if it's type IEnumerable
                dynamic _sourceList = source;

                foreach (var _source in _sourceList)
                {
                    // Create a temp class of the generic type of the target list
                    object _tempTarget = Activator.CreateInstance(_typeTarget.GetGenericArguments()[0]);

                    //Recursively call to map all child properties
                    CopyToModelProperties(_source, _tempTarget);

                    // Add to target collection passed in
                    target.GetType().GetMethod("Add").Invoke(target, new[] { _tempTarget });
                }

            }   
else
                {

                    var results = from srcProp in _typeSrc.GetProperties()
                                  let targetProperty = _typeTarget.GetProperty(GetModelPropertyNameFromSqlAttribute(_typeTarget, srcProp.Name))
                                  where srcProp.CanRead
                                  && targetProperty != null
                                  && (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
                                  && (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
                                  //  && targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)
                                  select new { sourceProperty = srcProp, targetProperty = targetProperty };

                    foreach (var prop in results)
                    {
                        if (prop.targetProperty.CanWrite && prop.sourceProperty.CanRead)
                        {
                            object targetValue = prop.targetProperty.GetValue(target, null);
                            object sourceValue = prop.sourceProperty.GetValue(source, null);

                            if (sourceValue == null) { continue; }

                            //if (prop.sourceProperty.PropertyType.IsArray
                            //    && prop.targetProperty.PropertyType.IsArray
                            //    && sourceValue != null)

                            if(prop.sourceProperty.PropertyType.IsClass && prop.targetProperty.PropertyType != typeof(string))
                            {
                                var destinationClass = Activator.CreateInstance(prop.targetProperty.PropertyType);
                                object copyValue = prop.sourceProperty.GetValue(source);

                                CopyToModelProperties(copyValue, destinationClass);

                                prop.targetProperty.SetValue(target, destinationClass);
                            }// See if there is a better way to do this.
                            else if (prop.targetProperty.PropertyType.GetInterfaces().Any(a => a.Name == "IEnumerable") && prop.sourceProperty.PropertyType.IsGenericType
                                && prop.targetProperty.PropertyType.IsGenericType
                                && sourceValue != null)
                            {
                                CopyToModelList(source, target, prop.targetProperty, prop.sourceProperty, sourceValue);
                            }
                            else
                            {
                                // CopySingleData(source, target, prop.targetProperty, prop.sourceProperty, targetValue, sourceValue);
                                prop.targetProperty.SetValue(target, prop.sourceProperty.GetValue(source, null), null);
                            }
                        }

                    }            
                }

               // return target;
            }

     private static void CopyToModelList(object source, object target, PropertyInfo piTarget, PropertyInfo piSource, object sourceValue)
            {
                //  int _sourceLength = (int)source.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, source, null);

                // First create a generic collection that matches the piTarget being passed in.

                var _listType = typeof(List<>);
props.sourceProperty.PropertyType.GetGenericArguments();
                var _genericTargetArgs = piTarget.PropertyType.GetGenericArguments();
                var _concreteTargetType = _listType.MakeGenericType(_genericTargetArgs);
                var _newtargetList = Activator.CreateInstance(_concreteTargetType);

                dynamic _sourceList = piSource.GetValue(source, null);

                foreach (var _source in _sourceList)
                {
                    object _tempTarget = Activator.CreateInstance(piTarget.PropertyType.GetGenericArguments()[0]);

                    CopyToModelProperties(_source, _tempTarget);

                    // here we have to make recursive call to copy data and populate the target list.
                    //_targetList.Add(CopyObjectPropertyData(_source, (dynamic)new object()));
                    _newtargetList.GetType().GetMethod("Add").Invoke(_newtargetList, new[] { _tempTarget });
                }

                piTarget.SetValue(target, _newtargetList, null);
            }



     public static string GetModelPropertyNameFromSqlAttribute(Type model, string sqlName)
        {
            string _ret = "";

            var _property = model.GetProperties().Where(w => w.GetCustomAttributes<SLSqlTableAttribute>().Where(w1 => w1.FieldName == sqlName).Any()).FirstOrDefault();

            if(_property != null)
            {
                _ret = _property.Name;
            }

            return _ret;
        }

推荐阅读