简体   繁体   English

从SQL Server读取多个子对象

[英]Reading multiple child objects from SQL Server

I have the need to load in a large object with all its children from my SQL Server database. 我需要从我的SQL Server数据库中加载带有其所有子级的大对象。

I plumped for using FOR XML in the queries and then using an XmlReader before deserialising the lot into the classes needed much like this: 我想在查询中使用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# code: 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;
            }
        }
    }

This works well, and I really have no issue with it - other than I ideally would like to use the repository pattern to grab all my objects from the DB and would rather use the IDbConnection / IDbCommand rather than the concrete SQL Server classes which are necessary to use the XmlReader . 这很好用,而且我对此没有任何问题-理想情况是我希望使用存储库模式从数据库中获取我的所有对象,而宁愿使用IDbConnection / IDbCommand而不是必需的具体SQL Server类使用XmlReader

My question is, is there another way that these child objects can be loaded in (without making multiple round trips to the DB for each child (and grandchild class)) using standard readers ? 我的问题是,有没有其他方法可以使用标准读取器加载这些子对象(而无需为每个子对象(和孙子类)进行多次往返DB的访问)?

Thanks 谢谢

Is the pure ADO.NET an option? 纯ADO.NET是一个选择吗?

Guess we have the following stored procedure: 猜猜我们有以下存储过程:

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;

and a pair of classes: 和一对类:

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; }
} 

the following code reads a list of parents with their children: 以下代码读取带有孩子的父母名单:

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;
    }
}

Of course the combination of two collections can be done simplier, like: 当然,可以更简单地完成两个集合的组合,例如:

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

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

but when two recordsets are sorted properly then GetEnumerator() makes the process much faster. 但是当两个记录集正确排序后,GetEnumerator()会使处理过程更快。

Here's a solution i'm using. 这是我正在使用的解决方案。 The procedure: 步骤:

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

You declare your models as needed. 您根据需要声明模型。 You can get only one or all parents, depending on passed parameter that I've commented out here. 您只能获得一个或所有父母,具体取决于我在此处注释掉的传递参数。

Here's function that will get the list: 这是将获取列表的函数:

    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;
    }

Here i created a generic function that will return a list of passed object. 在这里,我创建了一个通用函数,该函数将返回已传递对象的列表。 The only rule is that the model props are named as fields in table, and prop types should be the same as in table or all strings. 唯一的规则是模型道具在表中被命名为字段,并且道具类型应与表或所有字符串中的相同。 It will work nevertheless. 尽管如此,它将起作用。 If you've owerriden default constructor in your model, add it. 如果您在模型中添加了默认构造函数,请添加它。 Model must have a default constructor. 模型必须具有默认构造函数。

    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;
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM