3

There's two entities in db, Cars and Car's image:

public class Car
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    [Required]
    public string Title { get; set; }
}

and

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

It's all good, works fine. Example: car with car is = 123 has 5 images, one of them has been marked as 'primary' (first uploaded image or manualy choosen by moderator).

Then i decide to optimize db model: remove IsPrimaryImage and add PrimaryImage to Car as shown here:

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

and

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

(Car may contain no images - that's why int?, not int)

Compile, run -- failed with error:

An exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll but was not handled in user code

Additional information: Unable to determine the principal end of an association between the types 'MyApp.Domain.CarImage' and 'MyApp.Domain.Car'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.

Found many similar topics on stackoverlow about this error:

  • first, commons
  • second, same error but 1:1 relationship, not my case
  • third, looks like my case because about 1:0..1 but fluent API only.

Is it possible to fix error with data annotation? EF version is 6.

Community
  • 1
  • 1
A K
  • 714
  • 17
  • 39

2 Answers2

4

Found correct way:

public class Car
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int CarID { get; set; }

    [Required]
    public string Title { get; set; }

    public int? PrimaryImageID { get; set; }

    [ForeignKey("CarImageID")]
    public virtual CarImage PrimaryImage { get; set; }
}

and

public class CarImage
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int CarImageID { get; set; }

    [Required]
    public int CarID { get; set; }

    [ForeignKey("CarID")]
    [Required]
    public virtual Car Car { get; set; }

    public string FileName { get; set; }
}

sql

P.S. Unfortunately, this not works for such IDs:

public class Car
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }
}

and

public class CarImage
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }
}

Update: If you specify your property as ID and ForeignKey as Id - this will cause an error:

An exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll but was not handled in user code

Additional information: The ForeignKeyAttribute on property 'PrimaryImage' on type 'MyApp.Domain.Car' is not valid. The foreign key name 'Id' was not found on the dependent type 'MyApp.Domain.CarImage'. The Name value should be a comma separated list of foreign key property names.

So, this works also (case sensitive!):

public class Car
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    [Required]
    public string Title { get; set; }

    public int? PrimaryImageID { get; set; }

    [ForeignKey("ID")]
    public virtual CarImage PrimaryImage { get; set; }
}

and:

public class CarImage
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    [Required]
    public int CarID { get; set; }

    [ForeignKey("ID")]
    [Required]
    public virtual Car Car { get; set; }

    public string FileName { get; set; }
}

v2

A K
  • 714
  • 17
  • 39
3

In this particular case, you don't need data annotations. You need to help EF figure out that a Car 'has' multiple Images, in stead of Car and Image being random entities that point to each other.

public class Car
{
  ...

  public virtual ICollection<CarImage> Images { get; set; } // <-- Adding this relation makes the problem go away :-)

  [ForeignKey("PrimaryImageID")]
  public virtual CarImage PrimaryImage { get; set; }
  public int? PrimaryImageID { get; set; }
}

Updated by A K: complete code is (works without Required on virtual Car Car, and it works with my styleguide for ID names)

public class Car
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    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> Images { get; set; }
}

and

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

sql management studio

A K
  • 714
  • 17
  • 39
Paul-Jan
  • 16,746
  • 1
  • 63
  • 95
  • Like my own answer this works also. And, this looks more pretty that my version: it works with both primary keys (CarID -> ID). May I edit your answer before accepting (add sceenshot from sql management studio and complete sample of code)? – A K Jan 13 '17 at 20:20
  • 1
    Please do! Strictly speaking I implemented the model slightly different from what you originally proposed, your own answer is a more direct approach. I tend to setup the model to avoid true one<->one relationships in EF (and most other ORM's), because they are generally not handled very gracefully. – Paul-Jan Jan 13 '17 at 20:43
  • Thank you again for your help. I've found the way to fix errors with prop `ID`, see update in my answer. I'll mark my solution as accepted for future visitors, your version less acceptable. – A K Jan 21 '17 at 08:15