Since the many-to-many relationship is not supported in Entity Framework 7 yet,
I'm following work around in this link.
Here is the code from the link above:
class MyContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PostTag>()
.HasKey(t => new { t.PostId, t.TagId });
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId);
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId);
}
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class Tag
{
public string TagId { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class PostTag
{
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
Question
How can I associate a Tag to a Post?
In other words how can I add a row to the junction table?
Here's my answer to another SO question where the method I came up with is explained.
Create your Tag instance and add it to Tags. Do the same with the Post instance, initializing its PostTags navigation property, and add it to Posts.
Then create the PostTag instance, initializing it with both Post and Tag instances.
Finally call SaveChanges or SaveChangesAsync methods.
This will allow EF to properly manage ID properties before writing all the involved entities to the underlying DB.
Related
I'm trying to configure a relationship between two entities where the parent can have multiple (named) childs from the same type while the child should only have one generic parent.
Based on the Blog/Posts example I'm trying to configure the following. Blogposts aren't a good example but in my application I have 5 fixed child entities.
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public int? FirstPostId { get; set; }
public Post FirstPost { get; set; }
public int? SecondPostId { get; set; }
public Post SecondPost { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int? BlogId { get; set; }
public Blog Blog { get; set; }
}
I tried to configure this like:
modelBuilder
.Entity<Blog>()
.HasOne(_ => _.FirstPost)
.WithOne()
.HasForeignKey<Blog>(_ => _.FirstPostId);
modelBuilder
.Entity<Blog>()
.HasOne(_ => _.SecondPost)
.WithOne()
.HasForeignKey<Blog>(_ => _.SecondPostId);
modelBuilder
.Entity<Post>()
.HasOne(_ => _.Blog)
.WithOne()
.HasForeignKey<Post>(_ => _.BlogId);
In my real world problem a Post additionally does not need to belong to a Blog and all foreign keys are composite keys but that should be no problem.
Configured like that FirstPost and SecondPost are recognized but Post.Blog/Post.BlogId is always null.
Any ideas how to solve this?
This question already has an answer here:
Many to many relationship mapping in EF Core
(1 answer)
Closed 3 years ago.
Dotnet Core 2.2, EntityFrameworkCore 2.2.3
In a Many-to-Many relation between the entities "Post" and "Category" the linked Entity "PostCategory" returns the "Post" object but for the "Category" object only the Id and not the object itself.
Migrations and database update works fine and all three tables are created.
For the relation itself I tried it with EF "auto magic" and explicit definition of the relation in OnModelCreating in the ApplicationDbContext.
Models
Post-Model
public class Post
{
public int Id { get; set; }
public string Slug { get; set; }
public string Title { get; set; }
public string Abstract { get; set; }
public string Content { get; set; }
public string Author { get; set; }
public DateTime Created { get; set; }
public ICollection<PostCategory> PostCategories { get; set; }
}
Category-Model
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<PostCategory> PostCategories { get; set; }
}
PostCategory Model
public class PostCategory
{
public int PostId { get; set; }
public Post Post { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
DbSets in ApplicationDbContext
public DbSet<Post> BlogPosts { get; set; }
public DbSet<Category> BlogCategories { get; set; }
public DbSet<PostCategory> PostCategories { get; set; }
Get all Posts from Service
public IEnumerable<Post> GetAll()
{
var posts = _context.BlogPosts
.Include(x => x.PostCategories);
return posts;
}
Calling service from Controller
public IActionResult Index()
{
var blogPosts2 = _blogService.GetAll();
...
}
The result is seen in the screenshot.
In ApplicationDbContext I tried two versions:
Version 1:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<PostCategory>()
.HasKey(x => new { x.PostId, x.CategoryId });
}
public DbSet<Post> BlogPosts { get; set; }
public DbSet<Category> BlogCategories { get; set; }
public DbSet<PostCategory> PostCategories { get; set; }
Version 2:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<PostCategory>()
.HasKey(x => new { x.PostId, x.CategoryId });
builder.Entity<PostCategory>()
.HasOne(pt => pt.Post)
.WithMany(p => p.PostCategories)
.HasForeignKey(pt => pt.PostId);
builder.Entity<PostCategory>()
.HasOne(pt => pt.Category)
.WithMany(t => t.PostCategories)
.HasForeignKey(pt => pt.CategoryId); ;
}
public DbSet<Post> BlogPosts { get; set; }
public DbSet<Category> BlogCategories { get; set; }
public DbSet<PostCategory> PostCategories { get; set; }
Both version migrate and update with no errors and the same result.
I'm grateful for any help.
Best regards
Edit:
I tried the "ThenInclude" before but obviously my Visual Studio auto completion has a problem:
If I ignore the auto completion, then it works, thank you!
To eager load related data in multiple level, you have to use .ThenInclude as follows:
public IEnumerable<Post> GetAll()
{
var posts = _context.BlogPosts
.Include(x => x.PostCategories)
.ThenInclude(pc => pc.Category);
return posts;
}
Here is the more details: Loading Related Data: Including multiple levels
I'm using EF Core and SQLite in UWP. I tried to migrate by calling DbContext.Database.Migrate() but I always get Microsoft.Data.Sqlite.SqliteException: 'SQLite Error 1: 'table "Tags" already exists'.'.
I'm sure that the table doesn't exist because I checked on bin/debug folder, there is no database file. Even I check in wrong folder, there shouldn't have a problem doesn't it?
I've deleted Migrations folder many times but I don't think this is the cause of this exception.
This is DbContext code.
public class AppDbContext : DbContext
{
public DbSet<Word> Words { get; set; }
public DbSet<WordMeaning> WordMeanings { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<WordTag> WordTags { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=Vocabulary.db");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// PK declaration
modelBuilder.Entity<Word>()
.HasKey(w => w.Text);
modelBuilder.Entity<WordMeaning>()
.HasKey(wm => new { wm.WordText, wm.WordClass });
modelBuilder.Entity<Tag>()
.HasKey(t => t.Name);
modelBuilder.Entity<WordTag>()
.HasKey(wt => new { wt.WordText, wt.TagName });
// relation declaration
modelBuilder.Entity<Word>()
.HasMany(w => w.WordMeanings)
.WithOne(wm => wm.Word)
.HasForeignKey(wm => wm.WordText)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<WordTag>()
.HasOne(wt => wt.Tag)
.WithMany(t => t.WordTag)
.HasForeignKey(wt => wt.TagName);
modelBuilder.Entity<WordTag>()
.HasOne(wt => wt.Word)
.WithMany(w => w.WordTag)
.HasForeignKey(wt => wt.WordText);
}
}
and all of models code.
public class Tag
{
public string Name { get; set; }
public string Description { get; set; }
public List<WordTag> WordTag { get; set; }
}
public class Word
{
public string Text { get; set; }
public WordClass WordClasses { get; set; }
public DateTime AddedDate { get; set; }
public List<WordMeaning> WordMeanings { get; set; }
public List<WordTag> WordTag { get; set; }
}
public class WordMeaning
{
public string WordText { get; set; }
public string Definition { get; set; }
public string Example { get; set; }
public WordClass WordClass { get; set; }
public Word Word { get; set; }
}
public class WordTag
{
public Word Word { get; set; }
public Tag Tag { get; set; }
public string WordText { get; set; }
public string TagName { get; set; }
}
Do not use both EnsureCreated and Migrate, Only Migrate enough to create and migrate the DB
using (var db = new DBContext())
{
//db.Database.EnsureCreated(); Don't use
db.Database.Migrate();
}
#Gert Arnold said, Your SQLite database file (Vocabulary.db) should be created on the LocalFolder by default. You should be able to find the database with Tag table is already created on C:\Users\{username}\AppData\Local\Packages\{your app package name}\LocalState). The package name you can find by Package.appxmanifest->Packing->Package name on your project. More details about the file access on uwp app please reference Files, folders, and libraries.
And more details about entity framework with uwp please reference UWP - New Database.
How can I make many to many relation between AspNetRoles from Identity 3.0 and my custom table? I want simple 3 table, with both PermissionId and RoleId, something like AspNetUsersRole. I have something like this:
public class Permission
{
public int PermissionId { get; set; }
public string Name { get; set; }
public virtual ICollection<ApplicationRole> Roles { get; set; }
}
public class ApplicationRole : IdentityRole
{
public virtual ICollection<Permission> Permissions { get; set; }
}
But when I want to add migration, I got error:
Unable to determine the relationship represented by navigation property 'ApplicationRole.Permissions' of type 'ICollection<Permission>'. Either manually configure the relationship, or ignore this property from the model.
EF Core (EF7) does not currently support many to many relationship without a join entity. (Reference)
So, what you should do is to create an entity class for the join table and mapping two separate one-to-many relationships. Like;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PostTag>()
.HasKey(t => new { t.PostId, t.TagId });
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId);
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId);
}
public class PostTag
{
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
Regarding to this question answer, it can be done more easily like this-
class Photo
{
public int Id { get; set; }
public ICollection<PersonPhoto> PersonPhotos{ get; set; }
}
class PersonPhoto
{
public int PhotoId { get; set; }
public Photo Photo { get; set; }
public int PersonId { get; set; }
public Person Person { get; set; }
}
class Person
{
public int Id { get; set; }
public ICollection<PersonPhoto> PersonPhotos{ get; set; }
}
Be sure to configure PersonPhoto with a composite key:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PersonPhoto>().HasKey(x => new { x.PhotoId, x.PersonId });
}
To navigate, use a Select:
// person.Photos
var photos = person.PersonPhotos.Select(c => c.Photo);
Add This namespace-
using Microsoft.AspNetCore.Identity;
public class Permission
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PermissionId { get; set; }
public string Name { get; set; }
public string UserIdFK { get; set; } //Foreign Key of Identity tbl
[ForeignKey("UserIdFK")]
public IdentityUser UserDetail { get; set; }
}
That's it, Happy coding :)
I am building ASP.NET webforms application using Entity Framework 6.1, with code-first approach to generate database. I have two tables, Product and Tags, in many-to-many relationship. Classes are below:
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products{ get; set; }
}
I want two junction tables out of this relationship ProductTags and ProductTagsTradeFor. So I overrided OnModelCreating of WebsiteDbContext.
modelBuilder.Entity<Product>().HasMany<Tag>(s => s.Tags).WithMany(c => c.Products)
.Map(cs =>
{
cs.MapLeftKey("ProductId");
cs.MapRightKey("TagId");
cs.ToTable("ProductTags");
});
modelBuilder.Entity<Product>().HasMany<Tag>(s => s.Tags).WithMany(c => c.Products)
.Map(cs =>
{
cs.MapLeftKey("ProductId");
cs.MapRightKey("TagId");
cs.ToTable("ProductTradeForTags");
});
After running the application, database was created and table ProductTradeForTags is present but table ProductTags was missing.
What is the problem and how do I fix it so both tables are created?
You can't share the navigation properties. You will need to add a second set of navigation collections to each:
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public virtual ICollection<Tag> TradeForTags { get; set; }
}
public class Tag
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products{ get; set; }
public virtual ICollection<Product> TradeForProducts{ get; set; }
}
Then
modelBuilder.Entity<Product>().HasMany(s => s.Tags).WithMany(c => c.Products)
.Map(cs =>
{
cs.MapLeftKey("ProductId");
cs.MapRightKey("TagId");
cs.ToTable("ProductTags");
});
modelBuilder.Entity<Product>().HasMany(s => s.TradeForTags).WithMany(c => c.TradeForProducts)
.Map(cs =>
{
cs.MapLeftKey("ProductId");
cs.MapRightKey("TagId");
cs.ToTable("ProductTradeForTags");
});
Your model require to navigate from a Tag to Products and from a Product to Tags.
In this case one association table is enough.
EF should raise an exception but it simply ignores the first configuration.