如何使用Key对ViewModel进行编码,以获取人类可读的值而不是ID

时间:2014-06-30 11:27:48

标签: sql asp.net-mvc entity-framework

我在PM控制台中执行更新数据库时遇到错误。我理解为什么,但鉴于我的需要,我不确定如何最好地克服它。我希望有人能指出我正确的方向。

我有2张桌子,一张用于国家,一张用于州。通常我会使用Id作为主键,但我的客户要求我将关联设为人类可读的。例如,他们希望将其作为“美国”,而不是将其国家/地区的值设为1的用户。他们的状态不是14的值,而是看“CA”或“TX”等等。

我的迁移Configuration.cs包含一个添加国家和州的种子例程。

当我运行Update-Database时,出现以下错误:

  

运行种子方法。   System.Data.Entity.Infrastructure.DbUpdateException:无法执行   确定'XXX_XXX_XXX.Models.State_Country'的主要结尾   关系。多个添加的实体可以具有相同的主键。   ---> System.Data.Entity.Core.UpdateException:无法确定'XXXX_XXXX_XXXX.Models.State_Country'的主要结尾   关系。多个添加的实体可能具有相同的主键。

我的ViewModel包含:

namespace XXXX_XXXX_XXXX.Models
{
    public class Country
    {
        [Key]
        public string CountryCode { get; set; }

        public int CountryId { get; set; }
        public string CountryName { get; set; }

        public virtual ICollection<State> States { get; set; }
    }

    public class State
    {
        [Key]
        public string StateCode { get; set; }

        public int StateId { get; set; }
        public string StateName { get; set; }

        [ForeignKey("Country")]
        public string CountryCode { get; set; }

        public virtual Country Country { get; set; }
    }
}

我的种子例程如下所示:

protected override void Seed(ProgramDbContext context)
{
    // Initialize Countries
    context.Countries.AddOrUpdate(c => c.CountryCode, 
        new Country { CountryId = 1, 
                      CountryName = "United States", 
                      CountryCode = "USA" });

    context.Countries.AddOrUpdate(c => c.CountryCode, 
        new Country { CountryId = 2, 
                      CountryName = "Canada", 
                      CountryCode = "CAN" });

    {  ... }

    // Initialize States
    context.States.AddOrUpdate(c => c.StateId, 
        new State { StateId = 1, 
                    StateName = "Iowa", 
                    StateCode = "IA", 
                    CountryCode = "USA" });

    context.States.AddOrUpdate(c => c.StateId, 
        new State { StateId = 2, 
                    StateName = "Illinois", 
                    StateCode = "IL", 
                    CountryCode = "USA" });

    context.States.AddOrUpdate(c => c.StateId, 
        new State { StateId = 3, 
                    StateName = "California", 
                    StateCode = "CA", 
                    CountryCode = "USA" });

    {  ... }

}

我明白我收到了错误,因为CountryCode不包含每列的唯一值。如果我将CountryId设置为Key并将States的外键更改为CountryId我没有收到错误,但是我似乎必须在Id字段上执行查找以在需要时获取CountryCode。

我也预见到StateCode最终可能会有重复,但目前还没有。

使用ViewModel编码可以在一个表中有2个[Key]属性吗?我无法让它发挥作用。

我是否需要将[Key]属性放在每个表的Id列上,然后在每次需要更新用户配置文件或创建下拉列表时查找CountryCode和StateCode?顺便说一下,我为CountryCode和StateCode创建了自定义标识字段。

我的国家和州的ViewModel应该如何编码,以便我可以方便地将我的用户配置文件显示为“CA,USA”而不是3,1 - 例如?

修改

@David提议查看我的国家和州下拉列表的查看代码。我使用jQuery填充State下拉列表

我在控制器中填充了Model.Countries:

public IEnumerable<SelectListItem> GetCountryList()
{
    var countries = new SelectList(dbLocations.Countries, "CountryCode", "CountryName").ToList();
    countries.Insert(0, (new SelectListItem { Text = "Select Country", Value = "-1" }));
    countries.Insert(1, (new SelectListItem { Text = "United Sates", Value = "54" }));
    countries.Insert(2, (new SelectListItem { Text = "Canada", Value = "13" }));
    countries.Insert(3, (new SelectListItem { Text = "Mexico", Value = "12" }));
    countries.Insert(4, (new SelectListItem { Text = "Brazil", Value = "36" }));
    countries.Insert(5, (new SelectListItem { Text = "------------------------", Value = "-1" }));

    IEnumerable<SelectListItem> countrylist =
        countries.Select(m => new SelectListItem()
        {
            Text = m.Text,
            Value = m.Value
        });

    return countrylist;
}

