我有来自存储过程的一对多关系。我在查询中有几个一对多的关系,我试图将这些字段映射到C#对象。我遇到的问题是由于一对多关系,我得到重复的数据。这是我的代码的简化版本:
以下是对象类:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public List<Color> FavoriteColors { get; set; }
public List<Hobby> Hobbies { get; set; }
public Person()
{
FavoriteColors = new List<Color>();
Hobbies = new List<Hobby>();
}
}
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))
{
List<Person> persons = new List<Person>();
while (reader.Read())
{
Person person = new Person();
//What to do
}
}
}
}
正如您所看到的,给定人物可能有多种颜色和爱好。通常,我会使用Entity Framework来解决此映射,但我们不允许使用任何orms。有没有一种技术可以正确地解除这些数据?
答案 0 :(得分:15)
这个想法是在阅读器上迭代时检查人员列表中是否存在现有的行人员ID。如果没有创建一个新的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);
}
}
}
}
}
答案 1 :(得分:11)
我认为最终,这里提到的所有方法都有效。我们可以通过专注于绩效来提升解决方案。
@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;
}
答案 2 :(得分:5)
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);
}
}
}
}
}
答案 3 :(得分:3)
使用3个单独的查询来实现这一目标可能更容易。
人物查询
SELECT * FROM Person
然后对此查询的结果执行while循环。
...
var persons = new List<Person>();
while (reader.Read())
{
var person = new Person();
Person.Id = reader.GetInt32(0);
... // populate the other Person properties as required
// Get list of hobbies for this person
// Use a query to get hobbies for this person id
// e.g. "SELECT * FROM Hobby WHERE Hobby.PersonId = " + Person.Id
// Get a list of colours
// Use a query to get colours for this person id
}
答案 4 :(得分:3)
我认为问题不在于如何将检索到的数据映射到一个对象(因为我建议使用koders方法),而是select语句返回太多结果。
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";
在我看来,表格Color
和Hobby
每个都包含一个PersonId
,它将它们分配给一个独特的人。 (因此内部联接返回,即{personId,blue,fishing},{personId,red,fishing},{personId,blue,swimming},{personId,red,swimming}
而不是期望的 {personId,red,fishing},{personId,blue,swimming}
如果我没有错过这个,我建议改为在表ColorId
中添加一列HobbyId
和Person
。如果您这样做,则可以使用
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.ColorId = Color.Id
INNER JOIN Hobby on Person.HobbyId = Hobby.Id";
和koders将结果绑定到Person
类的方法将为您提供所需的结果。
编辑:实际上,由于
,koders代码会返回正确的结果if (!person.FavoriteColors.Contains(color))
和
if (!person.Hobbies.Contains(hobby))
答案 5 :(得分:2)
您可以使用下面的查询,为每个人返回一行。颜色和爱好以xml字符串形式返回,您可以在代码中解析它。
select p.personId, p.personName
,cast((select colorId,colorName from Color as c where c.personId = p.personId for xml raw) as nvarchar(max)) as Colors
,cast((select hobbyId,hobbyName from Hobby as h where h.personId = p.personId for xml raw) as nvarchar(max)) as Hobbies
from Person as p
然后您可以使用此代码来解析颜色
var root = XElement.Parse("<root>" + colorXml + "</root>");
var colors = root.Nodes()
.Where(n => n.NodeType == XmlNodeType.Element)
.Select(node =>
{
var element = (XElement)node;
return new Color()
{
Id = Convert.ToInt32(element.Attribute("colorId").Value),
Name = element.Attribute("colorName").Value
};
}).ToList();