外键实体框架和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 进行一对一的关系?

任何帮助都非常感谢。

外键实体框架和Razor

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。 它将尝试查找名称为 CountryIdViewBag,因为当您的 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 表的CountryIdCountry表上的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;),但我想我会展示将选项添加到下拉列表中的各种方法。