【问题标题】:ADO.NET build complex objects using DataReaderADO.NET 使用 DataReader 构建复杂对象
【发布时间】:2014-11-12 00:12:24
【问题描述】:

我正在尝试创建一种方法来对数据库进行唯一搜索并为我的需要构建正确的对象。我的意思是,我使用返回很多行的 SQL 查询,然后基于该数据库行构建集合。例如:

我们有一个名为People 的表和另一个名为Phones 的表。

假设这是我的 SQL 查询,将返回以下内容:

SELECT 
   P.[Id], P.[Name], PH.[PhoneNumber]
FROM
   [dbo].[People] P
INNER JOIN 
   [dbo].[Phones] PH ON PH.[Person] = P.[Id]

这就是返回的结果:

1   NICOLAS    (123)123-1234
1   NICOLAS    (235)235-2356

所以,我的课是:

public interface IModel {
    void CastFromReader(IDataReader reader);
}

public class PhoneModel : IModel {
    public string PhoneNumber { get; set; }

    public PhoneModel() {  }
    public PhoneModel(IDataReader reader) : this() {
         CastFromReader(reader);
    }

    public void CastFromReader(IDataReader reader) {
        PhoneNumber = (string) reader["PhoneNumber"];
    }
}

public class PersonModel : IModel {
    public int Id { get; set; }
    public string Name { get; set; }
    public IList<PhoneModel> Phones { get; set; }

    public PersonModel() {  
        Phones = new List<PhoneModel>();
    }

    public PersonModel(IDataReader reader) : this() {
         CastFromReader(reader);
    }

    public void CastFromReader(IDataReader reader) {
        Id = Convert.ToInt32(reader["Id"]);
        Name = (string) reader["Name"];

        var phone = new PhoneModel();
        phone.CastFromReader(reader);
        Phones.Add(phone);

        // or
        Phones.Add(new PhoneModel {
            PhoneNumber = (string) reader["PhomeNumber"]
        });
    }
}

此代码将生成一个带有两个电话号码的PersonModel 对象。到目前为止还不错。

但是,当我想通过此流程管理更多表时,我正在努力寻找一些好的方法来处理。

那么,假设我有一个名为Appointments 的新表。它将用户的约会存储到日程表中。

因此,将此表添加到查询中,结果将是:

1   NICOLAS   (123)123-1234    17/09/2014
1   NICOLAS   (123)123-1234    19/09/2014
1   NICOLAS   (123)123-1234    27/09/2014
1   NICOLAS   (235)235-2356    17/09/2014
1   NICOLAS   (235)235-2356    19/09/2014
1   NICOLAS   (235)235-2356    17/09/2014

正如你们所看到的,问题在于以这种方式管理电话和约会。你能想到任何可以解决这个问题的方法吗?

谢谢大家的意见!

【问题讨论】:

  • 问题是什么?问题应遵循来自已定义问题。
  • 好吧,也许您应该研究一下如何使用 ORM。这通常是他们的面包和黄油
  • 这个问题是为了收集关于这种方法的意见。我不能在这里征求意见,只是问题?
  • @Steve,在这种情况下,我必须使用 ADO.NET,无法使用 ORM,所以我正在努力寻找更好的工作方式,为许多表搜索特定列并在 DTO 中直接翻译。
  • 好吧,这取决于你。我只是建议不要重新发明轮子。例如,像Dapper 这样的轻量级 ORM 足以满足您的要求,它只是一个 .cs 文件,可以添加到您的项目中,它使用 ADO.NET。性能也很好。

标签: c# ado.net


【解决方案1】:

如果您想为查询产生的每个列组合手动编写数据类型,那么您有很多工作要做,并且最终会得到很多非常相似但略有不同的类,这些类很难命名。另请注意,这些数据类型不应被视为比它们更多的东西:Data Transfer Objects (DTOs)。它们不是具有特定领域行为的真实领域对象;它们应该只包含和传输数据,没有别的。

以下是两个建议或想法。这里只谈表面,不做太多细节;由于您没有提出非常具体的问题,因此我不会提供非常具体的答案。

1. 更好的方法可能是确定您拥有哪些域实体类型(例如 Person、Appointment)以及您拥有哪些域值类型(例如电话号码),然后构建一个对象模型:

struct PhoneNumber { … }

partial interface Person
{
    int Id { get; }
    string Name { get; }
    PhoneNumber PhoneNumber { get; }
}

partial interface Appointment
{
    DateTime Date { get; }
    Person[] Participants { get; }
}

