Here's my problem:
I have 3 tables in my database
Movies (list of movies)
ID
OriginalTitle
...
Genres (table with all possible genres)
ID
Name
RelatedGenres (those genres that belog to a specific movie and point a specific genre, since movie can have more than 1 genre)
ID
MovieID
GenreID
The relationships are as folows:
Movies.ID -> RelatedGenres.MovieID -> Genres.ID
I have a model with assosiations (navigation properties).
What I get:
class Movie
{
public int ID { get; set; }
public string OriginalTitle { get; set; }
public ObjectCollection<RelatedGenre> RelatedGenres { get; set; }
}
where
class RelatedGenre
{
public int ID { get; set; }
public int MovieID { get; set; }
public ObjectCollection<Genre> Genres { get; set; }
}
What I want:
class Movie
{
public int ID { get; set; }
public string OriginalTitle { get; set; }
public ObjectCollection<Genre> Genres { get; set; }
}
As you can see, i want to skip data from this array of RelatedGenres & just get array of concrete Genres...
How can I achive this?
Thanks in advance =)
You need to remove th ID column of the RelatedGenres table. The join table of many-to-many relationship should only contain the keys of the participating entities.
EF will automatically model the relationship as you have shown in the final code sample.
Related
I have two tables with one-to-one relationship.
public class Name
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Category { get; set; }
public int? NameId { get; set; }
[ForeignKey("NameId ")]
public virtual Name Name { get; set; }
}
I already have data in those tables.
I know the database relations are not supported to be changed.
Is it possible to change one-to-one relationships to many-to-many relationships?
What is the most suitable approach to overcome this requirement?
Yes, you can still change that, using migrations.
Step 1 is to create a linking table, like NameCategories, which looks something like this:
public class NameCategories
{
public int Id { get; set; }
public int NameId { get; set; }
public Name Name { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
Step 2 is to reference this table in the tables you already have. In Name it would look like this
public class Name
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<NameCategory> Categories { get; set; }
}
Step 3 is to add a migration. You'll have some AddColumn() and some DropColumn() statements. In between them, when all the add stuff was executed but the drops not yet, you can add SQL code to carry over all the existing relations into the newly created table. After that, the old data will be deleted by the DropColumn() code. In your example, this would look something like this
INSERT INTO NameCategories (NameId, CategoryId)
SELECT (n.Id, c.Id) FROM Names n
JOIN Categories c on c.NameId = n.Id
WHERE ..
You can execute the SQL in the migration like this:
var sql = #"...";
Sql(sql);
I hope this helps you out!
Context:
I'm building a collections management app for museums. Every entity has a Museum property to tie that entity to the appropriate museum. A user from a given museum should be able to maintain a lexicon through the app for things such as Genres. Likewise, museums can maintain a list of Artists. Watered-down versions of the relevant classes and a diagram of the database so far is below:
public class Museum
{
public Museum()
{
Artists = new List<Artist>();
Genres = new List<Genre>();
}
public int Id { get; set; }
public string Name { get; set; }
public List<Artist> Artists { get; set; }
public List<Genre> Genres { get; set; }
}
public class Artist
{
public int Id { get; set; }
public string Name { get; set; }
public Museum Museum { get; set; }
public int MuseumId { get; set; }
}
public class Genre
{
public int Id { get; set; }
public string Name { get; set; }
public Museum Museum { get; set; }
public int MuseumId { get; set; }
}
Problem:
An artist creates works in a variety of genres, so an artist has many genres. Likewise, a genre is used by many artists. So here I need a many-to-many relationship which I can achieve through EFCore by explicitly defining a joining entity - ArtistGenre. Updated code below:
public class Artist
{
public Artist()
{
ArtistGenres = new List<ArtistGenre>();
}
public int Id { get; set; }
public string Name { get; set; }
public Museum Museum { get; set; }
public int MuseumId { get; set; }
public List<ArtistGenre> ArtistGenres { get; set; }
}
public class Genre
{
public Genre()
{
ArtistGenres = new List<ArtistGenre>();
}
public int Id { get; set; }
public string Name { get; set; }
public Museum Museum { get; set; }
public int MuseumId { get; set; }
public List<ArtistGenre> ArtistGenres { get; set; }
}
public class ArtistGenre
{
public Artist Artist { get; set; }
public int ArtistId { get; set; }
public Genre Genre { get; set; }
public int GenreId { get; set; }
}
And the obligatory: modelBuilder.Entity<ArtistGenre>().HasKey(a => new { a.ArtistId, a.GenreId }); in OnModelCreating in my DbContext.
But this results in a situation where there are multiple cascade delete paths to the same entity:
Introducing FOREIGN KEY constraint 'FK_ArtistGenre_Genres_GenreId' on table 'ArtistGenre' 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.
Is there a trick to getting this to work? Have I designed this wrong? In the end, I'd like to be able to delete a Museum and have all related Genres and Artists (and ArtistGenres) get deleted automatically.
UPDATE
I've been experimenting with different configurations, and I've discovered that if I remove the cascading deletes from ArtistGenre like so:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ArtistGenre>()
.HasOne(a => a.Artist)
.WithMany(m => m.ArtistGenres)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<ArtistGenre>()
.HasOne(a => a.Genre)
.WithMany(g => g.ArtistGenres)
.OnDelete(DeleteBehavior.Restrict);
}
Then I'm able to update the database with the junction table, but I don't get the desired cascading delete effect.
If I add a Musuem and MuseumId property to ArtistGenre like so:
public class ArtistGenre
{
public Museum Museum { get; set; }
public int MuseumId { get; set; }
public Artist Artist { get; set; }
public int ArtistId { get; set; }
public Genre Genre { get; set; }
public int GenreId { get; set; }
}
I can delete a Museum and have all dependent entities (Artist, Genre, and ArtistGenre) automatically deleted. However, I still can't delete an Artist or Genre without first deleting any records where their IDs appear in ArtistGenre.
The report about the cascading is not caused by the Many-to-Many relation between Artists and Genres, but by the one-to-many relations Museum - Artists and Museum - Genres.
The error means that if you would Remove a Museum it would automatically Remove its Artists and its Genres. While deleting a Genre all Artists performing in this Genre should be adjusted, but you are also Deleting Artists because of the removal of the Museum, so we are removing in a Circle.
You can see that the one-to-many relation with the Museum is the problem, by removing the Museum from your DbContext and creating a standard many-to-many between Artists and Genres. This works fine without any attributes or fluent API.
So you have to promise the model builder that you will only remove museums that have no Artists nor Genres anymore. You'll have to remove the Artists and Genres exhibited in a Museum before you can remove the Museum.
I also saw some other strange things which are not necessary using entity framework. I'm not sure if they are needed because of ef-core.
For instance, your Museums has a List of Artists, are you sure that Artists[17] is a meaningful Action? And why are you creating those Lists in the constructor? If you fetch an Artist from the database, these Lists are created by the constructor and immediately replaced by the data retrieved from the database. Thirdly: are these Lists really Lists, or are they merely interfaces to some data fetched from the database? Could it be that the ICollection functionality of the Museum's Artists and Genres is all funcitonality you'll ever use, or do you really plan to use functionality provided by an IList?
If you stick to the entity framework code first conventions, entity framework is perfectly able to determine your primary and foreign keys and relations between tables just by looking at the properties of your classes.
The only thing that entity framework can't detect automatically is that you promise to remove only Museums without Genres and without Artists.
I have tested this all using the following classes:
class Museum
{
public int Id { get; set; }
public string Name { get; set; }
// every Museum has zero or more Artists (one-to-many)
public virtual ICollection<Artist> Artists { get; set; }
// every Museum has zero or more Genres (one-to-many)
public virtual ICollection<Genre> Genres { get; set; }
}
Artists:
class Artist
{
public int Id { get; set; }
// every Artist belongs to one Museum using foreign key:
public int MuseumId { get; set; }
public virtual Museum Museum { get; set; }
// every Artist creates work in zero or more Genres (many-to-many)
public virtual ICollection<Genre> Genres { get; set; }
public string Name { get; set; }
}
Genres:
class Genre
{
public int Id { get; set; }
// every Genre belongs to only one Museum using foreign key
public int MuseumId { get; set; }
public virtual Museum Museum { get; set; }
// every Genre is performed by zero or more Artists (many-to-many)
public virtual ICollection<Artist> Artists { get; set; }
public string Name { get; set; }
}
Finally the DbContext
class MuseumContext : DbContext
{
public DbSet<Museum> Museums { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Genre> Genres { get; set; }
}
Because I stuck to the code-first conventions, this was all that entity framework needed to know to configure the primary and foreign keys, and the one-to-many relations. It even creates an extra junction table for you to represent the many-to-many relation between Artists and Genres.
The only thing we need to do is to tell the modelBuilder not to Cascade On Delete:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var museumEntity = modelBuilder.Entity<Museum>();
museumEntity.HasMany(museum => museum.Genres)
.WithRequired(genre => genre.Museum)
.HasForeignKey(genre => genre.MuseumId)
.WillCascadeOnDelete(false);
museumEntity.HasMany(museum => museum.Artists)
.WithRequired(artist => artist.Museum)
.HasForeignKey(artist => artist.MuseumId)
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
This means: every Museum has a property Genres. Every element in Genre has a non-null Museum, meaning that every Genre is attached to exactly one Museum (nor zero, not two). This attachment is done by the foreign key MuseumId. If you plan to delete a Museum, the database should not delete all its Genres
As a result Genre.MuseumId may not be zero, nor can you delete a Museum while there are still Genres with a MuseumId pointing to this Museum.
I tested this as follows:
Database.SetInitializer<MuseumContext>(new DropCreateDatabaseIfModelChanges<MuseumContext>());
using (var dbContext = new MuseumContext())
{
Genre goldenAge = dbContext.Genres.Add(new Genre() { Name = "Dutch Golden Age Painting" });
Genre renaissance = dbContext.Genres.Add(new Genre() { Name = "Early Renaissance" });
Genre portrets = dbContext.Genres.Add(new Genre() { Name = "Portrets" });
Genre popArt = dbContext.Genres.Add(new Genre() { Name = "Pop Art" });
// The RijksMuseum Amsterdam has a Collection of several genres,
Museum rijksMuseum = dbContext.Museums.Add(new Museum()
{
Name = "RijksMuseum Amsterdam",
Genres = new List<Genre>()
{
goldenAge,
renaissance,
portrets,
},
});
// Rembrandt van Rijn can be seen in the Rijksmuseum:
Artist artist = dbContext.Artists.Add(new Artist()
{
Name = "Rembrandt van Rijn",
Museum = rijksMuseum,
// he painted in several Genres
Genres = new List() {goldenAge, portrets},
});
dbContext.SaveChanges();
}
TODO: see if you can remove a Museum that still has Artists and / or Genres
TODO: check that if you removed all Artists and all Genres from a Museum the Museum can be Removed.
Finally: are you sure that an Artist is only exhibited in one Museum? Is there only one Museum where I can see a Rembrandt?
Similarly: is a Genre only exhibited in one Museum, is there only one Museum that will show Impressionism?
Consider changing these relations to many-to-many. You'll probably won't even have to use the CascadeOnDelete anymore. Besides, if you feel naughty you will be able to destroy Museums that still have Artists in them ;-)
I have a DbContext which I via the developer command prompt and creating a migrations schema turn in to my database. But if you look at the product object I have a dictionary object named Parts. That property does not get added to the Product table when the database is updated in the command prompt. I don't even know if it is possible what I am trying to do.
I want to add a table in the database named Parts and then add a foreign key to the Product table which connects the Parts dictionary object in the Product table, and the the new Parts table. Is this possible with Entity Framework Core?
public class ShoppingDbContext : IdentityDbContext<User>
{
public ShoppingDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
public int CategoryId { get; set; }
Dictionary<string, Part> Parts { get; set; }
}
EF Core can't currently map a dictionary property directly. If you want to create an association between Products and Parts, then define each of them as an entity. You can then create navigation properties between them--a reference from Part to the Product which it belongs, and a collection of Parts on Product. For example:
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
public int CategoryId { get; set; }
public ICollection<Part> Parts { get; set; }
}
public class Part
{
public int PartId { get; set; }
public int ProductId { get; set; }
public Product Product { get; set;}
}
Part also defines a property ProductId that acts as the FK to the Product entity. You don't need to add that property--EF will simulate it for you if you don't want it, but usually it is easier to deal with entities if the FK is mapped to a property.
Relationships are tracked through object references instead of foreign key properties. This type of association is called an independent association.
More Details Here:
https://msdn.microsoft.com/en-us/data/jj713564.aspx
Sample code:
public partial class Product
{
public Product()
{
this.Parts = new HashSet<Part>();
}
public int ProductId { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
public int CategoryId { get; set; }
public virtual ICollection<Part> Parts { get; set; }
}
Basically like what Arthur said, EF Core does not support it yet.
However, another way is to create a composite table should you want to or if it's viable for your use.
Here's a simple example:
// -------------- Defining BrandsOfCategories Entity --------------- //
modelBuilder.Entity<BrandCategory>()
.HasKey(input => new { input.BrandId, input.CatId })
.HasName("BrandsOfCategories_CompositeKey");
modelBuilder.Entity<BrandCategory>()
.Property(input => input.DeletedAt)
.IsRequired(false);
// -------------- Defining BrandsOfCategories Entity --------------- //
public class BrandCategory
{
public int CatId { get; set; }
public int BrandId { get; set; }
public DateTime? DeletedAt { get; set; }
public Category Category { get; set; }
public Brands Brand { get; set; }
}
The DeletedAt is optional of course. This handles M-M Relationships.
I had the same issue, I resolved it by removing the keyword virtual on the navigation properties and with in the ApplicatinDbContext
I have been trying to work this out for a while now and can't find an answer that makes sense to me. The concept is very common, so I must be totally misunderstanding a basic concept.
If I have a recipe class that can be found in many recipe categories then I have;
public class Recipe
{
public int ID { get; set; }
public string Title { get; set; }
public int CategoryID { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public int ID { get; set; }
public string Description { get; set; }
public int RecipeID {get; set;
public virtual void Recipe Recipe {get; set;}
But I also need a join table that relates a recipe to a Category. I want to display this;
Recipe Title | Category
Mac-N-Cheese | Pasta
| Easy
Pot Roast | Beef
| Slow cooker
The Category is a table of available categories. So the join table has
RecipeID | CategoryID
I tried setting up the models using the Entity Framework format of foreign keys and navigation properties.
So I set up the join table like this;
public class RecipeCategories
{
public int ID { get; set; }
public int RecipeID { get; set; }
public int CategoryID { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
So a recipe can have many categories. The join table can have many recipes and many categories. The category table is just a simple list of categories. What am I missing? When I try to run the view there is no list of categories for the given recipe. The best I have achieved is the CategoryID.
Sorry for the long post, but you need all the details.
The problem is your Category model. The way you've currently defined it you have a one-to-many relationship between Category and Recipe (a Recipe can have many Categories but a Category one has a single Recipe). What you want is a many-to-many relationship so put a collection of Recipes on the Category and EF should automatically generate the join table.
public class Recipe
{
public int ID { get; set; }
public string Title { get; set; }
public int CategoryID { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public int ID { get; set; }
public string Description { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
}
I am no getting actual scenario as you trying to achieve.
as you mention your model and then what you expected both contradict. as per you mention you have recipe and category (one to many) but later you change your model and mention you want (many to many) so you need join table. as a relation model you can handle 2 ways in EF. 1st without creating separate table and keep collection map to each model and 2nd way creating separate table and explicitly map that in your model. you need to explicitly specify while model building.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Recipe>()
.HasMany(c => c.Categories).WithMany(i => i.Recipes)
.Map(t => t.MapLeftKey("RecipeID")
.MapRightKey("CategoryID")
.ToTable("ReceipeCategory")
);
you can define your class model (and third table will generate but don't required specific model )
class Recipe {.... public virtual ICollection<Category> Categories { get; set; }
class Category {... public virtual ICollection<Recipe> Recipes { get; set; } }
I have the Student, Course and a relationship between them as StudentCourse. The fields in these classes are as follows:
public class Student
{
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int CourseId { get; set; }
[ForeignKey("CourseId")]
public Course Course { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string CourseName { get; set; }
}
public class StudentCourse
{
public int ID { get; set; }
public virtual Student Student { get; set; }
public virtual Course Course {get;set;}
}
When I delete the students in student table , then I want to remove the corresponding rows from the relationship StudentClass. How can I do it?
I believe that you actually want a many-to-many relationship between Student and Course: A student can participate in many courses and a course can have many students.
In this case you can simplify your model:
public class Student
{
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Course> Courses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string CourseName { get; set; }
public ICollection<Student> Students { get; set; }
}
The "join entity" StudentCourse is not needed. EF will create three tables from this model: A Students table, a Courses table and StudentCourses (or CourseStudents) table that will have a composite primary key (StudentId, CourseId) (or named similar). Both parts are foreign keys to their respective tables.
For the two FK relationships in the database cascading delete will be turned on by default. So, if a Student gets deleted the link records in the join table will be deleted automatically. The same when a Course gets deleted.
You can also define the detailed names for join table and join table columns explicity and you can also work with only a single collection, for example only the Courses collection in Student but without the Students collection in Course. You must use Fluent API for this:
modelBuilder.Entity<Student>()
.HasMany(s => s.Courses)
.WithMany() // no parameter if there is no collection in Course
.Map(m =>
{
m.MapLeftKey("StudentId");
m.MapRightKey("CourseId");
m.ToTable("StudentCourses");
});