具有不同查询选择列表的广义 DTO 填充方法

Posted

技术标签:

【中文标题】具有不同查询选择列表的广义 DTO 填充方法【英文标题】:Generalized DTO population method with different query select lists 【发布时间】:2019-09-23 02:44:15 【问题描述】:

由于我不太了解的原因,我选择不使用 ORM 框架并使用通用的 ADO.NET 数据访问层。我最初创建了一个数据库类,我的所有控制器都可以从中访问。正如除我之外的任何人所预料的那样,这个访问对象已经变成了怪物。

为了重构我的数据层,我创建了一个“数据库适配器”类作为 DI 注入服务,并创建了一个“服务层”来使用它。所以现在每个控制器都有一个“域服务”,它将使用数据库适配器来查询数据库并返回一个通用数据表。然后,该服务将填充查询结果并将域对象返回给控制器,在那里它可以组装视图模型。

我遇到了一个问题,我似乎无法抽象用于映射从数据库访问层返回的数据集的代码,因为每个查询都可能选择不同的字段。比如一个简单的参考数据服务:

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 方法中抛出了一个异常,因为正如你所看到的,我只是为这个特定的方法选择代码和标签。我想避免每个方法的自定义映射,但我也不想不必要地将每个表行中的所有数据返回到控制器。我想保持 populate 方法的通用性,以便针对该表的任何查询都将被适当地映射。

我意识到我基本上几乎是在推出自己的 ORM,但我想使用没有它的服务模式,因为在这一点上我太投入了。

【问题讨论】:

【参考方案1】:

经过一番挖掘,似乎有一个非常明显和直接的解决方案,但我一直没有找到。 DataRow 实例对象能够检查其父表列是否存在。通过将表格行中的每个分配包装在其中一项检查中,填充方法将不关心实际选择到 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 的选择语句上使用 PopulateRecord(例如,如果我为下拉列表填充 SelectItemList,我会想这样做)。

我不知道这可能会或可能不会产生什么样的性能影响,因此可能需要考虑这一点。但这允许我正在寻找的灵活性。我希望这篇文章能帮助其他可能正在寻找相同类型解决方案的人。

如果有更好的方法来解决这个问题,请告诉我。谢谢!

【讨论】:

以上是关于具有不同查询选择列表的广义 DTO 填充方法的主要内容,如果未能解决你的问题,请参考以下文章

ms excel vba 填充具有不同列要求的列表框

从两个不同的对象填充 DTO

如何在具有枚举字段的实体上使用 JPA CriteriaQuery 填充 DTO 类字符串字段?

在选择框中选择不同的值和 id

如何在 Nestjs 上使用具有多个 dto 的一条路由?

将 DTO 映射到后端实体