如何使用反射将具有集合的复杂类复制到其他类和不同的专有名称

时间:2018-08-02 16:38:45

标签: c# reflection deep-copy

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

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

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

对于一个简单的类,我们可以完成所有工作。我们尚未解决的问题是如何处理收藏夹。

将是我们的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>());
    }
}

这很好用,并且如果条件检查IsGenericType有效,我们只需要弄清楚在返回客户时在此处处理产品集合的代码即可。

我们认为这将是对CopyToModelProperties的递归调用,因为“产品”内部可能也有一个集合,并且可能有一个集合,所以我们不想对代码进行硬编码。

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

2 个答案:

答案 0 :(得分:0)

我已经很长时间没有使用反射了,但是有点像这样的震撼没有出现或者没有反应。

在Internet上进行搜索时,发现了一些我拼凑的代码片段,以解决这个问题。它需要一些调整,但是可以处理我们要完成的任务。同样,不会处理所有可能的情况,但足以让我们开始并继续发展。同样,并非所有带有AutoMapper的PITA配置。我们编写了一个程序,该程序可以获取并获取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;
        }

答案 1 :(得分:0)

代码是如此可在此站点上编辑的PITA,从而创建了包括缓存在内的新答案。原始发布的代码具有60条记录的惊人表现。大约5秒钟。带有缓存的新代码,记录600条.36秒。在此代码中,我们具有审核字段,只有我们的映射器可以设置或更改“创建审核字段”。它可能不适用于您,如果是,请删除。下面需要的所有代码都是一个简单的Sql属性,如下所示。如果您的sql表字段名称与模型属性名称不同,请在模型属性上进行更新。比起Automapper容易得多,因为您不需要其他类型的配置,并且如果您的sql属性与模型属性匹配,则可以使用以下方法创建自己的mapper方法。

[AttributeUsage(AttributeTargets.Property)]
public class SLSqlTableAttribute : Attribute
{
    protected string _fieldName { get; set; }

    public string FieldName
    {
        get
        {
            return _fieldName;
        }
        set
        {
            _fieldName = value;
        }
    }

    public SLSqlTableAttribute(string fieldName)
    {
        FieldName = fieldName;
    }     
}


public class SourceTargetProperties
{
    public PropertyInfo SourceProperty { get; set; }
    public PropertyInfo TargetProperty { get; set; }
}

public static class DataMapper
{
    static Dictionary<string, List<SourceTargetProperties>> _dictTypeProperties = new Dictionary<string, List<SourceTargetProperties>>();

    public static void CopyToSqlProperties(object source, object target, int? userId, DateTimeOffset? modifiedDateTime, bool isInsert)
    {

        Type _typeTarget = target.GetType();
        Type _typeSrc = source.GetType();

        // If we're passed in a collection
        if (_typeSrc.GetInterfaces().Any(a => a.Name == "IEnumerable") && _typeSrc.IsGenericType && _typeTarget.IsGenericType)
        {

            dynamic _sourceList = source;

            foreach (var _source in _sourceList)
            {
                object _tempTarget = Activator.CreateInstance(_typeTarget.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 });
                target.GetType().GetMethod("Add").Invoke(target, new[] { _tempTarget });
            }

        }
        else
        {
            // Collect all the valid properties to map

            if (!_dictTypeProperties.ContainsKey(_typeSrc.BaseType.Name))
            {
                _dictTypeProperties.Add(_typeSrc.BaseType.Name, (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 SourceTargetProperties { SourceProperty = srcProp, TargetProperty = targetProperty }).ToList());
            }
            foreach (var prop in _dictTypeProperties[_typeSrc.BaseType.Name])
            {
                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
                    {
                        string _targetPropertyName = prop.TargetProperty.Name;

                        if (modifiedDateTime.HasValue && (_targetPropertyName == "CreatedDateTime" || _targetPropertyName == "LastModifiedDateTime" || _targetPropertyName == "CreatedBy" ||
                         _targetPropertyName == "LastModifiedBy"))
                        {
                            switch (_targetPropertyName)
                            {
                                case "CreatedDateTime":
                                    if (isInsert)
                                    {
                                        prop.TargetProperty.SetValue(target, modifiedDateTime, null);
                                    }
                                    break;

                                case "CreatedBy":
                                    if (isInsert)
                                    {
                                        prop.TargetProperty.SetValue(target, userId, null);
                                    }

                                    break;

                                case "LastModifiedDateTime":
                                    prop.TargetProperty.SetValue(target, modifiedDateTime, null);

                                    break;

                                case "LastModifiedBy":
                                    prop.TargetProperty.SetValue(target, userId, null);

                                    break;
                            }
                        }
                        else
                        {
                            prop.TargetProperty.SetValue(target, prop.SourceProperty.GetValue(source, null), null);
                        }

                    }
                }

            }
        }
    }

    /// <summary>
    ///  Copy from SQL EF Entities to our Models.
    ///  Models will have the sql table name as a SLSqlAttribute for this to work.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source"></param>
    /// <returns></returns>
    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)
        {
            // figure out a way to take in collections here instead of having to loop through outside of code.
            //  int _sourceLength = (int)source.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, source, null);

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

            //_newtargetList = piSource.GetValue(source, null);

            //var _genericTargetArgs
            dynamic _sourceList = source;

            //dynamic _sourceList = _typeSrc.GetValue(source, null);
            //    dynamic _targetList = piTarget.GetValue(target, null);

            foreach (var _source in _sourceList)
            {
                object _tempTarget = Activator.CreateInstance(_typeTarget.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 });
                target.GetType().GetMethod("Add").Invoke(target, new[] { _tempTarget });
            }

            //   _typeTarget.SetValue(target, _newtargetList, null);

        }
        else
        {
            // Collect all the valid properties to map

            if (!_dictTypeProperties.ContainsKey(_typeSrc.BaseType.Name))
            {
                _dictTypeProperties.Add(_typeSrc.BaseType.Name, (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 SourceTargetProperties { SourceProperty = srcProp, TargetProperty = targetProperty }).ToList());
            }

            foreach (var prop in _dictTypeProperties[_typeSrc.BaseType.Name])
            {
                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
                    {
                        string _targetPropertyName = prop.TargetProperty.Name;
                        prop.TargetProperty.SetValue(target, prop.SourceProperty.GetValue(source, null), null);

                    }
                }

            }
        }

        // return target;
    }


    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;
    }



    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<>);
        //var _genericArgs = props.sourceProperty.PropertyType.GetGenericArguments();
        var _genericTargetArgs = piTarget.PropertyType.GetGenericArguments();
        var _concreteTargetType = _listType.MakeGenericType(_genericTargetArgs);
        var _newtargetList = Activator.CreateInstance(_concreteTargetType);

        //_newtargetList = piSource.GetValue(source, null);

        //var _genericTargetArgs
        dynamic _sourceList = piSource.GetValue(source, null);
        //    dynamic _targetList = piTarget.GetValue(target, 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);
    }
}