我有来自存储过程的一对多关系.我在查询中有几个一对多的关系,我试图将这些字段映射到C#对象.我遇到的问题是由于一对多关系,我得到重复的数据.这是我的代码的简化版本:
这是对象类:
public class Person { public int Id { get; set; } public string Name { get; set; } public ListFavoriteColors { get; set; } public List Hobbies { get; set; } public Person() { FavoriteColors = new List (); Hobbies = new List (); } } public class Color { public int Id { get; set; } public string Name { get; set; } } public class Hobby { public int Id { get; set; } public string Name { get; set; } }
以下是我检索数据的方法:
using (SqlConnection conn = new SqlConnection("connstring..")) { string sql = @" SELECT Person.Id AS PersonId, Person.Name AS PersonName, Hobby.Id AS HobbyId, Hobby.Name AS HobbyName, Color.Id AS ColorId, Color.Name AS ColorName FROM Person INNER JOIN Color on Person.Id = Color.PersonId INNER JOIN Hobby on Person.Id = Hobby.PersonId"; using (SqlCommand comm = new SqlCommand(sql, conn)) { using (SqlDataReader reader = comm.ExecuteReader(CommandBehavior.CloseConnection)) { Listpersons = new List (); while (reader.Read()) { Person person = new Person(); //What to do } } } }
正如您所看到的,给定人物可以有多种颜色和爱好.通常,我会使用Entity Framework来解决此映射,但我们不允许使用任何orms.有没有一种技术可以正确地解除这些数据?
这个想法是在阅读器上迭代时检查人员列表中是否存在现有的行人员ID.如果没有创建一个新的person对象并声明两个单独的列表来保存爱好和颜色信息.对于后续迭代,请继续填充这两个列表,因为这些列表始终是相同的人员数据.您可以获得新人的新记录,将这些列表添加到person对象,然后重新开始使用新的person对象
以下是示例代码:
string sql = @" SELECT Person.Id AS PersonId, Person.Name AS PersonName, Hobby.Id AS HobbyId, Hobby.Name AS HobbyName, Color.Id AS ColorId, Color.Name AS ColorName FROM Person INNER JOIN Color on Person.Id = Color.PersonId INNER JOIN Hobby on Person.Id = Hobby.PersonId Order By PersonId"; // Order By is required to get the person data sorted as per the person id using (SqlCommand comm = new SqlCommand(sql, conn)) { using (SqlDataReader reader = comm.ExecuteReader(CommandBehavior.CloseConnection)) { List<Person> persons = new List<Person>(); while (reader.Read()) { var personId = reader.GetInt32(0); var personName = reader.GetString(1); var hobbyId = reader.GetInt32(3); var hobbyName = reader.GetString(4); var colorId = reader.GetInt32(5); var colorName = reader.GetString(6); var person = persons.Where(p => p.Id == personId).FirstOrDefault(); if (person == null) { person = new Person(); person.Id = personId; person.Name = personName; hobby = new Hobby() { Id = hobbyId, Name = hobbyName }; color = new Color() { Id = colorId, Name = colorName }; person.FavoriteColors = new List<Color>(); person.Hobbies = new List<Hobby>(); person.FavoriteColors.Add(color); person.Hobbies.Add(hobby); persons.Add(person); } else { hobby = new Hobby() { Id = hobbyId, Name = hobbyName }; color = new Color() { Id = colorId, Name = colorName }; //JT Edit: if the colour/hobby doesn't already exists then add it if (!person.FavoriteColors.Contains(color)) person.FavoriteColors.Add(color); if (!person.Hobbies.Contains(hobby)) person.Hobbies.Add(hobby); } } } } }
SqlDataReader支持结果集.试试这个.
using (SqlConnection connection = new SqlConnection("connection string here")) { using (SqlCommand command = new SqlCommand ("SELECT Id, Name FROM Person WHERE Id=1; SELECT Id, Name FROM FavoriteColors WHERE PersonId=1;SELECT Id, Name FROM Hobbies WHERE PersonId=1", connection)) { connection.Open(); using (SqlDataReader reader = command.ExecuteReader()) { Person p = new Person(); while (reader.Read()) { p.Id = reader.GetInteger(0); p.Name = reader.GetString(1); } if (reader.NextResult()) { while (reader.Read()) { var clr = new Color(); clr.Id = reader.GetInteger(0); clr.Name = reader.GetString(1); p.FavoriteColors.Add(clr); } } if (reader.NextResult()) { while (reader.Read()) { var hby = new Hobby(); hby.Id = reader.GetInteger(0); hby.Name = reader.GetString(1); p.Hobbies.Add(clr); } } } } }
我认为最终,这里提到的所有方法都有效.我们可以通过专注于性能来提升解决方案.
@Mark Menchavez评论了当我们开始使用简单的人员列表时反复返回数据库的性能影响.对于一个巨大的列表,这种影响是显着的,应尽可能避免.
最终,最好的方法是尽可能少地获取数据; 在这种情况下,因为一个块将是理想的(如果连接不是太昂贵).数据库针对处理数据集进行了优化,我们将使用它来避免设置多个重复连接的开销(特别是如果我们通过网络连接到另一台机器上运行的Sql实例).
我将使用@ Luke101的方法,但只需将List更改为值列表.密钥的哈希查找将比使用@ Koder的响应中的Where更快.另请注意,我已将SQL更改为LEFT JOIN,以适应记录中没有Hobby或Color的人员,并允许它们返回为NULL(.NET中的DBNull).
另请注意,由于表格和数据的形状,可能会多次重复颜色和/或爱好,因此我们也需要检查这些,而不仅仅是假设有一种颜色和一种爱好.
我没有在这里重复这些课程.
public static IEnumerable<Person> DataFetcher(string connString) { Dictionary<int, Person> personDict = new Dictionary<int,Person>(1024); //1024 was arbitrarily chosen to reduce the number of resizing operations on the underlying arrays; //we can rather issue a count first to get the number of rows that will be returned (probably divided by 2). using (SqlConnection conn = new SqlConnection(connString)) { string sql = @" SELECT Person.Id AS PersonId, Person.Name AS PersonName, Hobby.Id AS HobbyId, Hobby.Name AS HobbyName, Color.Id AS ColorId, Color.Name AS ColorName FROM Person LEFT JOIN Color on Person.Id = Color.PersonId LEFT JOIN Hobby on Person.Id = Hobby.PersonId"; using (SqlCommand comm = new SqlCommand(sql, conn)) { using (SqlDataReader reader = comm.ExecuteReader(CommandBehavior.CloseConnection)) { while (reader.Read()) { int personId = reader.GetInt32(0); string personName = reader.GetString(1); object hobbyIdObject = reader.GetValue(2); object hobbyNameObject = reader.GetValue(3); object colorIdObject = reader.GetValue(4); object colorNameObject = reader.GetValue(5); Person person; personDict.TryGetValue(personId, out person); if (person == null) { person = new Person { Id = personId, Name = personName, FavoriteColors = new List<Color>(), Hobbies = new List<Hobby>() }; personDict[personId] = person; } if (!Convert.IsDBNull(hobbyIdObject)) { int hobbyId = Convert.ToInt32(hobbyIdObject); Hobby hobby = person.Hobbies.FirstOrDefault(ent => ent.Id == hobbyId); if (hobby == null) { hobby = new Hobby { Id = hobbyId, Name = hobbyNameObject.ToString() }; person.Hobbies.Add(hobby); } } if (!Convert.IsDBNull(colorIdObject)) { int colorId = Convert.ToInt32(colorIdObject); Color color = person.FavoriteColors.FirstOrDefault(ent => ent.Id == colorId); if (color == null) { color = new Color { Id = colorId, Name = colorNameObject.ToString() }; person.FavoriteColors.Add(color); } } } } } } return personDict.Values; }