【问题标题】:MVC Models, Entity Framework and binding dropdowns best practiceMVC 模型、实体框架和绑定下拉列表最佳实践
【发布时间】:2016-05-26 22:06:25
【问题描述】:

关于绑定下拉列表时 MVC 最佳实践的非常基本的问题。

这是一个真实世界的例子,但是一个解释我的问题的基本例子:

采取以下模型

public class Person
{
    public int ID { get; set; }     
    public string Name { get; set; }
    public virtual Car Car { get; set; }
}

public class Car
{
    public int ID {get;set;}
    public string Make {get; set;{}
    public string Model {get; set;}
}

然后假设这些被展平成一个视图模型:

public class IndexViewModel
{
    public int PersonID;
    public string Name;
    public int SelectedCarID;
    public SelectList<Cars> Cars;
}

在我的构造函数中,我有一个索引方法:

[HttpGet]
public ActionResult Index()
{
    var person = _ctx.People.FirstOrDefault(x=>x.ID == 1);

    var vm = new IndexViewModel(){
        Name = person.Name,
        SelectedCarID = person.Car.ID,
    };

    return View(vm);
}

现在,假设从上下文返回的人在页面首次加载时没有汽车记录。 视图有一行:

@Html.DropDownListFor(m=>m.SelectedCarID, Model.Cars)

当表单被提交时,它被动作拾取:

[HttpPost]
public ActionResult Index(IndexViewModel model)
{
    var person = _ctx.People.FirstOrDefault(x=>x.ID == model.PersonID);

    var car = _ctx.Cars.FirstOrDefault(x=>x.ID == model.SelectedCarID);

    person.Name = model.name;
    person.Car = car;

    _ctx.SaveChanges();
}

现在这就是我多年来一直这样做的方式,当 LINQ to SQL 起飞时,我开始使用 EF,并且我一直创建这样的模型,因为我认为这是推荐的方式。 今天与另一位开发人员讨论后,我不确定这是否仍然是最好的方法?一直让我很恼火的是,我需要对数据库进行查找以获取 Car 记录,以便我可以更新记录。

我的问题是:

  1. 实现上述目标的最佳方式是什么?
  2. 以上是否正确?
  3. 有没有更好的方法来更新汽车实体而不进行查找(最好不包括模型中的外键)?
  4. 只在模型中包含 FK 是否更好(这不是我一直在做的方式,它似乎更明智)?
  5. 有没有办法将下拉菜单绑定到汽车对象(我与之交谈的那个人似乎建议你可以,但我对 MVC/asp.net 的了解和疯狂的谷歌搜索似乎表明你不能)?

【问题讨论】:

  • 与问题无关,但我想指出,在现实世界的应用程序中,上述某些应用程序将使用自动映射器,而我不会像这样在控制器中引用上下文 - 我一直是多年的开发人员 - 我确实(或多或少)知道我在做什么。有时质疑我们做事的方式并听取其他人的意见是件好事,这就是我在这里想要做的!我们在一个学习永远不会停止的职业中工作,在这种情况下,我非常感谢社区的投入。提前致谢。

标签: c# asp.net-mvc entity-framework model asp.net-mvc-5


【解决方案1】:

这真的不适合最佳实践之类的问题(可能是Code Review)。

不过最初有一些注意事项。

将您的域对象保留在域中

让我印象深刻的第一件事是SelectList&lt;Car&gt; 属性。它显示为您的Car 实体的地方实际上是一个域实体。出于多种原因,不应将域实体暴露给 UI。

  1. 实体框架代理类监控可能无意保存的属性更改。
  2. 重构域实体需要重构 UI 代码。
  3. 域实体通常会联系您不希望公开或不希望公开的属性。
  4. 域实体的序列化也会序列化导航属性并(很可能)导致循环引用错误。

你的问题

鉴于以上您知道答案,您将必须根据您的视图模型中的条件查找实体。您的视图模型不应该对数据上下文有任何了解。它实际上是 View Model 而不是 Domain Entity。通过告诉您的 View Model 与您的数据上下文交互,您的 Data Access 层和您的 Presentation 层之间没有分离。

不要让您的控制器也管理数据访问

您的控制器有很多工作要做,管理数据访问不应该是其中之一。这样做你已经将你的表示层与你的数据访问层相耦合。现在,作为一个示例,它很容易原谅,但是重构您的数据访问层将对您的表示层产生直接影响。我建议在数据访问层和表示层之间放置一个服务层。

好的,这一切在实践中看起来如何。

这是我个人的方法,但将着眼于将数据层与表示层分离,没有域对象传递到表示层,并使用服务将事务代理到数据层。

示例服务

该服务负责处理数据层和表示(注意模拟存储库)之间的交互。

public class SampleService
{
    public SampleService()
    {
        _dbContext = new SampleContext();
    }

    readonly SampleContext _dbContext;

