动态映射 SQL 结果集到业务对象






4.97/5 (33投票s)
使用反射和泛型将 DataTable 动态映射到类型安全的业务对象。
更新
该项目现已在NuGet和GitHub上提供!欢迎在GitHub上贡献任何想法或增强功能,我一定会审查它们。要在NuGet中找到它,只需搜索“AweSamNet”或“DynamicClasses”即可。
引言
评论和投票总是受到赞赏!AweSam.Net是开源软件的支持者,敬请期待更多优秀的.NET库!在Twitter上关注我们:@AweSamNet 或在Facebook上关注我们。
我们公司的一个常见问题是处理非类型安全数据。我们的DBA编写了非常高效的存储过程,这些过程经常与相关表进行连接以减少数据库调用。我们的开发人员必须依次遍历结果集并手动将每个列映射到适当的类和属性。问题是冗余代码的数量和人为错误的发生率。例如,可能有3个不同的存储过程都返回`School`表的行
- 用于获取学校的存储过程
- 用于获取学生及其关联学校信息的存储过程
- 用于获取教师及其关联学校信息的存储过程
对于每个存储过程,编码员可能会编写一个方法将结果集映射到它们对应的类。
编码人员还必须确保每次都正确进行类型转换并处理任何潜在的类型转换异常。这给开发人员带来了比大多数开发人员期望的更多的责任。
`DynamicResultSet` 库允许您使用一个存储过程返回的数据来填充任意数量的 `DynamicClasses`。例如,一个返回学生(与教师关联,教师又与学校关联)的存储过程将这样处理
DynamicResultSet drs = new DynamicResultSet(myDataTable);
List<Student> students = drs.GetList<Student>();
List<Teacher> teachers = drs.GetList<Teacher>();
List<School> schools = drs.GetList<School>();
//or access the School directly from the properties:
School myScool = students[0].Teacher.School;
通过反射,我们遍历类的属性并读取此属性的特性中指示的所有映射。然后我们确保列在表中存在,如果存在,我们进行类型转换并根据结果填充属性。
Using the Code
在使用此代码之前,建议您安装随附的C#代码片段。
要安装的代码片段是
DynamicClassSnippet.snippet
DynamicPropertySnippet.snippet
第一个任务是将 AweSamNet.Data.DynamicClasses.dll 包含在您的项目引用中。完成此操作后,请务必在您的 .cs 文件中添加一个
using AweSamNet.Data.DynamicClasses;
using System.Data;
到您的 .cs 文件。
现在我们准备创建第一个类。所有由 `DynamicResultSet` 生成的类都必须继承 `AweSamNet.Data.DynamicClasses.BusinessLogicBase`。
Overrides
通过继承 `BusinessLogicBase`,您必须重写三个函数。
- `OnLoaded()` - 当行完全解析到新类中后,此函数由 `DynamicResultSet` 执行。所有数据加载完成后需要设置的任何变量或需要执行的方法都应在此方法中完成。
- `IsEmpty()` - 此函数用于告诉 `DynamicResultSet` 忽略记录或在 `GetList()` 中返回它。例如,存储过程可能正在执行 `Outer Join`,在这种情况下,链接表的所有值都非常可能是 `null`。在这种情况下,如果记录中未包含某些ID字段,您将使用 `IsEmpty()` 返回 `true`。
- `ToString()` - 此字段仅用于强制我们的开发人员正确设置 `ToString()`,以便 `BusinessLogicBase` 默认比较器可以正确用于排序。
DynamicProperty 属性
如果您安装了代码片段,最快的方法是键入代码片段快捷方式“`dynamicProperty`”并按Tab键来生成 `DynamicProperty`(一个动态映射到 `DataTable` 列的属性)。一旦 Dynamic 属性生成,您可以通过 Tab 键遍历代码片段值占位符来更改它们。您需要更新4个值
- `Name` - 这是属性名称。例如,`StudentID`
- `Type` - 这是此属性的数据类型。这必须是可空类型,以正确表示可能从数据源返回的值。例如,`int?`
- `ColumnName` - 这是数据表中将映射到此属性的列的名称。
- `SqlDbType` - 这是预期(或尽可能接近)从数据源返回的数据类型。
一旦所有四个变量都已正确设置,按 Enter 键,如果您正在创建一个包含 `StudentID`、`StudentName`、`SchoolID` 和 `TeacherID` 的 `Student` 类,您将看到类似以下内容
#region StudentID
private int? _StudentID;
[DynamicProperty(ColumnName = "st_studentId", DatabaseType = SqlDbType.Int)]
public int? StudentID
{
get
{
return _StudentID;
}
set
{
IsStudentIDSet = true;
}
}
public bool IsStudentIDSet { get; set; }
#endregion
#region StudentName
private String _StudentName;
[DynamicProperty(ColumnName = "st_studentName", DatabaseType = SqlDbType.NVarChar)]
public String StudentName
{
get
{
return _StudentName;
}
set
{
IsStudentNameSet = true;
}
}
public bool IsStudentNameSet { get; set; }
#endregion
#region SchoolID
private int? _SchoolID;
[DynamicProperty(ColumnName = "st_schoolId", DatabaseType = SqlDbType.Int)]
public int? SchoolID
{
get
{
return _SchoolID;
}
set
{
IsSchoolIDSet = true;
}
}
public bool IsSchoolIDSet { get; set; }
#endregion
#region TeacherID
private int? _TeacherID;
[DynamicProperty(ColumnName = "st_teacherId", DatabaseType = SqlDbType.Int)]
public int? TeacherID
{
get
{
return _TeacherID;
}
set
{
IsTeacherIDSet = true;
}
}
public bool IsTeacherIDSet { get; set; }
#endregion
#region TeacherName
private String _TeacherName;
[DynamicProperty(ColumnName = "st_teacherName", DatabaseType = SqlDbType.Int)]
public String TeacherName
{
get
{
return _TeacherName;
}
set
{
IsTeacherNameSet = true;
}
}
public bool IsTeacherNameSet { get; set; }
#endregion
DynamicClass 属性
`DynamicClass` 属性是另一个继承 `BusinessLogicBase` 的类型,作为属性包含在当前类中。因此,例如,使用我们上面的 `Student` 类,一个存储过程可能会返回一个与学生记录在同一行上连接的学校。在这种情况下,我们有足够的数据来填充学生**和**其相关学校。在这种情况下,我们将包含一个 `Student.RelatedSchool` 属性,并添加 `DynamicClass` 属性来告诉 `DynamicResultSet` 将学校数据解析到 `Student.RelatedSchool` 属性中。
当使用 `dynamicClass` 代码片段时,创建类的 `DynamicClass` 属性与创建 `DynamicProperty` 非常相似。键入“`dynamicClass`”并按 Tab 键。一旦 Dynamic Class 生成,您可以通过 Tab 键遍历代码片段值占位符来更改它们。您需要更新3个值
- `Name` - 这是属性名称。例如 `RelatedSchool`
- `Type` - 这是此属性的数据类型。这必须是继承自 `BusinessLogicBase` 的类型。例如 `School`
- `IDFieldName` - 这是将 `DynamicClass` 属性链接到我们正在创建的类的字段。例如 `SchoolID`
一旦所有三个变量都已正确设置,按 Enter 键,如果您正在创建通过上述 `SchoolID` 属性链接的 `Student.RelatedSchool` 属性,您将得到类似以下内容
#region RelatedSchool
private School _RelatedSchool;
[ScriptIgnore]
[DynamicClass(typeof(School), "SchoolID")]
public School RelatedSchool
{
get
{
return _RelatedSchool;
}
private set
{
_RelatedSchool = value; // Set cached variable to the value
if (value == null) // If it's null set the identifier to null
this.SchoolID = null;
else // Otherwise set the identifier to the value's ID
this.SchoolID = value.ID;
}
}
#endregion
动态结果集
现在我们已经定义了类,是时候看看如何将数据连接到它们了。`DynamicResultSet` 构造函数接受 `DataTable` 作为参数。为了演示,我们将手动创建一个 `DataTable`,而不是从存储过程获取它。
DataTable table = new DataTable("Students");
table.Columns.Add("st_studentId");
table.Columns.Add("st_schoolId");
table.Columns.Add("st_teacherId");
table.Columns.Add("sch_schoolId");
table.Columns.Add("sch_schoolName");
DataRow row = table.NewRow();
row["st_studentId"] = 1;
row["st_studentName"] = "Sam Jr.";
row["st_schoolId"] = 23;
row["st_teacherId"] = 54;
row["te_teacherId"] = 54;
row["te_teacherName"] = "Mr. Smith";
row["sch_schoolId"] = 23;
row["sch_schoolName"] = "Pleasantview High School";
DataRow row2 = table.NewRow();
row2["st_studentId"] = 1;
row2["st_schoolId"] = 232;
row2["st_teacherId"] = 86;
row2["sch_schoolId"] = 232;
row2["sch_schoolName"] = "Colonel Saunders Military High School";
table.Rows.Add(row);
table.Rows.Add(row2);
现在我们有了 `DataTable`,我们可以将其导入 `DynamicResultSet` 并生成我们的对象。请注意,上面我们声明了一个 `Student.TeacherName` 属性,但我们没有在 `DataTable` 中包含相应的列。如果存在不期望的列,或者如果列为 `null` 或被排除,`DynamicResultSet` 不会中断。假设我们已经创建了 `School` 类和 `Teacher` 类,让我们来看看 `DynamicResultSet` 的工作方式。
DynamicResultSet drs = new DynamicResultSet(table);
List<Student> students = drs.GetList<Student>();
List<Teacher> teachers = drs.GetList<Teacher>();
List<School> schools = drs.GetList<School>();
//or access the School directly from the properties:
School myScool = students[0].RelatedSchool;
我们所做的就是将表填充到新的 `DynamicResultSet` 中。然后我们使用泛型方法 `GetList
请注意,在最后一行,我们可以通过访问 `RelatedSchool` 属性直接访问链接的学校(我们记得上面,它具有 `DynamicClass` 属性)。这是因为 `DynamicResultSet` 识别了该属性,并尽可能使用 `DataRow` 填充 `RelatedSchool` 属性。
Is[属性名]Set
如果您注意到为 `DynamicProperty` 生成的代码,它会创建一个与主属性关联的第二个布尔属性来存储正确的值。这个属性就是 `Is[PropertyName]Set` 属性。这个属性可以让您知道该列是否从存储过程返回。在我们上面的例子中,我们声明了一个名为 `Student.TeacherName` 的属性,但我们没有将关联的列添加到 `DataTable` 中。由于 `Student.TeacherName` 属性从未设置,`Student.IsTeacherNameSet` 将为 `false`。
更新
应要求,我添加了一些方便的新功能。
-
GetPropertyAttributes
该方法
public static T[] GetPropertyAttributes<T>(this object source, String propertyName) where T : Attribute
已添加为派生自对象的所有类的扩展方法。它返回类型为 `T` 的数组,其中 `T` 是 `System.Attribute`。
-
BusinessLogicBase.GetDynamicPropertyAttributes
该方法
public DynamicProperty[] GetDynamicPropertyAttributes(String propertyName)
已添加到 `BusinessLogicBase` 类中。此方法使用泛型方法 `GetPropertyAttributes` 返回给定属性名称的 `DynamicProperty` 数组。
-
BusinessLogicBase.GetDynamicClassAttributes
该方法
public DynamicProperty[] GetDynamicClassAttributes(String propertyName)
已添加到 `BusinessLogicBase` 类。此方法使用泛型方法 `GetPropertyAttributes` 返回给定属性名称的 `DynamicClass` 数组。
历史
- 2012-02-07 - 首次发布
- 2012-09-28 - 为给定属性添加了返回属性数组的新方法
- 2014-07-07
- 添加了 AweSam.Net 品牌
- 添加了 Nuget 包链接
- 添加了 GitHub 项目链接