Лучшие практики для удаления вызовов базы данных из классов контроллера MVC - PullRequest
1 голос
/ 24 марта 2009

У меня есть метод Action в классе ASP.NET MVC Controller, который обрабатывает посты форм с довольно простой страницы «создать / редактировать пользователя». Я новичок в MVC, поэтому я следил за примерами кода из различных руководств Microsoft, вот как метод выглядит в настоящее время:

[AcceptVerbs(HttpVerbs.Post)]
public ViewResult Save([Bind(Prefix = "ServiceUser")]ServiceUser SUser)
{
        if (SUser.ServiceUserId == 0) //new service user
            ServiceUserHelper.AddServiceUser(SUser);
        else //update to existing service user
        {
            using (ProjectDataContext db = DatabaseHelper.CreateContext())
            {
                this.UpdateModel(db.ServiceUsers.Single(su => su.ServiceUserId == SUser.ServiceUserId), "ServiceUser");
                db.SubmitChanges();
            }
        }

        //return a confirmation view
}

Это отлично работает; однако мои инстинкты говорят мне, что код ProjectDataContext ... не принадлежит контроллеру. Если бы я переместил функциональность Update в другой класс (так, как я это сделал с методом Insert), я бы потерял удобство метода ControlM UpdateLodel () и, возможно, в конечном итоге должен был бы сделать что-то довольно многословное для читать существующую сущность, обновлять ее свойства и отправлять изменения.

Итак, мой вопрос, каков наилучший способ достичь этого? Есть ли где-нибудь в LINQ метод, аналогичный UpdateModel (), который может объединить два объекта одного типа перед отправкой?

Спасибо.

Ответы [ 3 ]

5 голосов
/ 24 марта 2009

Большинство людей предложат использовать «Шаблон репозитория» для перемещения кода доступа к данным из контроллера (и для включения модульного тестирования с фиктивными объектами вместо реальной базы данных).

Вот несколько мест, где можно прочитать подробнее:

Редактировать:

Я настоятельно рекомендую прочитать всю главу о Скотте Гатри, связанную выше. В нем много полезных советов. Тем не менее, вот некоторые соответствующие примеры (за исключением главы) ...

Во-первых, мне обычно нравятся разные действия для «Обновления» и «Добавить». Даже если они представляют собой одно и то же представление для отображения формы, обычно удобнее иметь разные URL-адреса для размещения изменений по сравнению с размещением новых записей. Итак, вот как выглядит используемый шаблон репозитория в действии обновления контроллера:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues)
{
    //get the current object from the database using the repository class
    Dinner dinner = dinnerRepository.GetDinner(id);
    try
    {
        //update the object with the values submitted
        UpdateModel(dinner);
        //save the changes
        dinnerRepository.Save();
        //redirect the user back to the read-only action for what they just edited
        return RedirectToAction("Details", new { id = dinner.DinnerID });
    }
    catch
    {
        //exception occurred, probably from UpdateModel, so handle the validation errors
        // (read the full chapter to learn what this extention method is)
        ModelState.AddRuleViolations(dinner.GetRuleViolations());
        //render a view that re-shows the form with the validation rules shown
        return View(dinner);
    }
}

Вот пример "Добавить":

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create()
{
    //create a new empty object
    Dinner dinner = new Dinner();
    try
    {
        //populate it with the values submitted
        UpdateModel(dinner);
        //add it to the database
        dinnerRepository.Add(dinner);
        //save the changes
        dinnerRepository.Save();
        //redirect the user back to the read-only action for what they just added
        return RedirectToAction("Details", new { id = dinner.DinnerID });
    }
    catch
    {
        //exception occurred, probably from UpdateModel, so handle the validation errors
        // (read the full chapter to learn what this extention method is)
        ModelState.AddRuleViolations(dinner.GetRuleViolations());
        //render a view that re-shows the form with the validation rules shown
        return View(dinner);
    }
}

Для обоих приведенных выше примеров DinnerRepository выглядит следующим образом:

public class DinnerRepository
{
    private NerdDinnerDataContext db = new NerdDinnerDataContext();
    //
    // Query Methods
    public IQueryable<Dinner> FindAllDinners()
    {
        return db.Dinners;
    }
    public IQueryable<Dinner> FindUpcomingDinners()
    {
        return from dinner in db.Dinners
               where dinner.EventDate > DateTime.Now
               orderby dinner.EventDate
               select dinner;
    }
    public Dinner GetDinner(int id)
    {
        return db.Dinners.SingleOrDefault(d => d.DinnerID == id);
    }
    //
    // Insert/Delete Methods
    public void Add(Dinner dinner)
    {
        db.Dinners.InsertOnSubmit(dinner);
    }
    public void Delete(Dinner dinner)
    {
        db.RSVPs.DeleteAllOnSubmit(dinner.RSVPs);
        db.Dinners.DeleteOnSubmit(dinner);
    }
    //
    // Persistence
    public void Save()
    {
        db.SubmitChanges();
    }
}
0 голосов
/ 28 апреля 2009

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

Возможно, вы захотите перейти на 3-х или 4-х уровневую архитектуру, вставив сервисный уровень между вашими контроллерами и DAL. Таким образом, вы бы в конечном итоге:

Контроллеры -> Услуги -> DAL

4-уровневая архитектура может включать в себя уровень хранилища

Контроллеры -> Услуги -> Репозиторий -> DAL

Ваши контролеры будут нести ответственность только за 3 вещи

1) обработка аргументов 2) вызов уровня обслуживания для выполнения работы 2) поток приложения

Ваш приведенный выше пример может выглядеть примерно так:

[AcceptVerbs(HttpVerbs.Post)]
public ViewResult Save([Bind(Prefix = "ServiceUser")]ServiceUser SUser)
{
        // validate arguments
        if (SUser == null)
        {
              throw new ArgumentException("SUser can not be null");
        }

        // process form fields / query params / etc.
        this.TryUpdateModel(SUser, "ServiceUser");

        // update your model 
        // (toss in a try/catch to deal with validation errors etc)
        _userService.Save(SUser);

        //return a confirmation view
}

Тогда ваш уровень обслуживания будет отвечать за выполнение фактической работы:

  public class UserService : IUserService
   {
       public void Save(ServiceUser SUser)
       {
            // insert or update user info

            if (SUser.ServiceUserId == 0) //new service user
                ServiceUserHelper.AddServiceUser(SUser);
            else //update to existing service user
            {
                using (ProjectDataContext db = DatabaseHelper.CreateContext())
                {
                    db.ServiceUsers.Single(su => su.ServiceUserId == 
                                           SUser.ServiceUserId);
                    db.SubmitChanges();
                }
            }
       }
   }
0 голосов
/ 28 апреля 2009

Я согласен с Ли Д. Я тоже что-то искал. Я сделал аналогичную вещь с отражениями в начале превью MVC, которое использовалось в модели, а не в контроллере. Это был не лучший код и думал, что что-то будет добавлено в финал MVC. Не что-то застряло в контроллере. Было бы идеально передать форму или модель, если использовать строго типизированные представления, через контроллер в модель и позволить выполнить всю валидацию и перемещение данных там. Теперь даже плохо выполненный контроллер не может передать данные.

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