外键实体框架和Razor
本文关键字:Razor 框架 实体 | 更新日期: 2023-09-27 18:30:50
我正在使用实体框架6,MVC 5和最新的 ASP.NET Razor。
我有以下代码:
[ForeignKey("Country")]
[Display(Name = "CountryId", ResourceType = typeof(Internationalization.Resources.TenantFinanceConfiguration))]
public int CountryId { get; set; }
[Required(ErrorMessageResourceName = "Country_ValidationError", ErrorMessageResourceType = typeof(Internationalization.Resources.TenantFinanceConfiguration))]
[Display(Name = "Country", ResourceType = typeof(Internationalization.Resources.TenantFinanceConfiguration))]
public virtual Country Country { get; set; }
[ForeignKey("Currency")]
[Display(Name = "CurrencyId", ResourceType = typeof(Internationalization.Resources.TenantFinanceConfiguration))]
public int CurrencyId { get; set; }
[Required(ErrorMessageResourceName = "Currency_ValidationError", ErrorMessageResourceType = typeof(Internationalization.Resources.TenantFinanceConfiguration))]
[Display(Name = "Currency", ResourceType = typeof(Internationalization.Resources.TenantFinanceConfiguration))]
public virtual Currency Currency { get; set; }
无论我把外键放在哪里,表都是正确创建的;但是当我创建视图时:
<div class="form-group">
@Html.LabelFor(model => model.CountryId, "CountryId", htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownList("CountryId", null, htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.CountryId, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.CurrencyId, "CurrencyId", htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownList("CurrencyId", null, htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.CurrencyId, "", new { @class = "text-danger" })
</div>
</div>
我收到错误:
具有键"CountryId"的 ViewData 项的类型为"System.Int32",但必须为"IEnumerable"类型。
我错过了什么,这肯定应该有效,因为我不需要 IEnumerable 进行一对一的关系?
任何帮助都非常感谢。
The ViewData item that has the key 'CountryId' is of type 'System.Int32' but must be of type 'IEnumerable'.
[ForeignKey("Currency")]
[Display(Name = "CurrencyId", ResourceType = typeof(Internationalization.Resources.TenantFinanceConfiguration))]
public int CurrencyId { get; set; }
由于您在此处定义 int CurrencyID,因此您将获得 int 类型。如果你想要 int 的 Enumberable,那么你必须声明类似的东西
public IEnumerable<int> CurrencyId { get; set; }
当您需要下拉列表时,通常的做法是为您的视图使用另一个类(类似于视图模型)。该类还将具有下拉列表的所有可能值。(您也可以在模型类中使用它们并将属性标记为[NotMapped]
)
public class YourViewModel : YourModel
{
public IEnumerable<int> CurrencyIds { get; set; }
}
然后在控制器中填充CurrencyIds
,稍后在视图中使用:
@Html.DropDownListFor(model => model.CurrencyId, new SelectList(Model.CurrencyIds ))
只有这样才会显示带有 ID 的下拉列表。用户实际上可能对货币名称或其他属性感兴趣。因此:
public class YourViewModel : YourModel
{
public IEnumerable<Currency> Currencies { get; set; }
}
视图:
@Html.DropDownListFor(x => x.CurrencyId, new SelectList(Model.Currencies, "CurrencyId", "DisplayName", Model.CurrencyId))
(注意,我在SelectList
直接绑定到@Model)
谢谢大家的反馈。根据Microsoft文档,我只需要指定 [ForeignKey] 属性和虚拟属性。 这当然是我过去通过实体框架代码优先实现这种一对一关系的方式。 我一直在阅读大量关于一对一关系的文章,甚至下载示例代码,它们都可以工作,这似乎是我的解决方案中的东西,但我无法理解是什么。 我下载了一个 Contoso 大学示例,他们将那里的关系指定为:
public int DepartmentID { get; set; }
public virtual Department Department { get; set; }
并且有视图
<div class="form-group">
<label class="control-label col-md-2" for="DepartmentID">Department</label>
<div class="col-md-10">
@Html.DropDownList("DepartmentID", null, htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.DepartmentID, "", new { @class = "text-danger" })
</div>
</div>
这一切都正确连接。
如果您没有在控制器方法中提供ViewBag
,也没有在模型中提供List<SelectListItem>
或SelectList
,和/或如果您没有在DropDownListFor
中指定它,就会发生这种事情,因为它在您的代码中没有。 错误告诉您您有这个CountryId
,这是一个int
,但它希望您用项目集合填充下拉列表,而不是int
。 它将尝试查找名称为 CountryId
的ViewBag
,因为当您的 View 代码中没有指定集合时,它将默认尝试这样做,如果集合不存在,则无法继续,因为这样它只会看到CountryId
引用您尝试将下拉列表的 ID 值设置为的整数值。
我发现,避免出现问题的最好方法是添加:
public SelectListItem[] CountriesList;
public SelectListItem[] CurrenciesList;
然后,在控制器中相应地添加项到视图模型:
public ActionResult Index()
{
MyViewModel mvm = new MyViewModel();
mvm = Initialize(mvm);
return View(mvm);
}
public MyViewModel Initialize(MyViewModel mvm)
{
if (_CurrenciesList == null)
mvm.CurrenciesList = GetCurrencyList();
else
mvm.CurrenciesList = _CurrenciesList;
if (_CountriesList == null)
mvm.CountriesList = Get CountriesList();
else
mvm.CountriesList = _CountriesList;
return mvm;
}
private static SelectListItem[] _CurrenciesList;
private static SelectListItem[] _CountriesList;
/// <summary>
/// Returns a static category list that is cached
/// </summary>
/// <returns></returns>
public SelectListItem[] GetCurrenciesList()
{
if (_CurrenciesList == null)
{
var currencies = repository.GetAllCurrencies().Select(a => new SelectListItem()
{
Text = a.Name,
Value = a.CurrencyId.ToString()
}).ToList();
currencies.Insert(0, new SelectListItem() { Value = "0", Text = "-- Please select your currency --" });
_CurrenciesList = currencies.ToArray();
}
// Have to create new instances via projection
// to avoid ModelBinding updates to affect this
// globally
return _CurrenciesList
.Select(d => new SelectListItem()
{
Value = d.Value,
Text = d.Text
})
.ToArray();
}
public SelectListItem[] GetCountriesList()
{
if (_CountriesList == null)
{
var countries = repository.GetAllCountries().Select(a => new SelectListItem()
{
Text = a.Name,
Value = a.CountryId.ToString()
}).ToList();
countries.Insert(0, new SelectListItem() { Value = "0", Text = "-- Please select your country --" });
_CountriesList = countries.ToArray();
}
// Have to create new instances via projection
// to avoid ModelBinding updates to affect this
// globally
return _CountriesList
.Select(d => new SelectListItem()
{
Value = d.Value,
Text = d.Text
})
.ToArray();
}
注意 我使用"初始化"函数来填充视图模型,因此每当需要用下拉值填充视图模型时,都可以轻松完成。
我有一个单独的"存储库"类,其中我有从表中获取List<T>
枚举的函数。 在本例中,它看起来像这样:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using YourSite.Models;
using YourSite.ViewModels;
namespace YourSite
{
public class Repository
{
Model1 db = new Model1(); // this is your DB Context
// Currencies
public List<Currencies> GetAllCurrencies()
{
return db.Currencies.OrderBy(e => e.Name).ToList();
}
// Countries
public List<Countries> GetAllCountries()
{
return db.Countries.OrderBy(e => e.Name).ToList();
}
}
}
它允许您执行任何排序、排除、操作 - 基本上是在返回表数据以供使用之前需要对表数据执行的任何操作。
在模型中,要设置外键,您应该以与实际相反的方式使用它。 ForeignKey
属性将当前 ViewModel 中的字段映射到您正在访问的表的主键,即当前 ViewModel 表的CountryId
到Country
表上的CountryId
,因此 [ForeignKey("CountryId")]
属性位于该Country
表的虚拟实例之上。 DisplayName
属性可用于代替Display(Name="...", ...)]
:
[DisplayName("Country")]
[Required(ErrorMessage = "Country is required")]
public int CountryId { get; set; }
[ForeignKey("CountryId")]
public virtual Country Country { get; set; }
[DisplayName("Currency")]
[Required(ErrorMessage = "Currency is required")]
public int CurrencyId { get; set; }
[ForeignKey("CurrencyId")]
public virtual Currency Currency { get; set; }
我们使用DisplayName
是因为我们想,例如,有一个LabelFor(model => model.CountryId)
并让它显示 Country
,而不是 CountryId
. 我们永远不会显示虚拟Countries
表,因此没有必要对此进行DisplayName
。 验证错误也应高于Id
字段,并包含要给出的实际错误(如上所示)。 作为旁注:模型验证可以在控制器函数中使用以下行来制定(如果还没有):
ValidationContext vc = new ValidationContext(sta);
ICollection<ValidationResult> results = new List<ValidationResult>(); // results of the validation
bool isValid = Validator.TryValidateObject(mvm, vc, results, true); // Validates the object and its properties
int newId = 0;
if (isValid)
{
// Save MVM
newId = repository.SaveMVM(mvm); // function should save record and return ID
return View("Edit", new { mvm.Id = newId });
}
else
RedirectToAction("Edit", mvm);
在视图上,下拉代码如下所示:
<div class="form-group">
@Html.LabelFor(model => model.CurrencyId, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(
model => model.CurrencyId, // Specifies the selected CountryId
//(IEnumerable<SelectListItem>)ViewData["CurrencyId"],
Model.CurrenciesList, // supply the pre-created collection of SelectListItems
htmlAttributes: new { @class = "form-control" }
)
@Html.ValidationMessageFor(model => model.CurrencyId, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.CountryId, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(
model => model.CountryId, // Specifies the selected CountryId
//(IEnumerable<SelectListItem>)ViewData["CountryId"],
Model.CountriesList, // supply the pre-created collection of SelectListItems
htmlAttributes: new { @class = "form-control" }
)
@Html.ValidationMessageFor(model => model.CountryId, "", new { @class = "text-danger" })
</div>
</div>
如果你打算使用 (IEnumerable)<SelectListItem>ViewData["CountryId"]
,例如,不使用 Initialize()
函数,您可以使用 ViewBag
:在返回 View 之前在 Index()
方法中添加ViewBag.CountryId = GetCountriesList();
。 您也可以改用ViewData["CountryId"] = GetCountriesList();
- 它们是可互换的。 不过,我并没有成功地将值绑定到DropDownListFor
对象,所以这就是为什么我更喜欢使用模型对象(public SelectListItem[] CountriesList; public SelectListItem[] CurrenciesList;
),但我想我会展示将选项添加到下拉列表中的各种方法。