从SQL Server读取多个子对象

时间:2015-10-21 08:26:19

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

我需要从我的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 / {{1}而不是使用IDbCommand

所必需的具体SQL Server类

我的问题是,是否有其他方式可以加载这些子对象(不使用标准读取器为每个子(和孙子类)多次往返数据库)?

由于

2 个答案:

答案 0 :(得分: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()会使该过程更快。

答案 1 :(得分:0)

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

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