我有以下型号:
public class Team
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
[Required]
public string Owner { get; set; }
public int TransfersRemaining { get; set; }
public DateTime DateCreated { get; set; }
public IEnumerable<Player> StartingXI { get; set; }
public IEnumerable<Player> Subs { get; set; }
public Team() {
TransfersRemaining = 5;
StartingXI = Enumerable.Empty<Player>();
Subs = Enumerable.Empty<Player>();
DateCreated = DateTime.Now;
}
public Team(String teamName, String userId)
{
Name = teamName;
TransfersRemaining = 5;
Owner = userId;
StartingXI = Enumerable.Empty<Player>();
Subs = Enumerable.Empty<Player>();
DateCreated = DateTime.Now;
}
public int getTransfersRemaining() {
return TransfersRemaining;
}
public string getTeamName()
{
return Name;
}
}
我有以下ActionResult:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name, Owner")] Team team)
{
team.Owner = "bob";
if (ModelState.IsValid)
{
teamRepository.Insert(team);
teamRepository.Save();
return RedirectToAction("Index");
}
foreach (ModelState modelState in ViewData.ModelState.Values)
{
foreach (ModelError error in modelState.Errors)
{
Response.Write(error);
}
}
return View(team);
}
在调试时,我看到错误是 &#34;所有者字段是必需的。&#34;
当然,我已经向team.Owner提供了这一点 检查我的团队对象,我可以看到所有者=&#34; bob&#34;并且所有其他属性都已正确设置。
任何想法是什么问题?
答案 0 :(得分:3)
ModelState.IsValid
基于ModelState
的内容,而不是您的实体实例。因此,在您的操作中首次设置team.Owner
并不能否定它没有发布,因此在ModelState
中不存在。
但是,您还需要解决许多重要问题。首先,通过使用Bind
并直接保存由模型绑定器创建的实体实例的组合,您将会烧掉任何未发布的数据。 TransfersRemaining
和CreatedDate
都将设置为各自的默认值。同样地,将清空两个枚举StartingXI
和Subs
,这将导致在数据库中删除这些关系。此外,由于未发布Id
,因此它将是int 0
的默认值,这将导致Entity Framework创建新记录,而不是更新现有记录。
这些问题都不一定是初始创建的问题,但只要您将其应用到编辑中,您就会将所有数据搞砸。你应该坚持不要永远使用Bind
。太可怕了。同样严肃,不要。微软在解决直接与实体合作的过度问题方面的愚蠢尝试可能导致,但它导致的问题多于解决的问题。此外,还有一个更好,更简单的解决方案:不使用该实体。而是创建一个视图模型,该模型仅包含您希望用户能够编辑的属性,然后将这些已发布的值映射到您的实体实例。这样,用户无法操纵帖子数据,因为您可以明确控制从帖子中保存和不保存的内容。 (有关Bind
为何的原因的其他说明,请查看:https://cpratt.co/bind-is-evil/。)
接下来,我假设您希望在此处使用您的枚举与Player
建立实际关系:例如,您希望该关系在数据库中持续存在。这不会发生在IEnumerable
上。您需要使用ICollection
代替:
public ICollection<Player> StartingXI { get; set; }
public ICollection<Player> Subs { get; set; }
此外,它不是必需的,有些人可能认为这不是一个好主意,但如果您打算使用延迟加载,这些属性将需要virtual
关键字。它是不允许延迟加载的完全有效甚至建议的选择,但您必须知道这些集合将为null,除非您在需要时急切地或明确地加载它们。这往往会让人兴奋,所以关注你在这里所做的事并理解其优缺点是很好的。
最后,关于班级设计的一些注意事项:
您的两个构造函数中存在重复的逻辑。通过重载来处理它会好得多:
public Team()
: this(null, null)
{
}
public Team(String teamName, String userId)
{
Name = teamName;
TransfersRemaining = 5;
Owner = userId;
StartingXI = Enumerable.Empty<Player>();
Subs = Enumerable.Empty<Player>();
DateCreated = DateTime.Now;
}
但是,构造函数不是设置默认值的位置。重要的是,这将在此类实例化时运行,包括实体框架在查询时创建实例的时间。例如,如果要从数据库中提取一堆现有团队,则所有这些属性都将重置为这些默认值,而不是数据库中记录的值。如果你想要默认值,你需要使用自定义的getter和setter或C#6的初始化语法:
C#6 +
public DateTime DateCreated { get; set; } = DateTime.Now;
以前的C#
private DateTime? dateCreated;
public DateTime DateCreated
{
get { return dateCreated ?? DateTime.Now }
set { dateCreated = value; }
}
列表和其他复杂类型有点困难,因为如果它们为null,您只想实例化它们。 C#6初始化程序语法在此处相同,但对于以前版本的C#,您应该使用:
private IEnumerable<Player> subs;
public IEnumerable<Player> Subs
{
get
{
if (subs == null)
{
subs = new List<Player>();
}
return subs;
}
set { subs = value; }
}
虽然它很小,但getTransfersRemaining
和getTeamName
之类的方法仅返回属性值是多余的,只会为您的代码添加熵。如果您正在关注TDD,那么这些是您必须维护测试的内容,而该属性本身不需要测试。这些属性本身就是您实体的公共API的一部分;你不需要任何其他东西。如果你这样做是为了满足界面,那实际上违反了SOLID中的 I ,接口隔离原则:
永远不应该强迫客户实现它不使用的界面,或者不应该强迫客户依赖他们不使用的方法。