Добавление ассоциации linq-entity в контроллер MVC - PullRequest
1 голос
/ 31 марта 2009

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

Скажите, если модель Product имеет связь с таблицей Users через Владелец связь. Контроллер вызывает службу или хранилище, чтобы получить список пользователей, передать его в View, и View отображает пользователей в виде раскрывающегося списка.

Затем в стандартном POST: product / Create контроллер должен получить выбранный идентификатор пользователя, затем захватить сущность User этого идентификатора и связать ее с создаваемым продуктом.

  if (!String.IsNullOrEmpty(Request["SelectedUserId"]))
        {
            int selectedUserId = Convert.ToInt16(Request["SelectedUserId"]);
            newProduct.Owner = _productService.GetUserById(ownerId);
        }

  _productService.AddProduct(newProduct);

Но этот процесс усложняет логику контроллера. Еще хуже, если нам нужно проверить ассоциации (поскольку не было бы событий OnChanging для ассоциаций, мы не можем сделать это в частичном классе Model).

Мой вопрос: есть ли лучший способ справиться с ассоциациями? Лучше передать параметры Request и слоям обслуживания и там все делать? А как насчет проверок?

Ответы [ 2 ]

0 голосов
/ 31 марта 2009

Это идеальная книга , которую вы должны прочитать о том, как правильно моделировать ("M" в MVC) репозитории, модели, и он включает в себя прямые ассоциации Linq. В качестве бонуса он решает, как автоматически подключить все ваши пользовательские контроллеры к репозиторию, который они ожидают. Да, он был написан для версии ASP.NET MVC, которая имеет 4 версии (до выпуска), но концепции остаются с вами. Информация относится ко всем проектам MVC (или к любому проекту DDD, а точнее к Linq).

Короче говоря, в книге представлены концепции DDD для внедрения хранилища в ваш пользовательский контроллер.

public class ContentController : Controller
{
  IContentRepository _repository;
  public ContentController(IContentRepository repo)
  {
    _repository = repo;
  }

  public ActionResult Add(Content c)
  {
    _repository.Add(c);
    _repository.SubmitChanges();

    return View(c);
  }
}

Обратите внимание на используемую здесь концепцию внедрения зависимостей, принудительно вставив хранилище в конструктор. Теперь вы можете просто подключить его самостоятельно без конструктора. Но когда вы попадаете в большой проект (более 50 репозиториев), это помогает использовать инверсию контейнера управления, которая облегчает создание таких объектов для вас, как Castle Windsor (опять же, все в этой книге на 100 страниц выше).

В вашем случае вы просто обращаетесь к объекту User из репозитория пользователя, а затем позволяете остальным выполнить его.

Книга посвящена тому, как моделировать, скажем, ваши сущности «Пользователь» и «Продукт», используя LinqToSql с EntityRef и EntitySet, поэтому все ваши ассоциации будут приняты. Что слишком много кода, чтобы писать здесь. По сути, свяжите объект User () с вашим объектом Product () по идентификатору пользователя и украсьте каждую из этих сущностей атрибутами Linq:

[Table(Name="ut_Content")]
public class Content : EntityBase
{
  [Column(IsPrimaryKey = true
      , IsDbGenerated = true
      , AutoSync = AutoSync.OnInsert)]
  public int ContentID { get; set; }

  [Column(CanBeNull = false)]
  public string ContentKey { get; set; }

  [Column(CanBeNull = false)]
  public string Title { get; set; }

  [Column]
  public string Description { get; set; }

  [Column]
  public string Body { get; set; }

  [Column]
  public string BodyFormatted { get; set; }
}

Он рассматривает различные варианты использования LinqToSql и показывает, почему здесь работает «Сначала создайте модель, не используйте объекты Linq, сгенерированные схемой».

И, пример репозитория, я думаю, для хорошей меры:

public class ContentRepository : IContentRepository
{
  private Table<Content> _contents;

  public ContentRepository(string connectionString)
  {
    DataContext db = new DataContext(connectionString);
    _contents = db.GetTable<Content>();
  }

  public void Add(Content entity)
  {
    _contents.InsertOnSubmit(entity);
  }
}

Помните, одна из лучших особенностей Linq - это функция отложенного выполнения. Вы не работаете над всей таблицей Linq здесь. Ваш запрос откладывается, поэтому вы можете отфильтровать свой IQueryable с помощью Where и Joins и порядков заказов. Только когда вы запрашиваете методы .ToList () или аналогичные, выполняется запрос.

Использование службы DDD

Наконец, для решения вашей первоначальной проблемы «получить пользователя, а затем добавить продукт» в книге игр DDD (Domain Driven Design) есть идеальное решение: Услуги.

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

public class UserProductService
{
  private IProductRepository _productRepository;
  private IUserRepository _userRepository;
  public UserProductService()
  {
    // this is where CastleWindsor can really help
    _productRepository = new ProductRepository();
    _userRepository = new UserRepository();
  }

  public void AddProduct(int userID, Product product)
  {
    User user = userRepository.GetUserByID(userID);

    // your logic above, abstracted into a service
    if (user.IsValid && product.IsValid)
    {
      product.Owner = user.UserID;        
      _productRepository.AddProduct(product);
    }  
  }
}

Правило DDD для сервисов таково: если у вас есть два объекта, но вам нужно выполнить общее действие для обоих, используйте сервис для воздействия на оба объекта.

0 голосов
/ 31 марта 2009

Обновление: (после комментария он не является linq2sql)

В вашем конкретном сценарии (на основе вашего desc + кода, который вы разместили) я определенно перенесу нагрузку на уровень обслуживания. Это не означает перемещение запроса к нему. Я бы рефакторинг как:

string userIdStr = Request["SelectedUserId"];
var userId = !String.IsNullOrEmpty(userIdStr) ? 
     Convert.ToInt16(userIdStr) : 
     default(int?);
_productService.AddProduct(userId, newProduct);

У меня также обычно есть метод расширения, такой как T oNullable<int>(), который делает его:

var userId = Request["SelectedUserId"].ToNullable<int>();
_productService.AddProduct(userId, newProduct);

В зависимости от того, какое внимание вы уделяете моделям, у вас может быть что-то, содержащее оба поля, например CustomerProduct.


Если вы продолжаете использовать linq2sql, вам не нужно заставлять пользователя делать это в этом сценарии. Linq2sql сгенерирует свойство OwnerId, которое вы можете использовать, не обращаясь к БД, чтобы получить его.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...