绑定下拉列表时关于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记录,以便我可以更新记录。
我的问题是:
答案 0 :(得分:2)
这确实是最佳实践问题的地方(可能是Code Review)。
但最初有些注意事项。
将您的域对象保留在域中
对我来说第一件事就是SelectList<Car>
财产。显示为Car
实体实际上是域实体的位置。由于多种原因,不应将域实体公开给用户界面。
鉴于上述情况,您知道有答案,您必须根据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();
}
}
这似乎是更多的工作,现在绝对比现在更好地实现您的服务。如果我们希望更改任何查询或交互方法,我们还可以实现更新的位置。
正如我们已经同意的那样,我们不再将汽车对象传递给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)
如果你将你的代码与我的代码进行比较,我实际上已经为解决方案添加了更多的代码,但是删除了很多在大型应用程序中难以管理的耦合和依赖。
现在回到原来的问题。
- 是否有更好的方法可以在不进行查找的情况下更新汽车实体(最好不包括外键) 在模型中)?
醇>
不,您应该在模型之外对该实体(汽车)进行查找。模型不应该知道数据上下文。
- 将FK包含在模型中是否更好(这不是我一直在做的事情似乎更合理)?
醇>
不,您的模型不应该知道数据上下文,因此您不需要定义外键(在数据上下文意义上)将其留给您的控制器和服务。
- 有没有办法将下拉绑定到汽车对象(我采访过的人似乎建议你可以,但我知道MVC / asp.net 并且愤怒的谷歌搜索似乎表明你不能??
醇>
你可以,但你不想。我们的Car
实体是一个域实体,我们不想将实体暴露给UI(Presentation)。相反,我们将使用其他类来公开绑定的属性。在这个例子中,一个简单的IList<SelectListItem>
就足够了。