2014-12-09 2 views
24

У меня есть отношение от одного до многих, исходящее из хранимой процедуры. У меня есть несколько отношений от одного до многих в запросе, и я пытаюсь сопоставить эти поля с объектом 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 для решения этого сопоставления, но нам не разрешено использовать любые ормы. Есть ли способ правильно развязать эти данные?

+0

Может быть, вы можете использовать отражения для создания SQL и получить результат –

+0

@JeffreyZhang Здравствуйте, я не знаю, что вы имеете в виду. Что такое отражение? И как бы построить sql me в картографии? Извините за вопросы, которые просто пытаются понять. – Luke101

+0

Вам понадобятся 3 оператора "select". –

ответ

15

Идея заключается в том, что итерация на считывателе проверяет, существует ли существующий идентификатор лица строки в списке лиц. Если не создать объект нового человека и объявить два отдельных списка, чтобы сохранить информацию о хобби и цвета. Для последующих итераций продолжаются заполнение этих двух списков, поскольку они всегда будут одними и теми же данными. Один вы получите новый рекорд для нового человека, добавьте эти списки к объекту человека и начать с новым лицом объектом

Ниже приведен пример код:

   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); 
         } 
        } 
       } 
      } 
     } 
+0

@ Luke101 что не так с этим ответом? –

+1

@JeremyThompson Этот ответ замечательный. Благодарю. – Luke101

+0

Мне нравится этот рабочий процесс – MaylorTaylor

3

Возможно, проще использовать 3 отдельных запроса для достижения этого.

Person запрос

SELECT * FROM Person 

Затем сделайте свой цикл, а на результаты этого запроса.

... 
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 

} 
+0

Это решение будет работать. Я часто использую эту технику, если список людей, которых нужно найти, не огромен. Проблема с этим подходом заключается в том, что вы возвращаетесь взад и вперед по серверным серверам баз данных для пользователя, который может влиять на производительность. –

+0

Согласовано, накладные расходы на производительность, так как этот подход включает в себя несколько поездок в хранилище данных. Обычно это не проблема, так как большинство случаев использования будет включать какой-то пейджинг, чтобы предотвратить загрузку полного списка лиц. Я нахожу этот подход полезным для простых задач запроса, так как я могу использовать общие классы репозитория, чтобы код был коротким и быстро делал все. Однако использование таких случаев, как обработка отчетов или массовая обработка, не должно использовать этот подход. – failedprogramming

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); 
         } 
        } 
       } 
      } 
     } 
+0

Мне нравится идея получения нескольких наборов результатов для каждого запроса. Как вы можете расширить это решение, если вам нужно получить несколько человек? –

+0

Удалить, где условие или изменить, где условие для получения большего количества людей. Возможно, вам придется выполнять некоторые манипуляции с данными для разделения данных после извлечения. – Ahuman

+0

Как использовать этот метод, если я хочу только 10 лучших? – Luke101

3

Я думаю, что проблема заключается не в том, чтобы сопоставить полученные данные к объекту (для этого я предложил бы использовать Koders подход), а скорее о том, что оператор выбора возвращает слишком много результатов.

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, swimming}

вместо желаемого {personId, красный, рыбалка}, {PersonId, синий, плавание}

в случае я не missread это, я бы предложил вместо добавления столбца 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"; 

и кодеры подходят для привязки res ult к вашему классу Person даст вам желаемый результат.

редактировать: на самом деле Koders код возвращает правильный результат в любом случае из-за

if (!person.FavoriteColors.Contains(color)) 

и

if (!person.Hobbies.Contains(hobby)) 
+1

Добавление таблицы ColorId и HobbyId в Person приводит к изменению отношения. В вопросе каждый человек может иметь несколько любимых цветов и хобби. В предлагаемой структуре таблицы каждый человек может теперь иметь один цвет и хобби. –

+0

Вы абсолютно правы - он, вероятно, не хочет создавать несколько наборов данных с одним и тем же personId. Было просто неестественно, что вы не можете назначить один и тот же цвет нескольким лицам. –

11

Я думаю, в конечном счете, все подходы, упомянутые здесь работы. Мы могли бы решить эти проблемы, сосредоточившись на производительности.

@Mark Menchavez прокомментировал влияние производительности на повторное возвращение в базу данных, когда мы начинаем с простого списка лиц. Для огромного списка это влияние является значительным и его следует избегать как можно больше.

В конечном счете, лучше всего получить данные в виде нескольких кусков, насколько это возможно; в этом случае, поскольку один кусок будет идеальным (если соединения не слишком дороги). Базы данных оптимизированы для работы с наборами данных, и мы будем использовать это, чтобы избежать накладных расходов при настройке нескольких повторных подключений (особенно, если мы переходим через провод к экземпляру Sql, работающему на другой машине).

Я буду использовать подход @ Luke101, но просто перейду в список в словарь значений. Хэш-поиск ключей будет быстрее, чем использование Where in @ Koder. Также обратите внимание, что я изменил SQL, чтобы читать как LEFT JOIN, чтобы разместить тех лиц, у которых нет записи Хобби или Цвет, и разрешить их возвращать как NULL (DBNull в .NET).

Также обратите внимание, что из-за формы таблиц и данных возможно многократное повторение цветов и/или Хобби, поэтому нам нужно также проверить их, а не просто предположить, что будет один цвет и один Хобби.

Я не стал повторять классы здесь.

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

** «Я изменил SQL, чтобы читать как LEFT JOIN, чтобы разместить тех лиц, у которых нет записи Хобби или цвета, и разрешить их возвращать как NULL (DBNull в .NET)». ** - это могло бы быть хорошим, и я не пробовал этот запрос, хотя бы не сказал автор, были ли NULL люди/цвета/хобби или присоединились к таблице 'FROM Person'? Я воспринял это так, как если бы они были значениями по умолчанию, если они не указаны. Если так, то все оптимизации, которые вы предлагаете, будут устранены из этой ошибочной оптимизации LEFT JOIN? –

+0

ps Не поймите меня неправильно, вы и ваш ответ приветствуем [так] :) –

+0

@JeremyThompson, вы попробовали запрос? Я работал с возможностью, что никакой записи не будет существовать для Color или Hobby, я предполагаю, что это принимало вольности. Диктом его приложения может быть то, что запись человека не может быть сохранена без по крайней мере одного из каждого из этих типов записей. Но если они не являются обязательными, тогда ЛЕВЫЙ ПРИСОЕДИНЕНИЕ будет уместным включать этих Лиц без Хобби или Цвет. За исключением того, что целью этого запроса является получение только тех, которые имеют тот или иной. Luke101, вы не хотите взвесить? Возможно, более подробная информация позволит лучше настроить скрипт и поток? – Eniola

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(); 
Смежные вопросы