c# - 如何使用反射将具有集合的复杂类复制到其他类和不同的专有名称
问题描述
更新:我们熟悉 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 的集合中?
解决方案
我已经很长时间没有使用反射了,但是没有出现类似这样的震惊或没有反应。
在互联网上挖掘,找到了一些我拼凑起来的代码片段。它需要一些调整,但它可以处理我们打算做的任务。此外,不会处理最有可能的所有场景,但足以让我们开始和构建。也不是所有的 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;
}
推荐阅读
- r - 使用 gdtools::font_family_exists 检测系统字体的问题
- javascript - 如何在嵌套对象的 Strapi 调用中添加 id 过滤器?
- vue.js - Vue店面:如何覆盖助手内部的功能?
- node.js - 由于类声明,EJS 无法呈现页面
- bash - 如何用';'分割第二列 并附加第一列值
- javascript - 基于另一个数组中的排序号的数组排序
- javascript - 适用于所有数值数组类型的 TypeScript 函数
- php - 将数组索引和值分配给输入 - Jquery
- c# - TCP 和 UDP 连接
- c# - C#,在 DAL 项目的方法中访问 DbContext