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

422
23 января 2017, 17:24

Есть работающее приложение/вебсайт, в котором можно в паспорт автомобиля подгружать картинки, одна из картинок считается "заглавной". Первая загруженная картинка становится заглавной, впоследствии можно поменять.

Соответственно, были следующие классы:

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; }
}

я описывал в чате, как я к нему шёл, там же можно найти проблемы, с которыми я столкнулся и пока не смог решить.

Answer 1

Классы, приведенные в вопросе выглядят правильными, и должны по идее решать поставленную задачу. Следует только на уровне приложения следить за тем, чтобы 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;}
}
READ ALSO
Как вызвать команду Command=&ldquo;Copy&rdquo; из ContextMenu Click?

Как вызвать команду Command=“Copy” из ContextMenu Click?

Команда копировать тест Copy (в Button) связана через Command с ApplicationCommandsCopy

422
Как получить время простоя программы?

Как получить время простоя программы?

А именно время простоя в котором не было переключения на окно программы

396
ASP.NET настройка Ninject

ASP.NET настройка Ninject

Проблема с настройкой DI контейнера NinjectИмеется автоматически сгенерированный класс NinjectWebCommon

452
Asp.Net Web Api2 + SignalR

Asp.Net Web Api2 + SignalR

Подскажите плиз

450