繁体   English   中英

外键实体框架和剃刀

[英]Foreign Key Entity Framework & Razor

我正在使用Entity Framework 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; }

如果需要下拉菜单,通常的做法是为View使用另一个类(类似于ViewModel)。 该类还将具有下拉菜单的所有可能值。 (您也可以在Model类中使用它们,并将属性标记为[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]属性和虚拟属性。 当然,这是我过去使用Entity Framework Code First实现这种一对一关系的方式。 我已经阅读了大量关于一对一关系的文章,甚至下载了示例代码,它们都可以正常工作,这似乎是我的解决方案中的一部分,但我不明白是什么。 我下载了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指定它,因为您的代码中没有该ViewBag ,则可能会发生这种情况。 错误告诉您您有一个CountryId int ,它是一个int ,但是希望您用项目集合而不是int来填充下拉列表。 它会试图找到ViewBag名为CountryId ,因为它会默认尝试做时,有没有在你的浏览代码指定的集合,如果集合不存在它无法进行,因为那只能看到的是CountryId指的是您要设置下拉菜单的ID值的整数值。

我发现,避免问题的最佳方法是添加:

    public SelectListItem[] CountriesList;
    public SelectListItem[] CurrenciesList;

到ViewModel,然后在控制器中相应地添加项目:

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();
}

注意,我使用“ Initialize”函数来填充ViewModel,因此,只要ViewModel需要用下拉值填充,就可以轻松完成。

我有一个单独的“存储库”类,在其中我具有从表中获取可枚举的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属性映射你在当前视图模型您正在访问的表的主键有领域,即CountryId您当前视图模型的表到的CountryId上的Country表,所以[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"] ,则可以使用ViewBag代替使用Initialize()函数:添加ViewBag.CountryId = GetCountriesList(); 在返回View之前,请使用Index()方法。 您也可以使用ViewData["CountryId"] = GetCountriesList(); 相反-这些是可以互换的。 但是,我并没有获得成功将值绑定到DropDownListFor对象的任何一个,因此,这就是为什么我更喜欢使用模型对象( public SelectListItem[] CountriesList; public SelectListItem[] CurrenciesList; )的原因,但是,我以为我会展示在下拉菜单中添加选项的各种方式。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM