【问题标题】:Reading multiple child objects from SQL Server从 SQL Server 读取多个子对象
【发布时间】:2015-10-21 08:26:19
【问题描述】:

我需要从我的 SQL Server 数据库中加载一个大型对象及其所有子对象。

我很喜欢在查询中使用FOR XML,然后使用XmlReader,然后再将批次反序列化为所需的类,就像这样:

SELECT  
    MyClass.* ,
    (SELECT    
         ChildClass1.[ID],
         ChildClass1.[Description]
     FROM      
         [dbo].[ChildClass1Table] ChildClass1
     JOIN 
         dbo.LinkyTable lt ON lt.ChildClass1ID = ChildClass1.ID
     WHERE     
         lt.CID = MyClass.ID
     FOR XML AUTO, ROOT('ChildClass1'), TYPE, ELEMENTS),
    (SELECT    
         ChildClass2.[ID], ChildClass2.[Description]
     FROM      
         [dbo].[ChildClass1Table] ChildClass2
     JOIN 
         dbo.LinkyTable2 lt2 ON lt.ChildClass2ID = ChildClass2.ID
     WHERE     
         lt2.CID = MyClass.ID
     FOR XML AUTO, ROOT('ChildClass2'), TYPE, ELEMENTS) 
FROM 
    ...etc - for a fair few more

C#代码:

using (System.Xml.XmlReader xmlr = cmd.ExecuteXmlReader())
{
        if (xmlr.Read())
        {
            string xml = string.Empty;

            while (xmlr.ReadState != System.Xml.ReadState.EndOfFile)
            {
                xml = xmlr.ReadOuterXml();
            }

            var serializer = new XmlSerializer(typeof(MyClass));

            using (var stream = new StringReader(xml))
            using (var reader = XmlReader.Create(stream))
            {
                MyClass b = (MyClass)serializer.Deserialize(reader);
                return b;
            }
        }
    }

这很好用,我真的没有任何问题——除了理想情况下我想使用存储库模式从数据库中获取我的所有对象并且宁愿使用 IDbConnection / IDbCommand 而不是使用 XmlReader 所必需的具体 SQL Server 类。

我的问题是,是否有另一种方法可以使用标准阅读器加载这些子对象(无需为每个子对象(和孙子类)多次往返数据库)?

谢谢

【问题讨论】:

标签: c# asp.net sql-server xml ado.net


【解决方案1】:

可以选择纯 ADO.NET 吗?

猜想我们有以下存储过程:

create procedure dbo.GetParents
as
begin
    set nocount on;

    declare @Parents table (Id int, Name varchar(50));
    declare @Children table (Id int, ParentId int, Name varchar(50));

    insert into @Parents values 
        (1, 'First parent'),
        (1, 'First parent'),
        (1, 'First parent');

    insert into @Children values 
        (1, 1, 'First child of first parent'),
        (2, 1, 'Second child of first parent'),
        (3, 1, 'Third child of first parent'),
        (4, 2, 'First child of second parent'),
        (5, 2, 'Second child of second parent'),
        (6, 2, 'Third child of second parent'),
        (7, 3, 'First child of third parent'),
        (8, 3, 'Second child of third parent'),
        (9, 3, 'Third child of third parent');

    select * from @Parents order by Id;

    select * from @Children order by ParentId, Id;
end;

还有一对类:

public class Parent {
    public int Id {get; set; }
    public string Name {get; set; }
    public List<Child> Children {get; set; }
}
public class Child {
    public int Id {get; set; }
    public int ParentId {get; set; }
    public string Name {get; set; }
} 

以下代码读取父母及其子女的列表:

var parents = new List<Parent>();
var children = new List<Child>();

var connectionString=@"Data Source=.\SQLEXPRESS;Initial Catalog=MyDb;Integrated Security=True";

using (SqlConnection connection = new SqlConnection(connectionString)) {

    var cmd = connection.CreateCommand();

    cmd.CommandType = CommandType.StoredProcedure;
    cmd.CommandText = "GetParents";

    connection.Open();

    using (var reader = cmd.ExecuteReader())
    {
        while (reader.Read()) 
            parents.Add(new Parent {Id = reader.GetInt32(0), Name = reader.GetString(1) });

        reader.NextResult();

        while (reader.Read()) 
            children.Add(new Child {Id = reader.GetInt32(0), ParentId = reader.GetInt32(1), Name = reader.GetString(2) });
    }
}

