MVC模型,实体框架和绑定下拉列表最佳实践

时间:2016-05-26 22:06:25

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

绑定下拉列表时关于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和愤怒的谷歌搜索的知识似乎表明你不能)?

1 个答案:

答案 0 :(得分:2)

这确实是最佳实践问题的地方(可能是Code Review)。

但最初有些注意事项。

将您的域对象保留在域中

对我来说第一件事就是SelectList<Car>财产。显示为Car实体实际上是域实体的位置。由于多种原因,不应将域实体公开给用户界面。

  1. 实体框架代理类监视可能无意中保存的属性的更改。
  2. 重新分析域实体需要重新分解UI代码。
  3. 域实体通常会联系您不喜欢的属性或其他方式。
  4. 域实体的序列化还将序列化导航属性,并且(很可能)导致循环引用错误。
  5. 您的问题

    鉴于上述情况,您知道有答案,您必须根据View模型中的条件查找实体。您的视图模型不应该对数据上下文有任何了解。它实际上是View Model而不是Domain Entity。通过告诉您的View模型与您的数据上下文进行交互,您的数据访问层与 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();
        }
    }
    

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

    IndexViewModel

    正如我们已经同意的那样,我们不再将汽车对象传递给SelectList。事实上,我们只需要构建一个基本的IList<SelectListItem>并从我们的控制器中填充它。

    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. 是否有更好的方法可以在不进行查找的情况下更新汽车实体(最好不包括外键)   在模型中)?
    2.   

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

      
        
    1. 将FK包含在模型中是否更好(这不是我一直在做的事情似乎更合理)?
    2.   

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

      
        
    1. 有没有办法将下拉绑定到汽车对象(我采访过的人似乎建议你可以,但我知道MVC / asp.net   并且愤怒的谷歌搜索似乎表明你不能??
    2.   

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