简体   繁体   English

外键实体框架和剃刀

[英]Foreign Key Entity Framework & Razor

I am using Entity Framework 6, MVC 5 and the latest ASP.NET Razor. 我正在使用Entity Framework 6,MVC 5和最新的ASP.NET Razor。

I have the following code : 我有以下代码:

[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; }

Irrespective of where I place the foreign key, the tables are created correctly; 无论我将外键放在何处,表都将正确创建; but when I create the views: 但是当我创建视图时:

<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>

I get the error : 我得到错误:

The ViewData item that has the key 'CountryId' is of type 'System.Int32' but must be of type 'IEnumerable'. 关键字为“ CountryId”的ViewData项的类型为“ System.Int32”,但必须类型为“ IEnumerable”。

What I am missing, surely this should work as I do not require IEnumerable for a one-to-one relationship? 我想念的是什么,因为我不需要IEnumerable一对一的关系,这肯定可以工作吗?

Any assistance greatly appreciated. 任何帮助,不胜感激。

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

Since you are defining int CurrencyID here, you will get int type. 由于您在此处定义int CurrencyID,因此您将获得int类型。 If you want Enumberable of int then you must declare something like 如果您想要int的Enumberable,则必须声明类似

 public IEnumerable<int> CurrencyId { get; set; }

When you want a dropdown, common practice is to use another class for your View (something like a ViewModel). 如果需要下拉菜单,通常的做法是为View使用另一个类(类似于ViewModel)。 That class will also have all possible values for dropdown. 该类还将具有下拉菜单的所有可能值。 (You can also use them in your Model class and mark property as [NotMapped] ) (您也可以在Model类中使用它们,并将属性标记为[NotMapped]

public class YourViewModel : YourModel
{
    public IEnumerable<int> CurrencyIds { get; set; }
}

Then populate CurrencyIds in controller, and later use in your view: 然后在控制器中填充CurrencyIds ,稍后在您的视图中使用:

@Html.DropDownListFor(model => model.CurrencyId, new SelectList(Model.CurrencyIds ))

Only this will display dropdown with IDs. 只有这将显示带有ID的下拉列表。 User may actually be interested in currency name or another property. 用户可能实际上对货币名称或其他属性感兴趣。 Therefore: 因此:

public class YourViewModel : YourModel
{
    public IEnumerable<Currency> Currencies { get; set; }
}

View: 视图:

@Html.DropDownListFor(x => x.CurrencyId, new SelectList(Model.Currencies, "CurrencyId", "DisplayName", Model.CurrencyId))

(notice, that I bound directly to @Model in SelectList ) (注意,我直接绑定到SelectList @Model)

Thank you all for your feedback. 谢谢大家的反馈。 According to the Microsoft Documentation I need only specify a [ForeignKey] Attribute and virtual property. 根据Microsoft文档,我只需要指定[ForeignKey]属性和虚拟属性。 This is certainly how I have achieved this one-to-one relationship in the past with Entity Framework Code First. 当然,这是我过去使用Entity Framework Code First实现这种一对一关系的方式。 I have been reading a large number of articles on one-to-one relationship and even download sample code and they all work, it appears to be something in my solution but I cannot understand what. 我已经阅读了大量关于一对一关系的文章,甚至下载了示例代码,它们都可以正常工作,这似乎是我的解决方案中的一部分,但我不明白是什么。 I downloaded a Contoso University example and they have specified there relationship as : 我下载了Contoso大学的示例,他们将那里的关系指定为:

    public int DepartmentID { get; set; }

    public virtual Department Department { get; set; }

and there view has 那里有

<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>

And it all wires up correctly. 而且所有连线都正确连接。

This sort of thing can happen if you do not provide a ViewBag in your controller method or a List<SelectListItem> or SelectList in your model and/or if you do not specify it in your DropDownListFor , as it does not have there in your code. 如果您没有在控制器方法中提供ViewBag或在模型中未提供List<SelectListItem>SelectList ,并且/或者如果未在DropDownListFor指定它,因为您的代码中没有该ViewBag ,则可能会发生这种情况。 The error is telling you that you have this CountryId , which is an int , but it's expecting you to fill your dropdown list with a collection of items, not with an int . 错误告诉您您有一个CountryId int ,它是一个int ,但是希望您用项目集合而不是int来填充下拉列表。 It will try to find a ViewBag with the name CountryId , as it will default to try to do when there is no collection specified in your View code, and it can't proceed if the collection is not there, because then it only sees that CountryId is referring to an integer value to which you are trying to set the dropdown's ID value. 它会试图找到ViewBag名为CountryId ,因为它会默认尝试做时,有没有在你的浏览代码指定的集合,如果集合不存在它无法进行,因为那只能看到的是CountryId指的是您要设置下拉菜单的ID值的整数值。

The best way to avoid problems, I found, was to add: 我发现,避免问题的最佳方法是添加:

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

to the ViewModel, then, in the controller, you add items accordingly: 到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();
}