    public virtual Person GetPersonById(int id)
    {
        return _dbContext.Persons.FirstOrDefault(x => x.ID == id);
    }

    public virtual Car GetCarById(int id)
    {
        return _dbContext.Cars.FirstOrDefault(x => x.ID == id);
    }

    public virtual IList<Car> GetAllCars()
    {
        return _dbContext.Cars.ToList();
    }

    public virtual void UpdatePerson(Person person)
    {
        if (person == null)
            throw new ArgumentNullException(nameof(person));

        _dbContext.SaveChanges();
    }

    public virtual void UpdateCar(Car car)
    {
        if (car == null)
            throw new ArgumentNullException(nameof(car));

        _dbContext.SaveChanges();
    }
}

这似乎是更多的工作,绝对可以,但现在实施您的服务比以后再实施要好。如果我们希望更改任何查询或交互方法,我们还可以更新一个位置。

索引视图模型

正如我们所同意的,我们不再将汽车对象传递给SelectList。事实上,我们只需要构造一个基本的IList&lt;SelectListItem&gt; 并从我们的控制器中填充它。

public class IndexViewModel
{
    public IndexViewModel()
    {
        AvailableCars = new List<SelectListItem>();
    }

    public int PersonID { get; set; }

    public string Name { get; set; }

    public int SelectedCarId { get; set; }

    public IList<SelectListItem> AvailableCars { get; set; }
}

控制器

现在我们的控制器连接起来非常简单。

[HttpGet]
public ActionResult Index()
{
    var person = sampleService.GetPersonById(1);
    var model = new IndexViewModel
    {
        Name = person.Name,
        PersonID = person.ID,
        SelectedCarId = person.Car.ID
    };

    model.AvailableCars = sampleService.GetAllCars()
                                        .Select(car => new SelectListItem
                                        {
                                            Text = $"{car.Make} - {car.Model}",
                                            Value = car.ID.ToString()
                                        })
                                        .OrderBy(sli => sli.Text)
                                        .ToList();
    return View(model);
}

[HttpPost]
public ActionResult Index(IndexViewModel model)
{
    var person = sampleService.GetPersonById(model.PersonID);
    if(person != null)
    {
        person.Name = model.Name;

        //only update the person car if required.
        if(person.Car == null || person.Car.ID != model.SelectedCarId)
        {
            var car = sampleService.GetCarById(model.SelectedCarId);
            if (car != null)
                person.Car = car;
        }

        sampleService.UpdatePerson(person);
    }
    return View();
}

查看下拉列表

@Html.DropDownListFor(m => m.SelectedCarId, Model.AvailableCars)

如果您将您的代码与我的代码进行比较,我实际上已经向解决方案中添加了更多代码,但是消除了许多在大型应用程序中可能难以管理的耦合和依赖关系。

现在回到你最初的问题。

  1. 有没有更好的方法来更新汽车实体而不进行查找(最好不包括外键 在模型中)?

不,您应该在模型之外查找该实体(汽车)。模型不应该知道数据上下文。

  1. 只在模型中包含 FK 是否更好(这不是我一直在做的方式,它似乎更明智)?

不,您的模型不应该知道数据上下文,因此您不需要定义外键(在数据上下文的意义上)将其留给您的控制器和服务。

  1. 有没有办法将下拉菜单绑定到汽车对象(与我交谈的那个人似乎建议你可以,但我对 MVC/asp.net 的了解 和激烈的谷歌搜索似乎表明你不能)?

你可以,但你不想。我们的Car 实体是一个域实体,我们不想将实体暴露给 UI(演示)。相反,我们将使用其他类来公开绑定的属性。在这个例子中,一个简单的IList&lt;SelectListItem&gt; 就足够了。

【讨论】:

  • 同意@Nico 曾经按照你的方式做,但是一旦我开始使用服务层来处理所有从未返回的事情,只会让任何未来的更改/更新更容易,尤其是如果你最终在很多地方使用您的实体
  • 感谢您的评论。
  • 您好,感谢 cmets,您标记为不好的做法的大部分要点都归结为我将一个示例放在一起 - 在适当的应用程序中,会有一个单独的 CarVM 被引用而且我确实有一个服务层 - 只是想让事情变得简单。关于您的服务的一条评论-您有一个上下文实例-您会为每个服务提供一个上下文吗?通常我使用 Castle 将相同的上下文实例注入所有服务。
  • “不,你的模型不应该知道数据上下文,因此你不需要定义外键(在数据上下文的意义上)把它留给你的控制器和服务”——我认为问题是 FK 是否应该包含在数据模型中,允许通过 person.CarID = ViewModel.SelectedCarID 设置 CAR;这将保存对数据库的查找以检索汽车只是为了更新人员记录(这是问题中的要点之一) - 这也是我做的事情,我没有将 FK 放在数据模型中但是有时会认为这会更容易......意见?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-14
  • 2010-11-11
  • 2017-12-01
  • 1970-01-01
相关资源
最近更新 更多