I'm trying to convert the following model (see image below) to Code First. I've tried various combinations involving ForeignKey and InverseProperty attributes with no luck. I've found this answer but it seems that combinations of ForeignKey and InverseProperty cause a different behaviour.
The attached source code gives the following error:
Unable to determine the principal end of an association between the types 'InversePropertyTest.Author' and 'InversePropertyTest.Book'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
This is my model with EDMX
Sample code:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace InversePropertyTest
{
public class Author
{
public int AuthorId { get; set; }
public Nullable<int> CurrentlyWorkingBookId { get; set; }
[InverseProperty("Author")] public ICollection<Book> Books { get; set; }
[ForeignKey("CurrentlyWorkingBookId"), InverseProperty("EditoredBy")] public Book CurrentlyWorkingBook { get; set; }
}
public class Book
{
public int BookId { get; set; }
public int AuthorId { get; set; }
[ForeignKey("AuthorId"), InverseProperty("Books")] public Author Author { get; set; }
[InverseProperty("CurrentlyWorkingBook")] public Author EditoredBy { get; set; }
}
public class SimpleContext : DbContext
{
public DbSet<Author> Authors { get; set; }
public DbSet<Book> Books { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
class Program
{
static void Main(string[] args)
{
using (var context = new SimpleContext())
{
IList<Author> authors = (from a in context.Authors select a).ToList();
IList<Book> books = (from b in context.Books select b).ToList();
}
}
}
}
Any help is greatly appreciated
When you use [InverseProperty] at both ends of a 1:1 association it is not clear who the principal should be. The principle is the entity the other end (the dependent) refers to by a foreign key. Even though you tell EF that EditoredBy and CurrentlyWorkingBookId both are part of one association it still would be possible to have a foreign key field for EditoredBy in Book (that wouldn't show in the class model).
Admittedly, one could contend that you've told EF enough to create the database model properly. EF could have logic that says: if I've been told about one foreign key in a 1:1 association, then I know who the principle should be. However, unfortunately it doesn't.
So I would use the fluent API to model this:
public class Author
{
public int AuthorId { get; set; }
public ICollection<Book> Books { get; set; }
public Book CurrentlyWorkingBook { get; set; }
}
public class Book
{
public int BookId { get; set; }
public int AuthorId { get; set; }
public Author Author { get; set; }
public Author EditoredBy { get; set; }
}
In OnModelCreating:
modelBuilder.Entity<Author>()
.HasMany(a => a.Books)
.WithRequired(b => b.Author)
.HasForeignKey(b => b.AuthorId);
modelBuilder.Entity<Author>()
.HasOptional(a => a.CurrentlyWorkingBook)
.WithOptionalDependent(b => b.EditoredBy)
.Map(m => m.MapKey("CurrentlyWorkingBookId"));
Personally, I like the fluent API because the lambda expressions allow compile-time checks and it is much more conspicuous which ends comprise one association.
As you see, CurrentlyWorkingBookId can not be part of the class model in this scenario. That is because an OptionalNavigationPropertyConfiguration (the return type of WithOptionalDependent) doesn't have HasForeignKey method. I'm not sure why not. I think it should be possible to set a primitive FK value (CurrentlyWorkingBookId) as well as a reference property (CurrentlyWorkingBook).
Related
I am making a web app similar to google classroom in that you can join classes.
I have a class "Account" and inside that account I have a list that should hold the IDs of all the classes the account has joined. I tried to make the list a list of longs, but I couldn't do that because I got the error:
System.InvalidOperationException: 'The property
'Account._classesJoined' could not be mapped, because it is of type
'List' which is not a supported primitive type or a valid entity
type. Either explicitly map this property, or ignore it using the
'[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in
'OnModelCreating'.
The way I solved this problem is to create a class "JoinedClassId" to make a list of instead, with a property "classIdNumber". However, during testing, I noticed that the JoinedClassIds that I added to the the Account object were not saving. I think this is because I am not saving the database table for the JoinedClassId class.
Do I have to create a database context and controller for the JoinedClassId class? I don't want to be able to manipulate the JoinedClassId class from the API, I'm only using it as a data container. Is there a way I could either create a long list and save it or save the JoinedClassIds?
In EF Core "Many-to-many relationships without an entity class to represent the join table are not yet supported".
Book -> Category has many-to-may rel so this should create the 3 tables in DB :
Books, Category and BookCategory
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
//public ICollection<Category> Categories { get; set; } // cannot appear
// For the many-to-many rel
public List<BookCategory> BookCategories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
//public ICollection<Book> Books { get; set; } // cannot appear
// For the many-to-many rel
public List<BookCategory> BookCategories { get; set; }
}
// Class because of the many-to-many rel
public class BookCategory
{
public int BookId { get; set; }
public Book Book { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class MyContextDbContext : DbContext
{
public MyContextDbContext(DbContextOptions<MyContextDbContext> dbContextOptions)
: base(dbContextOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BookCategory>()
.HasKey(t => new { t.BookId, t.CategoryId });
modelBuilder.Entity<BookCategory>()
.HasOne(bctg => bctg.Book)
.WithMany(ctg => ctg.BookCategories)
.HasForeignKey(book => book.CategoryId);
modelBuilder.Entity<BookCategory>()
.HasOne(bctg => bctg.Category)
.WithMany(ctg => ctg.BookCategories)
.HasForeignKey(ctg => ctg.BookId);
}
public DbSet<Book> Book { get; set; }
public DbSet<Category> Category { get; set; }
}
I have read a lot of related questions about this topic but none of them seemed to address my problem, so please bear with me.
I am new to EF and trying to establish the following relationship, in ASP .NET MVC, using EF6:
I need to have two permanent tables, Drivers and Cars. I now need to create a relationship between these tables when a Driver is associated to a Car. But one Driver can only be assigned to one Car.
A Driver may not always be associated to a Car and vice-versa and I want to maintain both tables even if there isn't always an association between them, so that is why I believe I need to have an additional table exclusively to make this connection. Which I think will create a 1:1:1 relationship between these classes.
Below is the model for my POCO classes.
Models
public class Driver
{
public int DriverID { get; set; }
public string Name { get; set; }
//other additional fields
public DriverCar DriverCar { get; set; }
}
public class Car
{
public int CarID { get; set; }
public string Brand { get; set; }
//other additional fields
public DriverCar DriverCar { get; set; }
}
public class DriverCar
{
public int DriverCarID { get; set; }
public int DriverID { get; set; }
public Driver Driver { get; set; }
public int CarID { get; set; }
public Car Car { get; set; }
}
I have tried configuration the relationships using Fluent API but I believe I am doing it completly wrong since I have got errors such as:
Introducing FOREIGN KEY constraint 'FK_dbo.DriverCar_dbo.Car_CarId' on
table 'DriverCar' may cause cycles or multiple cascade paths. Specify
ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN
KEY constraints. Could not create constraint or index. See previous
errors.
Fluent Api
modelBuilder.Entity<DriverCar>()
.HasRequired(a => a.Driver)
.WithOptional(s => s.DriverCar)
.WillCascadeOnDelete(false);
modelBuilder.Entity<DriverCar>()
.HasRequired(a => a.Car)
.WithOptional(s => s.DriverCar)
.WillCascadeOnDelete(false);
I am really not sure if I am missing something or if there is some better approach to handle this situation and I would appreciate so much if someone can give me some feedback on how to solve this.
Update
Just found an interesting answer here: Is it possible to capture a 0..1 to 0..1 relationship in Entity Framework?
Which I believe is exactly what I want: a 0..1 to 0..1 relationship. But all the mentioned options seem too complex and I'm not quite sure which one is the best or how to even correctly implement them.
Are these type of relationships supposed to be so hard to implement in EF?
For example, I tried Option 1 but it created a 0..1 to many relationship from both tables - Driver to Car and Car to Driver. How am I suppose to create an unique association between them then?
Try this for your models. Virtual enables lazy loading and is advised for navigation properties. DataAnnotations showing the Foreign Keys (or use fluent) to be sure each relationship is using the correct key.
public class Driver
{
public int DriverID { get; set; }
public string Name { get; set; }
//other additional fields
public DriverCar? DriverCar { get; set; }
}
public class Car
{
public int CarID { get; set; }
public string Brand { get; set; }
//other additional fields
public DriverCar? DriverCar { get; set; }
}
public class DriverCar
{
public int DriverCarID { get; set; }
[ForeignKey("Driver")]
public int DriverID { get; set; }
public Driver Driver { get; set; }
[ForeignKey("Car")]
public int CarID { get; set; }
public Car Car { get; set; }
}
modelBuilder.Entity<Driver>()
.HasOptional(a => a.DriverCar)
.WithRequired(s => s.Driver)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Car>()
.HasOptional(a => a.DriverCar)
.WithRequired(s => s.Car)
.WillCascadeOnDelete(false);
Note: Changed to Data Annotations for Foreign Keys. Inverted fluent statements. Fixed Driver to Car in second relationship.
Here is a simple way to create a one to zero. Note that I'm a fan of keeping the Id of all tables as just Id, not CarId etc, just my style. This is just a console app so once you add the EF nuget you could just copy/paste.
But the below code works with .net framework 4.6 and EF6.2 It creates the following tables
Car
Id (PK, int, not null)
Driver_Id (FK, int, null)
Driver
Id (PK, int, not null)
Under this schema a Car can have only one driver. A driver may still drive multiple cars though. I'm not sure if that's an issue for you or not.
using System.Data.Entity;
namespace EFTest
{
class Program
{
static void Main(string[] args)
{
var connectionString = "<your connection string>";
var context = new DatabaseContext(connectionString);
var car = new Car();
var driver = new Driver();
context.Cars.Add(car);
context.Drivers.Add(driver);
car.Driver = driver;
context.SaveChanges();
}
}
public class Car
{
public int Id { get; set; }
public virtual Driver Driver { get; set; }
}
public class Driver
{
public int Id { get; set; }
}
public class DatabaseContext : DbContext, IDatabaseContext
{
public DbSet<Car> Cars { get; set; }
public DbSet<Driver> Drivers { get; set; }
public DatabaseContext(string connectionString) : base(connectionString){ }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasKey(n => n.Id)
.HasOptional(n => n.Driver);
modelBuilder.Entity<Driver>()
.HasKey(n => n.Id);
}
}
}
But if you REALLY wanted to enforce the constraint of only one mapping per car and driver, you could do it with the code below. Note that when you have the joining entity, you don't put it's Id anywhere on the joined entities.
using System.Data.Entity;
namespace EFTest
{
class Program
{
static void Main(string[] args)
{
var connectionString = "your connection string";
var context = new DatabaseContext(connectionString);
//Create a car, a driver, and assign them
var car = new Car();
var driver = new Driver();
context.Cars.Add(car);
context.Drivers.Add(driver);
context.SaveChanges();
var assignment = new DriverAssignment() { Car_id = car.Id, Driver_Id = driver.Id };
context.DriverAssignments.Add(assignment);
context.SaveChanges();
//Create a new car and a new assignment
var dupCar = new Car();
context.Cars.Add(dupCar);
context.SaveChanges();
var dupAssignment = new DriverAssignment() { Car_id = dupCar.Id, Driver_Id = driver.Id };
context.DriverAssignments.Add(dupAssignment);
//This will throw an exception because it will violate the unique index for driver. It would work the same for car.
context.SaveChanges();
}
}
public class Car
{
public int Id { get; set; }
}
public class Driver
{
public int Id { get; set; }
}
public class DriverAssignment
{
public int Car_id { get; set; }
public int Driver_Id { get; set; }
}
public class DatabaseContext : DbContext, IDatabaseContext
{
public DbSet<Car> Cars { get; set; }
public DbSet<Driver> Drivers { get; set; }
public DbSet<DriverAssignment> DriverAssignments { get; set; }
public DatabaseContext(string connectionString) : base(connectionString) { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>().HasKey(n => n.Id);
modelBuilder.Entity<Driver>().HasKey(n => n.Id);
modelBuilder.Entity<DriverAssignment>().HasKey(n => new { n.Car_id, n.Driver_Id });
modelBuilder.Entity<DriverAssignment>().HasIndex(n => n.Car_id).IsUnique();
modelBuilder.Entity<DriverAssignment>().HasIndex(n => n.Driver_Id).IsUnique();
}
}
}
I'm using this kind of model for a 0..1 to many relationship. A Page must either have a valid book id or null.
public class Book
{
[Key]
public Guid Id { get; set; }
public virtual List<Page> Pages { get; set; }
}
public class Page
{
[Key]
public Guid Id { get; set; }
public virtual Book Book { get; set; }
}
I want to add cascading deletes, so that if a book is deleted then all of its pages are also deleted, not set to null.
I can (only?) do this with the fluent api:
modelBuilder.Entity<Page>()
.HasOptional(a => a.Book)
.WithOptionalDependent()
.WillCascadeOnDelete(true);
Using [Required] is not suitable, because the field is not required.
However, this creates another column Book_Id1, index and foreign key in the database, rather than adding cascading deletes on the existing FK, because it's defined twice.
If I comment out the Book.Pages property, it works, but I lose the ability to call book.Pages and have to instead call dbcontext.Pages.Where(p => p.Book.Id == book.Id), which is not ideal because I don't want the calling code to have to know about the dbcontext object.
Is there a way to have both the Book.Pages property and cascading deletes? Perhaps setting both to use the same FK name?
here what you can do
public class Book
{
[Key]
public Guid Id { get; set; }
public virtual List<Page> Pages { get; set; }
}
public class Page
{
[Key]
public Guid Id { get; set; }
public Guid BookId { get; set;}
//[ForeignKey("BookId")] you can add the fluent here or during entity builder
public virtual Book Book { get; set; }
}
modelBuilder.Entity<Page>()
.HasOptional(a => a.Book)
.WithMany(a=>a.Pages)
.HasForeignKey(a=>a.BookId)
.WillCascadeOnDelete(true);
var pages= dbcontext.Pages.Where(p => p.BookId == book.Id); // this will work
this code should work normally for you
i think in codefirst you have to try this
dbcontext.Page.RemoveRange(book.Pages);
dbcontext.Book.Remove(book);
dbContext.SaveChanges();
I'm just ramping up on MVC 4 and have encountered an error in my application that I need some assistance in fixing.
I have an Author & Book table. The Author table is the parent, and you can have multiple Books associated with each author.
Everything is working well until I try to delete an Author that still has Books assigned to him. When that happens, I receive an error at SaveChanges() stating:
The DELETE statement conflicted with the REFERENCE constraint
"FK_Book_Author".
The error makes perfect sense, but I would like the application to give a nice error message to the users rather than simply exploding.
How do I go about defining this relationship in the model so it doesn't cause the application to explode when you delete a record with children associated to it?
Author Class
public partial class Author
{
public Author()
{
this.Book = new HashSet<Book>();
}
[Key]
public int AuthorId { get; set; }
[Required]
public string AuthorName { get; set; }
public virtual Book Book { get; set; }
}
Book Class
public partial class Book
{
[Key]
public int BookId { get; set; }
[Required]
public string BookName { get; set; }
[Required]
[ForeignKey("Author")]
public string AuthorId { get; set; }
}
Model
I have recently started attempting to override OnModelCreating, but it appears to have no affect.
public partial class BookEntities : DbContext
{
public BookEntities()
: base("name=BookEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>().HasRequired(p => p.Author)
.WithMany(b => b.Books)
.HasForeignKey(p => p.AuthorId);
modelBuilder.Entity<Author>()
.HasMany(p => p.Books)
.WithRequired(b => b.Author)
.WillCascadeOnDelete(false);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
}
Partial Updates
This is an issue regarding 0..1-To-Many relationships
I am using an .edmx. I've just learned that this negates the OnModelCreating method completely.
It appears that I can throw a Linq statement into the DeleteConfirmed method to block this from crashing, but I really do not like that approach.
I generally do not use the attributes if I am also using the fluent API, so this code will be entirely convention and fluent API.
Your post does not specify, but the classic book=>authors model is a many to many relationship. Your Entity code seems to be saying that there is exactly one author and exactly one book, while your fluent code seems to be implying that there are collections of Books and Authors.
FWIW, here is what I would Have:
public class Author
{
public int AuthorId { get; set; }
public string AuthorName { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
public class Book
{
public int BookId { get; set; }
public string BookName { get; set; }
public virtual ICollection<Author> Authors { get; set; }
}
and in the Override:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Author>()
.HasMany(a => a.Books)
.WithMany(b => b.Authors)
.Map(m =>
{
m.ToTable("AuthorBooks");
m.MapLeftKey("AuthorId");
m.MapRightKey("BookId");
});
}
This will yield the db of:
I believe that is all you would need to get the cascade, by convention it should perform the cascade.
Please post if this is not what you were looking for.
Tal
Solution
After spending a day struggling on this one issue, I decided to put some custom code into the DeleteConfirmed method to prevent the error from arising & to alert the user that this record could not be removed.
This is probably not the best way to handle the situation, but it is functional.
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Author author = db.Authors.Find(id);
// Count # of Books for this Author
int count = (from book in db.Books
where book.BookId == id
select book.BookId).Count();
// Prevent deletion of this record has any associated records
if (count> 0)
{
TempData["errors"] = "Bad user! You can't delete this record yet!";
return RedirectToAction("Delete");
}
else
{
db.Categories.Remove(category);
db.SaveChanges();
return RedirectToAction("Index");
}
}
Hi I try use Many to Many relationship with EF Fluent API. I have 2 POCO classes.
public class Project
{
public int ProjectId { get; set; }
public virtual ICollection<Author> Authors { get; set; }
public Project()
{
Authors = new List<Author>();
}
}
public class Author
{
public int AuthorId { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public Author()
{
Projects = new List<Project>();
}
}
And I map many to many relationship with this part of code:
////MANY TO MANY
modelBuilder.Entity<Project>()
.HasMany<Author>(a => a.Authors)
.WithMany(p => p.Projects)
.Map(m =>
{
m.ToTable("ProjectAuthors");
m.MapLeftKey("ProjectId");
m.MapRightKey("AuthorId");
});
This created table ProjectsAuthors in DB. It is my first attempt with this case of relationship mapping.
If I omitted this mapping it created table AuthorProject with similar schema. It is correct bevahior?
By trial and error I found the following. Given two classes...
public class AClass
{
public int Id { get; set; }
public ICollection<BClass> BClasses { get; set; }
}
public class BClass
{
public int Id { get; set; }
public ICollection<AClass> AClasses { get; set; }
}
...and no Fluent mapping and a DbContext like this...
public class MyContext : DbContext
{
public DbSet<AClass> AClasses { get; set; }
public DbSet<BClass> BClasses { get; set; }
}
...the name of the created join table is BClassAClasses. If I change the order of the sets...
public class MyContext : DbContext
{
public DbSet<BClass> BClasses { get; set; }
public DbSet<AClass> AClasses { get; set; }
}
...the name of the created join table changes to AClassBClasses and the order of the key columns in the table changes as well. So, the name of the join table and the order of the key columns seems to depend on the order in which the entity classes are "loaded" into the model - which can be the order of the DbSet declarations or another order if more relationship are involved - for example some other entity refering to AClass.
In the end, it doesn't matter at all, because such a many-to-many relationship is "symmetric". If you want to have your own name of the join table, you can specify it in Fluent API as you already did.
So, to your question: Yes, naming the join table AuthorProjects is correct behaviour. If the name had been ProjectAuthors it would be correct behaviour as well though.