Note I use an "Initialize" function to fill the ViewModel, so that anytime the ViewModel needs to be filled with dropdown values, it can easily be done. 注意,我使用“ Initialize”函数来填充ViewModel,因此,只要ViewModel需要用下拉值填充,就可以轻松完成。

I have a separate "Repository" class, where I have functions for getting a List<T> of enumerables from the tables. 我有一个单独的“存储库”类,在其中我具有从表中获取可枚举的List<T>的功能。 In this case, it would look like this: 在这种情况下,它看起来像这样:

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

It allows you to do any ordering, exclusion, manipulation - basically whatever you need to do on the table data before returning it for use. 它允许您执行任何排序,排除,操作-基本上,您需要在返回表数据之前对表数据进行任何处理。

In the model, to set up your Foreign Key, you should have it the opposite way than you do. 在模型中,要设置外键,您应该使用与您相反的方式。 The ForeignKey attribute maps the field you have in your current ViewModel to the primary key of the table you are accessing, ie the CountryId of your current ViewModel's table to the CountryId on the Country table, so the [ForeignKey("CountryId")] attribute goes over top the virtual instance of that Country table. ForeignKey属性映射你在当前视图模型您正在访问的表的主键有领域,即CountryId您当前视图模型的表到的CountryId上的Country表,所以[ForeignKey("CountryId")]属性越过该Country表的虚拟实例。 The DisplayName attribute can be used in place of Display(Name="...", ...)] : 可以使用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; }

We use DisplayName because we want to, for example, have a LabelFor(model => model.CountryId) and have it show Country , not CountryId . 我们使用DisplayName是因为,例如,我们想要有一个LabelFor(model => model.CountryId)并显示Country而不是CountryId We won't ever be displaying the virtual Countries table, so having a DisplayName over that isn't necessary. 我们将永远不会显示虚拟Countries表,因此没有必要使用DisplayName The validation errors should go above the Id fields, as well, and contain the actual errors to give (as shown above). 验证错误也应位于Id字段上方,并包含要提供的实际错误(如上所示)。 As a side note: Model validation can be enacted with the following lines in a controller function if you didn't already have it: 附带说明:如果尚未进行模型验证,则可以在控制器函数中使用以下代码行:

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

On the view, the dropdown code would look like this: 在视图上,下拉代码如下所示:

<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>

If you were going to use the (IEnumerable)<SelectListItem>ViewData["CountryId"] , for example, instead of using the Initialize() function, you use a ViewBag : adding ViewBag.CountryId = GetCountriesList(); 例如,如果要使用(IEnumerable)<SelectListItem>ViewData["CountryId"] ,则可以使用ViewBag代替使用Initialize()函数:添加ViewBag.CountryId = GetCountriesList(); in the Index() method before returning the View. 在返回View之前,请使用Index()方法。 You could also use ViewData["CountryId"] = GetCountriesList(); 您也可以使用ViewData["CountryId"] = GetCountriesList(); instead - these are interchangeable. 相反-这些是可以互换的。 I haven't had as great success getting values to bind to the DropDownListFor objects with either of those, though, so that's why I prefer using the model objects ( public SelectListItem[] CountriesList; public SelectListItem[] CurrenciesList; ), instead, but thought I would show the various ways of adding in the options to the dropdowns. 但是,我并没有获得成功将值绑定到DropDownListFor对象的任何一个,因此,这就是为什么我更喜欢使用模型对象( public SelectListItem[] CountriesList; public SelectListItem[] CurrenciesList; )的原因,但是,我以为我会展示在下拉菜单中添加选项的各种方式。

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

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