然后将您的数据库代码映射到这些。例如,如果某些查询返回人员 ID、人员姓名、电话号码和约会日期,则必须将每个属性放入正确的实体类型中,并且必须将它们链接在一起(例如通过 @987654323 @) 正确。相当多的工作。如果您不想手动执行此操作,请查看 LINQ to SQL、Entity Framework、NHibernate 或任何其他 ORM。如果你的数据库模型和你的领域模型差别太大,即使这些工具也可能无法进行翻译。

2. 如果您想手动编码将数据转换为域模型的数据查询层,您可能希望以这样一种方式设置查询,即如果它们返回一个属性 A实体 X 的属性,而实体 X 具有其他属性 B、C 和 D,则查询也应返回这些属性,以便您始终可以根据查询结果构建完整的域对象。例如,如果查询返回人员 ID 和人员电话号码,但未返回人员姓名,则您无法从查询中构建 Person 对象(如上定义),因为缺少姓名。

第二个建议至少可以部分避免您必须定义许多非常相似的 DTO 类型(每个属性组合一个)。这样,您可以有一个 DTO 用于个人记录,另一个用于电话号码记录,另一个用于约会记录,也许(如果需要)另一个用于个人和电话号码的组合;但您不需要区分 PersonWithAllAttributesPersonWithIdButWithoutNameOrPhoneNumberPersonWithoutIdButWithPhoneNumber 等类型。您只需拥有包含所有属性的 Person

【讨论】:

  • 感谢您的回答。我要做的是直接从 SQL 查询生成 DTO 对象,而不是创建 POCO 类,然后将它们转换为 DTO,你知道吗?在某些情况下,它可能更适合我的场景。因此,我正在努力将上面的查询转换为 DTO 对象。
  • 基本事实:如果没有先创建对象的类型,就无法创建对象。一些想法供您跟进:1. 定义接口或抽象基类,并构建一个框架,通过反映 SQL DB 架构并使用System.Reflection.Emit,在运行时自动实现它们。 2. 使用 DLR(动态语言运行时),即dynamic 和例如ExpandoObject。但是这样你会失去对编译时 IntelliSense 的支持。 3. 在编译时自动生成类型,例如使用 T4 模板。 4. 使用 ORM。
  • 说到#2,您可以从DynamicObject 派生一个新类型,它会自动将(后期绑定)属性匹配到IDataRecord 的同名字段。
【解决方案2】:

如果不先定义这些对象的类型,您就无法将查询结果转移到强类型对象。如果您想将查询数据保留在内存中,我建议您在某个时候将其传输到先前定义的类型的对象中。

因此,我实际上不建议这样做。但我想向你展示一种可能性。自己判断。

正如我在 a previous comment 中所建议的那样,您可以使用动态语言运行时 (DLR) 来模仿强类型 DTO,该动态语言运行时 (DLR) 已随 .NET 4 提供。

这是一个自定义DynamicObject 类型的示例,它为IDataReader 提供了一个看似强类型的外观。

using System.Data;
using System.Dynamic; // needs assembly references to System.Core & Microsoft.CSharp
using System.Linq;

public static class DataReaderExtensions
{
    public static dynamic AsDynamic(this IDataReader reader)
    {
        return new DynamicDataReader(reader);
    }

    private sealed class DynamicDataReader : DynamicObject
    {
        public DynamicDataReader(IDataReader reader)
        {
            this.reader = reader;
        }

        private readonly IDataReader reader;

        // this method gets called for late-bound member (e.g. property) access   
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            int index = reader.GetOrdinal(binder.Name);
            result = index >= 0 ? reader.GetValue(index) : null;
            return index >= 0;
        }
    }
}

那么你可以这样使用它:

using (IDataReader reader = someSqlCommand.ExecuteReader(…))
{
    dynamic current = reader.AsDynamic(); // façade representing the current record
    while (reader.Read())
    {
        // the magic will happen in the following two lines:
        int id = current.Id; // = reader.GetInt32(reader.GetOrdinal("Id"))
        string name = current.Name; // = reader.GetString(reader.GetOrdinal("Name"))
        …
    }
}

但请注意,通过此实现,您得到的只是当前记录的外观。如果你想在内存中保存多条记录的数据,这个实现不会有太大帮助。为此,您可以进一步研究几种可能性:

  • 使用匿名对象:cachedRecords.Add(new { current.Id, current.Name });。仅当您在构建它的同一方法中访问 cachedRecords 时,这才有用,因为使用的匿名类型将无法在该方法之外使用。

  • ExpandoObject 中缓存current 的数据。

【讨论】:

  • 我喜欢您在此处介绍的这种动态方法。我会更深入地检查它,看看它是否适用于我的场景。缓存也是一个有趣的东西。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多