我在视图中使用的示例:

<div class="form-group">
    @Html.LabelFor(m => m.CountryId, new { @class = "col-md-3 control-label" })
    <div class="col-md-9">
        @Html.DropDownListFor(
        x => x.CountryId,
        new SelectList(Model.Countries, "Value", "Text"), 
        new { @class = "form-control" })
    </div>
</div>

<div class="form-group">
    @Html.LabelFor(m => m.StateId, new { @class = "col-md-3 control-label" })
    <div class="col-md-9">
        <div id="stateid_select">
            <select id="StateId" class="form-control" name="StateId"></select>
        </div>
    </div>
</div>

jQuery for Country change(检索状态):

<script type="text/javascript">

// Populate State/Province dropdown on Country select
$(function () {
     $('#CountryCode').change(function () {

         $.getJSON('/Account/StateList/' + $('#CountryCode').val(), function (data) {

             var cnt = 0;
             var items = '<option>Select a State/Province</option>';
             $.each(data, function (i, state) {
                items += "<option value='" + state.Value + "'>" + state.Text + "</option>";
                cnt = cnt + 1;
            });

            // Hide SELECT and show TEXT field if no state/provinces to choose from.
            // Else show SELECT and hide TEXT field.
            if (!cnt == 0) {
                $('#stateprovince_select select').html(items).show().attr("disabled", false).removeClass("hide");
                $('#stateprovince_text input[type="text"]').hide().attr("disabled", true).addClass("hide");
             } else {
                $('#stateprovince_select select').html('').hide().attr("disabled", true).addClass("hide");
                $('#stateprovince_text input[type="text"]').show().attr("disabled", true).removeClass("hide");
                $('#StateProvinceCode').hide();
            }
        });
    });
});
</script>

jQuery在Controller中调用此例程:

[AllowAnonymous]
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult StateList(int countryid)
    {
        var state = from s in dbLocations.States
                    where s.CountryId == countryid
                    select s;
        return Json(new SelectList(state.ToArray(), "StateId", "StateName"), JsonRequestBehavior.AllowGet);
    }

可能比您需要的更多。我做了一些更改以反映ModelView的更改,但我还没有能够运行应用程序 - 仍在进行更改。我知道,jQuery需要一些清理工作。 ;)

1 个答案:

答案 0 :(得分:1)

您应该在Id列上保留Key索引,但为CountryCode添加唯一约束。这有以下好处:

  1. 您的关系都使用整数Id列,其效果优于连​​接字符串。

  2. CountryCode现在不允许重复输入。

  3. 可以向用户显示CountryCode,并隐藏数据库与整数链接的事实。

  4. 所以现在你的模型看起来像这样:

    public class Country
    {
        [Key]
        public int Id { get; set; }
    
        [Index("UX_Country_CountryCode", IsUnique = true)]
        [MaxLength(10)]
        public string CountryCode { get; set; }
    
        public string CountryName { get; set; }
    
        public virtual ICollection<State> States { get; set; }
    }
    
    public class State
    {
        [Key]
        public int Id { get; set; }
    
        [Index("UX_State_StateCode", IsUnique = true)]
        [MaxLength(10)]
        public string StateCode { get; set; }
    
        public string StateName { get; set; }
    
        [ForeignKey("Country")]
        public int CountryId { get; set; }
    
        public virtual Country Country { get; set; }
    }
    

    如果您希望能够在不同的国家/地区拥有相同的州代码,请扩展这样的唯一约束:

    public class State
    {
        [Key]
        public int Id { get; set; }
    
        [Index("UX_State_StateCodeCountryId", IsUnique = true, Order = 1)]
        [MaxLength(10)]
        public string StateCode { get; set; }
    
        public string StateName { get; set; }
    
        [ForeignKey("Country")]
        [Index("UX_State_StateCodeCountryId", IsUnique = true, Order = 0)]
        public int CountryId { get; set; }
    
        public virtual Country Country { get; set; }
    }