// combination of two collections

var childEnumerator = children.GetEnumerator();
var child = childEnumerator.MoveNext() ? childEnumerator.Current : null;

foreach (var parent in parents)
{
    parent.Children = new List<Child>();

    while (child != null && child.ParentId == parent.Id)
    {
        parent.Children.Add(child);
        child = childEnumerator.MoveNext() ? childEnumerator.Current : null;
    }
}

当然,两个集合的组合可以更简单,比如:

foreach (var parent in parents)
{
    parent.Children = new List<Child>();

    foreach (var child in children) 
    {
        parent.Children.Add(child);
    }
}

但是当两个记录集被正确排序时,GetEnumerator() 会使这个过程更快。

【讨论】:

  • 感谢您的回复,这对于几个子属性来说看起来不错,但我的顶级对象中有 12 个,并且有些有相当数量的属性。这可能会变得非常混乱
  • 针对这种情况,专门创建了以下库——以减少读取和保存对象图形时的混乱:github.com/lobodava/artisan-orm
【解决方案2】:

这是我正在使用的解决方案。 程序:

CREATE PROCEDURE [dbo].[GetParentsWithChildren] 
@ParentId int = null
AS
BEGIN
SET NOCOUNT ON;
 select * from Parents
 where (@ParentId IS NULL OR Id = @ParentId)    
 order by Id;

select * from Children
where (@ParentId IS NULL OR ParentId = @ParentId )  
order by ParentId, Id;
END

您可以根据需要声明模型。根据我在此处注释掉的传递参数,您只能获得一个或所有父母。

下面是获取列表的函数:

    public List<Partner> GetParentsWithChildren()
    {
        List<Parent> parents = new List<Parent>();
        List<Child> children = new List<Child>();

        using (SqlConnection conn = new SqlConnection(connStr))
        {

            conn.Open();
            SqlCommand cmd = new SqlCommand("GetPartnersWithDevices", conn);
            cmd.CommandType = System.Data.CommandType.StoredProcedure;
            //cmd.Parameters.AddWithValue("@ParentId", 1);
            cmd.ExecuteNonQuery();

            using (SqlDataReader reader = cmd.ExecuteReader())
            {                   
                parents = GetModelFromReader<Parent>(reader);

                reader.NextResult();                    

                children = GetModelFromReader<Child>(reader);
            }

            foreach (Parent parent in parents)
            {
                parent.Children = children.Where(x => x.ParentId == parent.Id).ToList();
            }
        }
        return partners;
    }

在这里,我创建了一个通用函数,它将返回传递对象的列表。 唯一的规则是模型道具在表格中被命名为字段,并且道具类型应该与表格中的相同或所有字符串。尽管如此,它仍然会起作用。 如果您在模型中使用了默认构造函数,请添加它。模型必须有一个默认构造函数。

    private List<T> GetModelFromReader<T>(SqlDataReader reader) where T : new()
    {
        List<T> listModels = new List<T>();

        var modelItem = new T();

        var modelType = modelItem.GetType();
        var modelProps = modelType.GetProperties();

        while (reader.Read())
        {
           var columns = Enumerable.Range(0, reader.FieldCount)
                       .Select(reader.GetName)
                       .ToList();
            foreach (var prop in inProps)
            {
                try
                {
                    if (columns.Contains(prop.Name))
                    {
                        var value = reader[prop.Name];
                        if (ReferenceEquals(value, DBNull.Value))
                        {
                            value = null;
                        }                            
                        var propType = prop.PropertyType;
                        Type t = Nullable.GetUnderlyingType(propType) ?? propType;
                        value = (value == null) ? null : Convert.ChangeType(value, t);

                        prop.SetValue(modelItem, value);
                    }                       
                }
                catch (Exception ex)
                { //it type not the same will try to convert to string and cast.
                  //It will work fine without this part if types are the same.
                  //I recommend that you set types properly and comment out this part
                    try
                    {
                        var strValue = reader[prop.Name].ToString();
                        var value = Convert.ChangeType(strValue, prop.PropertyType);
                        prop.SetValue(modelItem, value);
                    }
                    catch { }
                }
            }
            listModels.Add(modelItem);
            modelItem = new T();
        }
        return listModels;
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-17
    • 1970-01-01
    • 2016-06-17
    • 2013-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-22
    相关资源
    最近更新 更多