2014-09-30 14 views
2

Я пытаюсь решить эту проблему, когда я использую Entity Framework (6) в приложении N-уровня. Поскольку данные из репозитория (который содержит всю связь с базой данных) должны использоваться на более высоком уровне (пользовательский интерфейс, службы и т. Д.), Мне нужно сопоставить его с DTO.Сопоставление объектов с DTO без дублированного кода

В базе данных существует довольно много взаимосвязей «многие-ко-многим», поэтому структура данных может/будет становиться сложной где-то вдоль линии срока службы приложений. Я наткнулся на то, что повторяю точный код при написании методов репозитория. Примером этого является мой FirmRepository, который содержит метод GetAll() и метод GetById(int firmId).

В методе GetById(int firmId), у меня есть следующий код (неполный, так как есть намного больше отношения, которые должны быть отображены на DTO):

public DTO.Firm GetById(int id) 
    { 
     // Return result 
     var result = new DTO.Firm(); 

     try 
     { 
      // Database connection 
      using (var ctx = new MyEntities()) 
      { 
       // Get the firm from the database 
       var firm = (from f in ctx.Firms 
          where f.ID == id 
          select f).FirstOrDefault(); 

       // If a firm was found, start mapping to DTO object 
       if (firm != null) 
       { 
        result.Address = firm.Address; 
        result.Address2 = firm.Address2; 
        result.VAT = firm.VAT; 
        result.Email = firm.Email; 

        // Map Zipcode and City 
        result.City = new DTO.City() 
        { 
         CityName = firm.City.City1, 
         ZipCode = firm.City.ZipCode 
        }; 

        // Map ISO code and country 
        result.Country = new DTO.Country() 
        { 
         CountryName = firm.Country.Country1, 
         ISO = firm.Country.ISO 
        }; 

        // Check if this firm has any exclusive parameters 
        if (firm.ExclusiveParameterType_Product_Firm.Any()) 
        { 
         var exclusiveParamsList = new List<DTO.ExclusiveParameterType>(); 

         // Map Exclusive parameter types 
         foreach (var param in firm.ExclusiveParameterType_Product_Firm) 
         { 
          // Check if the exclusive parameter type isn't null before proceeding 
          if (param.ExclusiveParameterType != null) 
          { 
           // Create a new exclusive parameter type DTO 
           var exclusiveParameter = new DTO.ExclusiveParameterType() 
           { 
            ID = param.ExclusiveParameterType.ID, 
            Description = param.ExclusiveParameterType.Description, 
            Name = param.ExclusiveParameterType.Name 
           }; 

           // Add the new DTO to the list 
           exclusiveParamsList.Add(exclusiveParameter); 
          } 
         } 

         // A lot more objects to map.... 

         // Set the list on the result object 
         result.ExclusiveParameterTypes = exclusiveParamsList; 
        } 
       } 
      } 

      // Return DTO 
      return result; 
     } 
     catch (Exception e) 
     { 
      // Log exception 
      Logging.Instance.Error(e); 

      // Simply return null 
      return null; 
     } 
    } 

Это только один метод. Тогда метод GetAll() будет иметь точную топологию , которая приводит к дублированному коду. Кроме того, когда добавляется больше методов, то есть метод Find или Search, повторное копирование необходимо скопировать. Это, конечно, не идеально.

Я много читал о знаменитой структуре AutoMapper, которая может отображать объекты в/из DTO, но поскольку у меня есть эти отношения «многие-ко-многим», она быстро ощущается раздутой с помощью кода конфигурации AutoMapper. Я также прочитал эту статью, которая имеет смысл в моих глазах: http://rogeralsing.com/2013/12/01/why-mapping-dtos-to-entities-using-automapper-and-entityframework-is-horrible/

Есть ли другой способ сделать это без копирования и вставки одного и того же кода снова и снова?

Заранее благодарен!

+1

Что вы пытаетесь достичь? Вы уверены, что должны возвращать объекты DTO вместо объектов? В чем смысл «переназначения» объектов на почти одни и те же объекты? – tdragon

+0

Привет, tdragon, спасибо за ваш вклад. Когда я возвращаю объекты, то есть мои MVC-контроллеры, данные теряются, поскольку они не входят в область DbContext. Вот почему я собираю данные в DTO. Да? :-) –

+0

проверить это, если это поможет http://stackoverflow.com/questions/13156437/mapping-a-dto-to-an-entity-with-automapper – NMK

ответ

0

