Есть работающее приложение/вебсайт, в котором можно в паспорт автомобиля подгружать картинки, одна из картинок считается "заглавной". Первая загруженная картинка становится заглавной, впоследствии можно поменять.
Соответственно, были следующие классы:
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<CarImages>()
.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<CarImage> 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; }
}
я описывал в чате, как я к нему шёл, там же можно найти проблемы, с которыми я столкнулся и пока не смог решить.
Классы, приведенные в вопросе выглядят правильными, и должны по идее решать поставленную задачу. Следует только на уровне приложения следить за тем, чтобы PrimaryImage у разных машин не ссылался на один и тот же CarImage, и чтобы этот CarImage в свою очередь ссылался именно на эту машину. Такую логику хорошо бы поместить прямо в свойство PrimaryImage (Entity Framework позволяет так делать).
private CarImage fPrimaryImage;
public virtual CarImage PrimaryImage {
get { return fPrimaryImage; }
set {
if (fPrimaryImage == value) return;
fPrimaryImage = value;
if (fPrimaryImage != null)
fPrimaryImage.Car = this;
}
Можно сделать еще такую вещь - добавить таблицу в которой хранить ссылки на Car и CarImage. В этой таблице хранить PrimaryImage всех машин. При таком подходе, будет сложнее запутаться при объявлении one to zero-or-one связи.
public class Car {
public virtual PrimaryImage PrimaryImage {get;set;}
}
public class CarImage {
[Required]
public virtual Car Car { get;set;}
}
public class PrimaryImage {
[Required]
public virtual Car Car {get;set;}
[Required]
public virtual CarImage Image {get;set;}
}
Сборка персонального компьютера от Artline: умный выбор для современных пользователей