具有不同查询 select 列表的广义 DTO 填充方法
Generalized DTO population method with different query select lists
由于我不太明白的原因,我选择不使用 ORM 框架,而是使用通用的 ADO.NET 数据访问层。我最初创建了一个数据库 class,我的所有控制器都可以从中访问。除了我以外,任何人都可以预料到,这个访问对象已经变成了一个怪物。
为了重构我的数据层,我创建了一个 'database adapter' class 作为 DI 注入服务,并创建了一个 'service layer' 来利用它。所以每个控制器现在都有一个 'domain service' 将使用数据库适配器查询数据库和 return 一个通用数据 table。该服务随后会将查询结果和 return 域对象填充回控制器,在那里它可以 assemble 视图模型。
我 运行 遇到了一个问题,我似乎无法从数据库访问层抽象出用于映射数据集 return 的代码,因为每个查询可能 select 不同的字段.例如一个简单的引用数据服务:
public class ReferenceDataService : IReferenceDataService
{
private IDatabaseAdapter _dbAdapter;
public ReferenceDataService(IDatabaseAdapter dbAdapter)
{
_dbAdapter = dbAdapter;
}
public IEnumerable<ReferenceData> GetReferenceData(string table)
{
List<ReferenceData> rdList = new List<ReferenceData>();
StringBuilder sb = new StringBuilder();
sb.Append("SELECT [CODE], [LABEL] FROM [dbo].");
sb.Append(table);
sb.Append(" WHERE END_DATETIME > GETDATE()");
DataSet ds = _dbAdapter.ExecuteDataSet(sb.ToString(), null);
foreach (DataRow row in ds.Tables[0].Rows)
{
rdList.Add(PopulateRecord(row));
}
return rdList;
}
private ReferenceData PopulateRecord(DataRow row)
{
return new ReferenceData
{
ReferenceId = (int)row["REFERENCE_ID"],
Code = (string)row["CODE"],
Label = (string)row["LABEL"],
Description = (string)row["DESCRIPTION"],
BeginDatetime = (DateTime)row["BEGIN_DATETIME"],
EndDatetime = (DateTime)row["END_DATETIME"],
UpdatedBy = (string)row["UPDATED_BY"],
UpdatedOn = (DateTime)row["UPDATED_ON"],
CreatedBy = (string)row["CREATED_BY"],
CreatedOn = (DateTime)row["CREATED_ON"]
};
}
}
在这个例子中,我从 populate 方法中抛出了一个异常,因为如您所见,我只是 select为此特定方法编写代码和标签。我想避免为每个方法自定义映射,但我也不想不必要地 return 来自每个 table 行的所有数据到控制器。我想保留填充方法的通用性,以便针对该 table 的任何查询都将被适当地映射。
我意识到我基本上几乎是在推出自己的 ORM,但我想在没有它的情况下使用服务模式,因为在这一点上我投入太多了。
经过一番挖掘,似乎有一个非常明显和直接的解决方案是我一直缺少的。 DataRow 实例对象能够检查其父 table 列是否存在。通过将来自 table 行的每个赋值包装在这些检查之一中,填充方法将不关心实际 select 编辑到 DataTable 中的内容,并且能够填充一个对象,而不管有多少从查询返回的数据。
因此,在我的示例中,如果我想为 ReferenceData 保留通用填充方法但使用仅重新调整 CODE 和 LABEL 列的查询,则以下更改将使返回的业务对象的填充不可知且无错误:
private ReferenceData PopulateRecord(DataRow row)
{
return new ReferenceData
{
ReferenceId = row.Table.Columns.Contains("REFERENCE_ID") ? (int)row["REFERENCE_ID"] : default(int),
Code = row.Table.Columns.Contains("CODE") ? (string)row["CODE"] : default(string),
Label = row.Table.Columns.Contains("LABEL") ? (string)row["LABEL"] : default(string),
Description = row.Table.Columns.Contains("DESCRIPTION") ? (string)row["DESCRIPTION"] : default(string),
BeginDatetime = row.Table.Columns.Contains("BEGIN_DATETIME") ? (DateTime)row["BEGIN_DATETIME"] : default(DateTime),
EndDatetime = row.Table.Columns.Contains("END_DATETIME") ? (DateTime)row["END_DATETIME"] : default(DateTime),
UpdatedBy = row.Table.Columns.Contains("UPDATED_BY") ? (string)row["UPDATED_BY"] : default(string),
UpdatedOn = row.Table.Columns.Contains("UPDATED_ON") ? (DateTime)row["UPDATED_ON"] : default(DateTime),
CreatedBy = row.Table.Columns.Contains("CREATED_BY") ? (string)row["CREATED_BY"] : default(string),
CreatedOn = row.Table.Columns.Contains("CREATED_ON") ? (DateTime)row["CREATED_ON"] : default(DateTime)
};
}
这将允许我在只返回 CODE 和 LABEL 的 select 语句上使用 PopulateRecord
(例如,如果我为下拉菜单填充 SelectItemList,我会想这样做)。
我不知道这可能会或可能不会产生什么样的性能影响,因此可能需要考虑这一点。但这提供了我一直在寻找的灵活性。我希望这个 post 可以帮助可能正在寻找相同类型解决方案的其他人。
如果有更好的方法来解决这个问题,请告诉我。谢谢!
由于我不太明白的原因,我选择不使用 ORM 框架,而是使用通用的 ADO.NET 数据访问层。我最初创建了一个数据库 class,我的所有控制器都可以从中访问。除了我以外,任何人都可以预料到,这个访问对象已经变成了一个怪物。
为了重构我的数据层,我创建了一个 'database adapter' class 作为 DI 注入服务,并创建了一个 'service layer' 来利用它。所以每个控制器现在都有一个 'domain service' 将使用数据库适配器查询数据库和 return 一个通用数据 table。该服务随后会将查询结果和 return 域对象填充回控制器,在那里它可以 assemble 视图模型。
我 运行 遇到了一个问题,我似乎无法从数据库访问层抽象出用于映射数据集 return 的代码,因为每个查询可能 select 不同的字段.例如一个简单的引用数据服务:
public class ReferenceDataService : IReferenceDataService
{
private IDatabaseAdapter _dbAdapter;
public ReferenceDataService(IDatabaseAdapter dbAdapter)
{
_dbAdapter = dbAdapter;
}
public IEnumerable<ReferenceData> GetReferenceData(string table)
{
List<ReferenceData> rdList = new List<ReferenceData>();
StringBuilder sb = new StringBuilder();
sb.Append("SELECT [CODE], [LABEL] FROM [dbo].");
sb.Append(table);
sb.Append(" WHERE END_DATETIME > GETDATE()");
DataSet ds = _dbAdapter.ExecuteDataSet(sb.ToString(), null);
foreach (DataRow row in ds.Tables[0].Rows)
{
rdList.Add(PopulateRecord(row));
}
return rdList;
}
private ReferenceData PopulateRecord(DataRow row)
{
return new ReferenceData
{
ReferenceId = (int)row["REFERENCE_ID"],
Code = (string)row["CODE"],
Label = (string)row["LABEL"],
Description = (string)row["DESCRIPTION"],
BeginDatetime = (DateTime)row["BEGIN_DATETIME"],
EndDatetime = (DateTime)row["END_DATETIME"],
UpdatedBy = (string)row["UPDATED_BY"],
UpdatedOn = (DateTime)row["UPDATED_ON"],
CreatedBy = (string)row["CREATED_BY"],
CreatedOn = (DateTime)row["CREATED_ON"]
};
}
}
在这个例子中,我从 populate 方法中抛出了一个异常,因为如您所见,我只是 select为此特定方法编写代码和标签。我想避免为每个方法自定义映射,但我也不想不必要地 return 来自每个 table 行的所有数据到控制器。我想保留填充方法的通用性,以便针对该 table 的任何查询都将被适当地映射。
我意识到我基本上几乎是在推出自己的 ORM,但我想在没有它的情况下使用服务模式,因为在这一点上我投入太多了。
经过一番挖掘,似乎有一个非常明显和直接的解决方案是我一直缺少的。 DataRow 实例对象能够检查其父 table 列是否存在。通过将来自 table 行的每个赋值包装在这些检查之一中,填充方法将不关心实际 select 编辑到 DataTable 中的内容,并且能够填充一个对象,而不管有多少从查询返回的数据。
因此,在我的示例中,如果我想为 ReferenceData 保留通用填充方法但使用仅重新调整 CODE 和 LABEL 列的查询,则以下更改将使返回的业务对象的填充不可知且无错误:
private ReferenceData PopulateRecord(DataRow row)
{
return new ReferenceData
{
ReferenceId = row.Table.Columns.Contains("REFERENCE_ID") ? (int)row["REFERENCE_ID"] : default(int),
Code = row.Table.Columns.Contains("CODE") ? (string)row["CODE"] : default(string),
Label = row.Table.Columns.Contains("LABEL") ? (string)row["LABEL"] : default(string),
Description = row.Table.Columns.Contains("DESCRIPTION") ? (string)row["DESCRIPTION"] : default(string),
BeginDatetime = row.Table.Columns.Contains("BEGIN_DATETIME") ? (DateTime)row["BEGIN_DATETIME"] : default(DateTime),
EndDatetime = row.Table.Columns.Contains("END_DATETIME") ? (DateTime)row["END_DATETIME"] : default(DateTime),
UpdatedBy = row.Table.Columns.Contains("UPDATED_BY") ? (string)row["UPDATED_BY"] : default(string),
UpdatedOn = row.Table.Columns.Contains("UPDATED_ON") ? (DateTime)row["UPDATED_ON"] : default(DateTime),
CreatedBy = row.Table.Columns.Contains("CREATED_BY") ? (string)row["CREATED_BY"] : default(string),
CreatedOn = row.Table.Columns.Contains("CREATED_ON") ? (DateTime)row["CREATED_ON"] : default(DateTime)
};
}
这将允许我在只返回 CODE 和 LABEL 的 select 语句上使用 PopulateRecord
(例如,如果我为下拉菜单填充 SelectItemList,我会想这样做)。
我不知道这可能会或可能不会产生什么样的性能影响,因此可能需要考虑这一点。但这提供了我一直在寻找的灵活性。我希望这个 post 可以帮助可能正在寻找相同类型解决方案的其他人。
如果有更好的方法来解决这个问题,请告诉我。谢谢!