Альтернативная стратегия заключается в использовании комбинации конструктора классов и операторов преобразования explicit и/или implicit. Это позволяет вам наложить один пользовательский объект на другой объект. Эта функция также обладает дополнительным преимуществом абстрагирования процесса, поэтому вы не повторяетесь.

В вашем DTO.Firm классе, либо указать явный или неявный оператор (Примечание: Я делаю предположение о названии ваших классов):

public class Firm { 
    public Firm(DB.Firm firm) { 
    Address = firm.Address; 
    Email = firm.Email; 
    City = new DTO.City() { 
     CityName = firm.City.City1; 
     ZipCode = firm.City.ZipCode; 
    }; 
    // etc. 
    } 

    public string Address { get; set;} 
    public string Email { get; set; } 
    public DTO.City City { get; set; } 
    // etc. 

    public static explicit operator Firm(DB.Firm f) { 
    return new Firm(f); 
    } 
} 

Вы можете использовать его в хранилище кода:

public DTO.Firm GetById(int id) { 
    using (var ctx = new MyEntities()) { 
    var firm = (from f in ctx.Firms 
       where f.ID == id 
       select f).FirstOrDefault(); 

    return (DTO.Firm)firm; 
    } 
} 

public List<DTO.Firm> GetAll() { 
    using (var ctx = new MyEntities()) { 
    return ctx.Firms.Cast<DTO.Firm>().ToList(); 
    } 
} 

Вот reference в MSDN.

+0

Будет ли этот подход работать с Entity Frameworks ленивыми запросами? Будет ли вызываться оператор преобразования до того, как dbcontext выйдет за пределы области видимости или будет необходимо выполнить кастинг сразу же после выбора? –

+0

Последний проект, над которым я работал, полагался на этот подход, и мы использовали ленивую загрузку в EF, поэтому я уверен, что ответ да, но я оставляю за собой право ошибаться. :) Что касается dbContext, вам не нужно делать преобразование при выборе. Вы можете извлекать данные из контекста, а затем приводить результат позже. – Brett

+0

Привет, Бретт, на самом деле это довольно аккуратное решение. Благодаря! :-) Теперь, если я не хочу делать броски каждый раз, мне придется использовать неявный оператор. Использование неявного оператора означает, что мои объекты DTO должны быть на 100% равны объектам, не так ли? –

2

Вы можете сделать метод расширения Entity фирмы (DB.Firm), как это,

public static class Extensions 
    { 
     public static DTO.Firm ToDto(this DB.Firm firm) 
     { 
      var result = new DTO.Firm(); 
      result.Address = firm.Address; 
      result.Address2 = firm.Address2; 
      //... 

      return result;  
     } 
    } 

Затем вы можете преобразовать объект DB.Firm где-нибудь в вашем коде как firm.ToDto();

0

О отображении: это на самом деле не имеет большого значения, если вы используете Automapper или готовите сопоставления полностью вручную в каком-то методе (расширение или как явный оператор литья, как указано в других ответах) - дело в том, чтобы оно было в одном месте для повторного использования.

Просто помните - вы использовали метод FirstOrDefault, поэтому вы фактически назвали базу данных для объекта Firm. Теперь, когда вы используете свойства этого объекта, особенно коллекции, они будут лениться загружены. Если у вас их много (как вы предлагаете в своем вопросе), вы можете столкнуться с огромным количеством дополнительных вызовов, и это может быть проблемой, особенно в цикле foreach. Вы можете столкнуться с дюжиной звонков и тяжелыми проблемами производительности, чтобы получить один dto. Просто передумайте, если вам действительно нужно получить такой большой объект со всеми его отношениями.

Для меня ваша проблема гораздо глубже и учитывает архитектуру приложения. Должен сказать, мне лично не нравится шаблон репозитория с Entity Framework, в дополнение к шаблону Unit Of Work. Это кажется очень популярным (по крайней мере, вы смотрите на результаты Google для запроса), но для меня это не очень хорошо подходит для EF. Конечно, это просто мое мнение, вы можете не согласиться со мной. Для меня это просто создает еще одну абстракцию по уже реализованной Единице работы (DbContext) и репозиториям (объекты DbSet). Я нашел this article очень интересно с учетом этой темы. Командование/запрос разделения вещей для дела кажется мне более элегантным, а также намного лучше подходит к правилам SOLID.

Как я уже сказал, это просто мое мнение, и вы можете или не согласны со мной. Но я надеюсь, что это даст вам несколько вариантов.

Смежные вопросы