I have a one to many relationship coming from a stored procedure. I have several one to many relationships in the query and i am trying to map these fields to a C# object. The problem i am having is i get duplicate data because of the one to many relationships. Here is a simplified version of my code:
Here is the objects classes:
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; }
}
Here is how I am retrieving the data:
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
}
}
}
}
As you can see there can be multiple colors and hobbies for a given Person. Usually, I would use Entity Framework to solve this mapping but we are not allowed to use any orms. Is there a technique to properly unflatten this data?
The idea is while iterating on the reader check if the existing row person id exists in the person list. If not create a new person object and declare two separate lists to hold the hobby and color info. For subsequent iterations go on populating these two lists because these will always be the same persons data. One you get to a new record for a new person, add these lists to the person object and start over with a new person object
Below is the sample code:
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);
}
}
}
}
}