Страницы

Поиск по вопросам

пятница, 9 ноября 2018 г.

Настройка отношения one to zero-or-one совместно с one-to-many

Есть работающее приложение/вебсайт, в котором можно в паспорт автомобиля подгружать картинки, одна из картинок считается "заглавной". Первая загруженная картинка становится заглавной, впоследствии можно поменять.
Соответственно, были следующие классы:
public class Car { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; }
[Required] public string Title { get; set; } }
и
public class CarImage { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; }
[Required] public int CarID { get; set; }
[ForeignKey("CarID")] public virtual Car Car { get; set; }
public string FileName { get; set; }
public bool IsPrimaryImage { get; set; } }
И как-то знакомые предложили мне попробовать отрефакторить это следующим образом: не помечать заглавную картинку как IsPrimaryImage = true, а вынести в свойство PrimaryImage:
public class CarImage { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; }
[Required] public int CarID { get; set; }
[ForeignKey("CarID")] public virtual Car Car { get; set; }
public string FileName { get; set; } }
и
public class Car { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; }
[Required] public string Title { get; set; }
public int? PrimaryImageID { get; set; }
public virtual CarImage PrimaryImage { get; set; } }
Однако чем больше я вникаю в эту задачу - тем больше понимаю, что она не такая простая как мне показалось на первый взгляд.
Сначала я думал обойтись только data anntotation (не очень люблю fluent API, когда определения где-то отдельно от таблиц), есть мой топик на en-so, где я пытался расставить атрибуты.
Однако проблемы начались когда я начал пытаться сохраниться в базу:
using (var db = new DataContext()) { var car = db.Car.First(x => x.ID == CarID);
var image = new CarImage { CarID = car.ID, //Car = car, IsDeleted = false, };
db.CarImages.Add(image);
db.SaveChanges();
if (isFirstImage) { car.PrimaryImage = image; car.PrimaryImageID = image.ID; }
db.SaveChanges(); }
(Ошибок было много разных, могу привести, но как мне кажется, что там нет ничего полезного для дальнейшего обсуждения)
После чего я решил оставить эти попытки и использовать fluent API:
modelBuilder.Entity() .HasRequired(x => x.Car) .WithOptional(x => x.PrimaryImage) .Map(x => x.MapKey("PrimaryImageID"));
И... тоже как ни странно стал получать те или иные ошибки. Тоже не привожу, т.к. в этот момент я понял, что нужно разбираться основательно и сначала.
Итак, для начала разберёмся с отношениями в базе
У одного автомобиля может быть несколько изображений -- это очевидно, связь one-to-many.
Но, с другой стороны, у каждого автомобиля может быть одна заглавная картинка -- это очевидно связь one to zero-or-one.
Как правильно прописать эти связки в моих классах?
В контроле у меня собственно всего лишь две вьюхи:
нужно вывести список машин с заглавными картинками (или картинкой-заглушкой, если картинок ещё нет); нужно вывести карточку машины со списком параметров и всеми картинками, которые относятся к этой машине.
И, как раз в первой будут намного лучше план запроса к БД, если сделать рефакторинг.
Пробовал такой вариант решения:
public class Car { [Key] public int ID { get; set; }
[Required] public string Title { get; set; }
public int? PrimaryImageID { get; set; }
[ForeignKey("PrimaryImageID")] public virtual CarImage PrimaryImage { get; set; }
public virtual ICollection AllImages { get; set; } }
и
public class CarImage { [Key] [ForeignKey("Car")] public int ID { get; set; }
[Required] public int CarID { get; set; }
[ForeignKey("CarID")] public virtual Car Car { get; set; }
public string FileName { get; set; } }
я описывал в чате, как я к нему шёл, там же можно найти проблемы, с которыми я столкнулся и пока не смог решить.


Ответ

Вы слишком все усложнили. Атрибут
[ForeignKey("Car")] public int ID { get; set; }
Т.е. ок, вы скопировали его из статьи про one-to-one or zero - но нужно понимать, за счет чего достигается эта связь.
По сути, вы просто говорите "ID картинки должен быть равен ID существующей машины". Достаточно очевидно, что при этом у машины может быть не больше одной картинки. Вы физически не можете записать в базу картинку с ID = 2, если в базе нет машины с ID = 2. Никак. Вы явно обозначили это в своей модели. В вашей модели в базе не может быть ровно одна машина и две картинки - то ID у картинок должны быть равны, и вы просто не сможете их различить.
И тут же вы пытаетесь заявить "но у машины (как-то!) может быть несколько картинок!" - что делает вашу модель противоречивой. И EF умирает.
Не усложняйте. Просто уберите лишнее - лишние атрибуты, лишние вызовы SaveChanges, возможность достать машину из CarImage (можно оставить, но скорее всего она не нужна) и оставить работу через сущности - все заработает именно так, как вы хотите. Вот минимальный рабочий пример:
public class Car { public int ID { get; set; }
public virtual CarImage PrimaryImage { get; set; }
public virtual ICollection AllImages { get; set; } }
public class CarImage { public int ID { get; set; } } public class Model1 : DbContext { public Model1() : base("name=Model1") { }
public DbSet Cars { get; set; } }
using (var db = new Model1()) { var car = db.Cars.First(x => x.ID == 1); bool isFirstImage = !car.AllImages.Any();
var image = new CarImage();
car.AllImages.Add(image);
if (isFirstImage) { car.PrimaryImage = image; }
db.SaveChanges(); }
По желанию можно вернут на место все PrimaryImageId, CarID, Car DbSet - все будет работать точно так же (т.е. вы этим сможете явно сделать это позволит вам сделать связь CarImage -> Car обязательной, но сейчас она все равно практически обязательна - т.к. у CarImage нет Car - вы не можете его занулить :) )

Комментариев нет:

Отправить комментарий