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();
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'm struggling with composite keys and extra fields being generated by Entity Framework. I have a question about something which I think is odd.
Let's say I have a one to many relationships with these classes:
File (dossier)
[Table("Dossier")]
public class Dossier
{
[Key]
public string Dossiernummer { get; set; }
[Key]
public string Dossierversie { get; set; }
[Required]
public string Dossierreferentie { get; set; }
[Required]
public string Relatienr { get; set; }
public ICollection<Artikel> Artikels { get; set; } ();
}
And my artikel (article) class:
[Table("Artikel")]
public class Artikel
{
[Key]
public string Artnr { get; set; }
[Key]
public string ArtVersie { get; set; }
public string ArtOmschrijving { get; set; }
public Dossier Dossier { get; set; }
public string Dossiernummer { get; set; }
}
I'm using migrations and a code first approach. For some reason using migrations creates a dossiernummer1 column in the artikel table. I don't understand why and would like it gone. Does anyone know how?
Another thing which I prefer not to have is the second primary key in my artikel table. It puts both keys from the dossier table in the artikel table yet I only want to use Dossiernummer as a foreign key. Do you know how to change this?
When getting all the dossiers from the context I notice something odd as well. When I look into a dossier object the artikels list is empty, even though data exists in the database for that. Is it normal you have to initialize it yourself?
Thanks for any help and info in advance.
Kind regards,
you must use fluent API for set relations and add ColumnAttribute to order keys :
[Table("Artikel")]
public class Artikel
{
[Key]
[Column(Order = 1)]
public string Artnr { get; set; }
[Key]
[Column(Order = 2)]
public string ArtVersie { get; set; }
public string ArtOmschrijving { get; set; }
public Dossier Dossier { get; set; }
public string Dossiernummer { get; set; }
}
[Table("Dossier")]
public class Dossier
{
[Key]
[Column(Order = 1)]
public string Dossiernummer { get; set; }
[Key]
[Column(Order = 2)]
public string Dossierversie { get; set; }
[Required]
public string Dossierreferentie { get; set; }
[Required]
public string Relatienr { get; set; }
public ICollection<Artikel> Artikels { get; set; }
}
in your dbcontext override OnModelCreating method:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
builder.Entity<Dossier>()
.HasMany(x => x.Artikels)
.WithOne(a => a.Dossier)
.HasForeignKey(a => new { a.Dossiernummer, a.Artnr });
builder.Entity<Artikel>()
.HasKey(x => new {x.Artnr,x.ArtVersie});
builder.Entity<Dossier>()
.HasKey(x => new {x.Dossiernummer,x.Dossierversie});
}
Dossiernummer1:
Artikel has a Dossier. EF knows an K relationship must be set up. This requires that the PK of Dossier must be included in Artikel and wants to add it. It finds you've already put in a field with that name (what for it has no idea) and so it adds it as Dossiernummer1. You should not add Dossiernummer to Artikel - unless you you actually need one for something else - as the only reason it's there is to be an FK. EF will take care of that for you.
Adding Dossierversie to Artikel:
It thinks that the PK of Dossier is Dossiernummer + Dossierversie, and so to point to the correct Dossier it must have both of tem. I don't use code-first so I can't advise you on a) how to specify a PK and another, separate index (I assume that's what you want) versus a compound PK (which is what you appear to have).
Dossier.Artikels is empty: That's the way EF works, known as lazy loading. It gets the 'root' objects by not anything owned by them at first. Once your code accesses an Artikels collection it should load them (for that Dossier) at that point. This prevents EF pulling in what could be a large percentage of your database Imaging an ECommerce system. Getting a Customer list would pull in all Orders related to cutomers in that list; all order lines owned by those orders; all Product data related to the products on those order lines and so on. This would not be a good thing. Instead it just gets the things you've specifically mentioned and then pulls in related items as needed.
Incidentally, when looking as an unloaded collection as an attribute of the owner (e.g. looking at Artikels on a loaded Dossier), the debugger in VS tells me that examining the collection will result in it being loaded and gives me the option to continue or not.
I'm using entity framework code first approach
I have a class
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public Person Director { get; set; }
public virtual ICollection<Person> Actors { get; set; }
}
and a class
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
When the database is created I get one table Movies with Id, Title, Director_Id and a table Person with Id and Name.
I expect to have a table Movies_Persons with columns Movie_Id and Actor_Id
How can I achieve this?
Your Problem is, that you don`t tell the Person Class, that there can be multiple Movies per person.
So by adding the following line in your person class:
public virtual ICollection<Movie> Movies { get; set; }
Your entity knows that both your classes can have multiple references to the other class.
To fulfill this requirement Entity Framework will create a third table with Movie_ID and Person_ID.
If you want more informations just look for:
Entity Framework - Many to many relationship
or follow this link:
http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
You can check out the other articels on that page too, if you are new to entity framework.
UPDATE:
Sorry i missed, that you are already have another reference to your person table.
Here you have to tell your entity framework, which way you want to reference the two tables by fluent api.
Check out this stackoverflow answer. That should do the trick.
You have to insert this code into your OnModelCreating Function of your DbContext Class.
So your final code should look like this:
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public virtual Person Director { get; set; }
public virtual ICollection<Person> Actors { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Movie> Movies_Actors { get; set; }
public virtual ICollection<Movie> Movies_Directors { get; set; }
}
And in your OnModelCreating add following code:
modelBuilder.Entity<Movie>()
.HasMany(a => a.Actors)
.WithMany(a => a.Movies_Actors)
.Map(x =>
{
x.MapLeftKey("Movie_ID");
x.MapRightKey("Person_ID");
x.ToTable("Movie_Actor");
});
modelBuilder.Entity<Movie>()
.HasRequired<Person>(s => s.Director)
.WithMany(s => s.Movies_Directors);
I don't have the possibility to test the code, but that should do the trick.
If you have to do some adjustments to make it work, plz add them in the comments, so other ppl can benefit from it.
When using data annotations with EF4.1 RC is there an annotation to cause cascade deletes?
public class Category
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public Category Category { get; set; }
}
Using this model the constraint generated is:
ALTER TABLE [Product] ADD CONSTRAINT [Product_Category]
FOREIGN KEY ([Category_Id]) REFERENCES [Categorys]([Id])
ON DELETE NO ACTION ON UPDATE NO ACTION;
If not how is it achieved?
Putting required on the Product table Category relationship field solves this
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
[Required] //<======= Forces Cascade delete
public Category Category { get; set; }
}
I like to turn off cascade delete by default (by removing the OneToManyCascadeDeleteConvention)
I was then hoping to add them back in via annotations, but was surprised that EF doesn't include a CascadeDeleteAttribute.
After spending way too long working around EF's ridiculous internal accessor levels, the code in this gist adds a convention that allows attributes to be used: https://gist.github.com/tystol/20b07bd4e0043d43faff
To use, just stick the [CascadeDelete] on either end of the navigation properties for the relationship, and add the convention in your DbContext's OnModeCreating callback. eg:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Add<CascadeDeleteAttributeConvention>();
}
And in your model:
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[CascadeDelete]
public List<BlogPostComment> Comments { get; set; }
}
Not sure on Data Annotations, but you can add it in the database by modifying the actual relationship.
Looks like the answer is no for data annotations:
http://social.msdn.microsoft.com/Forums/en-US/adonetefx/thread/394821ae-ab28-4b3f-b554-184a6d1ba72d/
This question appears to show how to do it with the fluent syntax, but not sure if that applies for 4.1 RC
EF 4.1 RC: Weird Cascade Delete
As an additional example to Tyson's answer, I use the [CascadeDelete] attribute like follows in an entity, which successfully adds the "Cascade" delete rule to the Parent-Child relation.
public class Child
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
[SkipTracking]
public Guid Id { get; set; }
[CascadeDelete]
public virtual Parent Parent { get; set; }
[Required]
[ForeignKey("Parent")]
public Guid ParentId { get; set; }
}
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");
}
}