通用关系到复合 C# 对象映射器
Generic Relational to Composite C# Object Mapper
我有以下代码可以将 Reader
映射到简单对象。问题是万一对象是复合的,它就无法映射。我无法通过检查 属性 是否是 class 本身来执行递归
prop.PropertyType.IsClass
因为需要 Type 才能调用 DataReaderMapper()
。关于如何实现或其他方法的任何想法?另外,目前我不希望使用任何 ORM。
public static class MapperHelper
{
/// <summary>
/// extension Method for Reader :Maps reader to type defined
/// </summary>
/// <typeparam name="T">Generic type:Model Class Type</typeparam>
/// <param name="dataReader">this :current Reader</param>
/// <returns>List of Objects</returns>
public static IEnumerable<T> DataReaderMapper<T>(this IDataReader dataReader)where T : class, new()
{
T obj = default(T);
//optimized taken out of both foreach and while loop
PropertyInfo[] PropertyInfo;
var temp = typeof(T);
PropertyInfo = temp.GetProperties();
while (dataReader.Read())
{
obj = new T();
foreach (PropertyInfo prop in PropertyInfo)
{
if (DataConverterHelper.ColumnExists(dataReader,prop.Name) && !dataReader.IsDBNull(prop.Name))
{
prop.SetValue(obj, dataReader[prop.Name], null);
}
}
yield return obj;
}
}
}
不要使 DataReaderMapper
递归。只需使映射部分递归即可:
static void Assign(IDataReader reader, object instance) {
foreach (PropertyInfo prop in PropertyInfo)
{
if (IsValue(prop))
{
prop.SetValue(obj, dataReader[prop.Name], null);
}
else if (IsClass(prop)) {
var subInstance = Activator.CreateInstance(prop.PropertyType);
prop.SetValue(obj, subInstance, null);
Assign(subInstance, dataReader);
}
}
}
就像那样。这将使用默认构造的实例递归初始化所有 class 类型属性,并为它们分配数据 reader 值。
代码明显简化了。我删除了您的一些内容,IsValue
/IsClass
留给您根据自己的喜好实施。此外,您可能希望使用命名方案,以便 a.b.c
作为列名映射到 属性。这可以通过将当前名称前缀作为参数传递给 Assign
.
来实现
另外请注意,DataReaderMapper
不是通用的。我这么说是因为你为此苦苦挣扎。将 typeof(T)
替换为 Type
参数,并将 return 替换为 IEnumerable<object>
。然后在您的方法的结果上调用 Cast<T>()
。所以你看到这个算法原则上可以在没有泛型的情况下工作。
我的偏好是将繁重的工作留给调用代码。这避免了相对较慢的递归,并允许您构建字段名称不完全对齐或没有默认构造函数的对象:
public static class MapperHelper
{
public static IEnumerable<T> DataReaderMapper<T>(this IDataReader dataReader, Func<IDataRecord, T> map)
{
while (dataReader.Read())
{
yield return map(dataReader);
}
}
然后我会将您现有的 属性 代码移动到可以作为默认实现传递到此处的方法:
public static T DefaultMapper<T>(IDataRecord record) where T : class, new()
{
//This is now effectively inside the while loop,
// but .Net caches the expensive recursive calls for you
PropertyInfo[] PropertyInfo;
var temp = typeof(T);
PropertyInfo = temp.GetProperties();
obj = new T();
foreach (PropertyInfo prop in PropertyInfo)
{
if (DataConverterHelper.ColumnExists(dataReader,prop.Name) && !dataReader.IsDBNull(prop.Name))
{
prop.SetValue(obj, dataReader[prop.Name], null);
}
}
return obj;
}
}
您可以像这样使用默认映射器调用它:
foreach (var record in myDataReader.DataReaderMapper<SomeType>(MapperHelper.DefaultMapper) )
{
//...
}
当然,这在复杂的复合类型上仍然会失败,但我不明白你怎么能指望任何通用映射器在那种情况下都能成功……如果你的内部类型本身有属性要填充,那就不好了获取指定匹配名称的方法。这允许您快速编写自己的映射器,如果您需要:
foreach (var record in myDataRecord.DataReaderMapper<SomeType>(r => {
//complex mapping goes here
SomeType result = new SomeType() {
field1 = r["field1"],
field2 = new OtherType() {
subField = r["subField"],
otherField = r["otherField"]
}
}
return result;
})
{
//...
}
当然,您始终可以将该转换的逻辑构建到可以通过名称传递的方法中。
我有以下代码可以将 Reader
映射到简单对象。问题是万一对象是复合的,它就无法映射。我无法通过检查 属性 是否是 class 本身来执行递归
prop.PropertyType.IsClass
因为需要 Type 才能调用 DataReaderMapper()
。关于如何实现或其他方法的任何想法?另外,目前我不希望使用任何 ORM。
public static class MapperHelper
{
/// <summary>
/// extension Method for Reader :Maps reader to type defined
/// </summary>
/// <typeparam name="T">Generic type:Model Class Type</typeparam>
/// <param name="dataReader">this :current Reader</param>
/// <returns>List of Objects</returns>
public static IEnumerable<T> DataReaderMapper<T>(this IDataReader dataReader)where T : class, new()
{
T obj = default(T);
//optimized taken out of both foreach and while loop
PropertyInfo[] PropertyInfo;
var temp = typeof(T);
PropertyInfo = temp.GetProperties();
while (dataReader.Read())
{
obj = new T();
foreach (PropertyInfo prop in PropertyInfo)
{
if (DataConverterHelper.ColumnExists(dataReader,prop.Name) && !dataReader.IsDBNull(prop.Name))
{
prop.SetValue(obj, dataReader[prop.Name], null);
}
}
yield return obj;
}
}
}
不要使 DataReaderMapper
递归。只需使映射部分递归即可:
static void Assign(IDataReader reader, object instance) {
foreach (PropertyInfo prop in PropertyInfo)
{
if (IsValue(prop))
{
prop.SetValue(obj, dataReader[prop.Name], null);
}
else if (IsClass(prop)) {
var subInstance = Activator.CreateInstance(prop.PropertyType);
prop.SetValue(obj, subInstance, null);
Assign(subInstance, dataReader);
}
}
}
就像那样。这将使用默认构造的实例递归初始化所有 class 类型属性,并为它们分配数据 reader 值。
代码明显简化了。我删除了您的一些内容,IsValue
/IsClass
留给您根据自己的喜好实施。此外,您可能希望使用命名方案,以便 a.b.c
作为列名映射到 属性。这可以通过将当前名称前缀作为参数传递给 Assign
.
另外请注意,DataReaderMapper
不是通用的。我这么说是因为你为此苦苦挣扎。将 typeof(T)
替换为 Type
参数,并将 return 替换为 IEnumerable<object>
。然后在您的方法的结果上调用 Cast<T>()
。所以你看到这个算法原则上可以在没有泛型的情况下工作。
我的偏好是将繁重的工作留给调用代码。这避免了相对较慢的递归,并允许您构建字段名称不完全对齐或没有默认构造函数的对象:
public static class MapperHelper
{
public static IEnumerable<T> DataReaderMapper<T>(this IDataReader dataReader, Func<IDataRecord, T> map)
{
while (dataReader.Read())
{
yield return map(dataReader);
}
}
然后我会将您现有的 属性 代码移动到可以作为默认实现传递到此处的方法:
public static T DefaultMapper<T>(IDataRecord record) where T : class, new()
{
//This is now effectively inside the while loop,
// but .Net caches the expensive recursive calls for you
PropertyInfo[] PropertyInfo;
var temp = typeof(T);
PropertyInfo = temp.GetProperties();
obj = new T();
foreach (PropertyInfo prop in PropertyInfo)
{
if (DataConverterHelper.ColumnExists(dataReader,prop.Name) && !dataReader.IsDBNull(prop.Name))
{
prop.SetValue(obj, dataReader[prop.Name], null);
}
}
return obj;
}
}
您可以像这样使用默认映射器调用它:
foreach (var record in myDataReader.DataReaderMapper<SomeType>(MapperHelper.DefaultMapper) )
{
//...
}
当然,这在复杂的复合类型上仍然会失败,但我不明白你怎么能指望任何通用映射器在那种情况下都能成功……如果你的内部类型本身有属性要填充,那就不好了获取指定匹配名称的方法。这允许您快速编写自己的映射器,如果您需要:
foreach (var record in myDataRecord.DataReaderMapper<SomeType>(r => {
//complex mapping goes here
SomeType result = new SomeType() {
field1 = r["field1"],
field2 = new OtherType() {
subField = r["subField"],
otherField = r["otherField"]
}
}
return result;
})
{
//...
}
当然,您始终可以将该转换的逻辑构建到可以通过名称传递的方法中。