#sql #asp.net-mvc #entity-framework
#sql #asp.net-mvc #entity-framework
Вопрос:
Я получаю сообщение об ошибке при выполнении обновления базы данных в консоли PM. Я понимаю почему, но, учитывая мои потребности, я не уверен, как лучше всего это преодолеть. Я надеюсь, что кто-нибудь может указать мне правильное направление.
У меня есть 2 таблицы, одна для стран и одна для штатов. Обычно я бы использовал идентификатор для первичного ключа, но мой клиент попросил, чтобы я сделал ассоциации удобочитаемыми. Например, вместо пользователя, имеющего значение для своей страны как 1, они хотят, чтобы оно было «США». Вместо значения 14 для их состояния они видят «CA» или «TX» и т.д.
Мой Migration Configuration.cs содержит начальную процедуру для добавления стран и штатов.
Когда я запускаю Update-Database, я получаю следующую ошибку:
Запуск начального метода. System.Data.Entity.Инфраструктура.Исключение DbUpdateException: невозможно определить основной конец ‘XXX_XXX_XXX.Models.Связь State_Country. Несколько добавленных объектов могут иметь один и тот же первичный ключ. —> System.Data.Entity.Core.UpdateException: невозможно определить основной конец ‘XXXX_XXXX_XXXX.Модели.Связь 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" });
{ ... }
}
I DO understand that I am getting the error because CountryCode doesn’t contain unique values for every column. If I set the CountryId to the Key and change the Foreign Key for States to CountryId I don’t get the error, but then it seems I have to perform a lookup on the Id fields to get the CountryCode whenever I need it.
I also foresee that it’s possible and even likely that the StateCode will eventually have a duplicate, though it doesn’t currently.
Is it possible to have 2 [Key] attributes in a single table using ViewModel coding? I couldn’t get that to work.
Do I need to place the [Key]
attribute on the Id columns of each of these tables and then look-up the CountryCode and StateCode each time I need to update a user profile or create a dropdown list? Btw, I’ve created custom Identity fields for CountryCode and StateCode.
How should my ViewModel for Country and State be coded so that I can conveniently have my user profiles show «CA, USA» instead of 3, 1 — for example?
EDIT
@David offered to look at my View code for the Country and State dropdowns. I populate the State dropdown using jQuery
I populate Model.Countries in my controller with:
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;
}
An example of how I use in a View:
<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 для изменения страны (извлекает состояния):
<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 вызывает эту процедуру в контроллере:
[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. Оставьте ключ в столбце Id, но отображайте коды только пользователю. Кроме того, установите уникальное ограничение на коды, чтобы у вас не могло быть дубликатов, и это позволяет выполнять индексированный поиск.
2. В целом я понимаю, что вы советуете, просто не знаю, как это сделать.
3. Я расширил это ответом. Если вы добавите часть своего кода представления, я могу включить и это.
4. Я отредактировал вопрос и включил еще немного своего кода.
Ответ №1:
Вы должны оставить Key
индекс в столбце Id, но добавить уникальное ограничение к CountryCode
. Это имеет следующие преимущества:
-
Все ваши отношения используют целочисленные столбцы идентификаторов, которые будут работать лучше, чем объединение по строкам.
-
Код страны теперь не будет допускать повторяющихся записей.
-
Код страны может быть показан пользователям и скрывает тот факт, что ваша база данных связывается с целыми числами.
Итак, теперь ваши модели выглядят так:
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; }
}
Комментарии:
1. Я не уверен, насколько важно ограничение для кода состояния — я не думаю, что могу указать это, поскольку страна может иметь тот же двухбуквенный код для состояния, что и другая страна. Я вижу, что вы сделали. Спасибо.
2. Я внес изменения, чтобы помочь.
3. Я получаю эту ошибку при запуске Update-Database:
Column 'CountryCode' in table 'dbo.Countries' is of a type that is invalid for use as a key column in an index.
4. Ах, поставьте на него атрибут максимальной длины.
5. Понял,
MaxLength(3)
по коду страны иMaxLength(2)
по коду состояния. Теперь я вижу эту ошибку:Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
Я подозреваю, что сейчас есть проблема в начальном коде?