外键实体框架&剃刀

时间:2014-11-18 09:04:42

标签: c# asp.net-mvc entity-framework asp.net-mvc-4 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>

我收到错误:

  

具有键&#39; CountryId&#39;的ViewData项。属于&#39; System.Int32&#39;但必须属于&#39; IEnumerable&#39;。

我缺少的,当然这应该有效,因为我不需要IEnumerable来实现一对一的关系吗?

非常感谢任何帮助。

4 个答案:

答案 0 :(得分:0)

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类型。如果你想要Enumberable of int那么你必须声明像

这样的东西
 public IEnumerable<int> CurrencyId { get; set; }

答案 1 :(得分:0)

当您想要下拉列表时,通常的做法是为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)

答案 2 :(得分:0)

谢谢大家的反馈。根据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>

这一切都正确连线。

答案 3 :(得分:0)

如果您未在控制器方法中提供ViewBag或在模型中未提供List<SelectListItem>SelectList和/或未在其中指定,则可能会发生这种情况您的DropDownListFor,因为您的代码中没有CountryId。错误告诉您您拥有这个int,它是一个int,但是希望您用一系列项目而不是ViewBag来填充下拉列表。它将尝试查找名称为CountryId的{​​{1}},因为在您的View代码中未指定任何集合时,它将默认尝试执行此操作;如果没有,则它将无法继续进行在那里,因为这样只能看到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属性将当前ViewModel中的字段映射到要访问的表的主键,即当前ViewModel表的CountryId映射到{{ 1}}表,因此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"]:在{{1 }}方法,然后返回View。您也可以改用Initialize()-它们是可互换的。但是,我并没有获得将值绑定到ViewBag对象中的任何一个方面的成功,所以这就是为什么我更喜欢使用模型对象(ViewBag.CountryId = GetCountriesList();)的原因,但是我想显示在下拉菜单中添加选项的各种方式。