我正在尝试创建一种方法来对数据库进行独特搜索,并根据我的需要构建正确的对象。我的意思是,我使用SQL查询返回很多行,然后根据该数据库行构建集合。 E.g:
我们有一个名为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
正如大家们所看到的,问题是以这种方式管理手机和约会。你能想到任何可以解决这个问题的东西吗?
谢谢大家的意见!
答案 0 :(得分:1)
如果您想为查询产生的每个列组合手动编写数据类型,那么您需要做很多工作,最终会遇到很多非常相似但略有不同的类很难命名。另请注意,这些数据类型不应被视为超出它们的内容:Data Transfer Objects (DTOs)。它们不是具有特定于域的行为的真实域对象;它们应该只包含和传输数据,没有别的。
以下是两个建议或想法。我只会在这里划伤,而不是进入太多的细节;既然你没有问过一个非常具体的问题,我就不会提供一个非常具体的答案。
1。更好的方法可能是确定您拥有的域实体类型(例如,人员,约会)以及您拥有的域值类型(例如电话号码),然后从中构建一个对象模型:
struct PhoneNumber { … }
partial interface Person
{
int Id { get; }
string Name { get; }
PhoneNumber PhoneNumber { get; }
}
partial interface Appointment
{
DateTime Date { get; }
Person[] Participants { get; }
}
然后让您的数据库代码映射到这些。例如,如果某个查询返回人员ID,人名,电话号码和约会日期,则每个属性都必须放入正确的实体类型,并且必须将它们链接在一起(例如通过{{ 1}})正确。相当多的工作。如果您不想手动执行此操作,请查看LINQ to SQL,Entity Framework,NHibernate或任何其他ORM。如果您的数据库模型和您的域模型太不相同,那么即使这些工具也可能无法进行翻译。
2。如果您想手动编码将数据转换为域模型的数据查询图层,您可能希望以这样的方式设置查询:如果它们返回一个属性A实体X和实体X具有其他属性B,C和D,然后查询也应返回这些属性,这样您始终可以从查询结果中构建完整的域对象。例如,如果查询返回了人员ID和人员电话号码,但没有返回人员姓名,则无法从查询中构建Participants
个对象(如上所述),因为名称丢失。
这第二个建议至少可以部分地避免您必须定义许多非常相似的DTO类型(每个属性组合一个)。这样,您可以为个人记录设置DTO,为电话号码记录设置另一个DTO,为约会记录设置另一个,也许(如果需要)另一个用于人员和电话号码的组合;但您不需要区分Person
,PersonWithAllAttributes
,PersonWithIdButWithoutNameOrPhoneNumber
等类型。您只需要PersonWithoutIdButWithPhoneNumber
包含所有属性。
答案 1 :(得分:1)
如果没有先定义这些对象的类型,则无法将查询结果传输到强类型对象。如果要将查询数据保留在内存中,我建议您在某些时候将其传输到先前定义类型的对象中。
因此,以下内容并非我实际建议做的事情。但我想向你展示一种可能性。自己判断。
正如我在a previous comment中所建议的那样,你可以使用动态语言运行时(DLR)来模仿强类型的DTO,它已经可以在.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
的数据。