I have two simple model classes.
Service
Attendee
Each service can be attended by 1 or more people. I want Create and Edit views based on service where all attendees display as a check box and when any of the selected/unselected it should add or remove from the corresponding service in the database.
I have been trying to build it for more than a week now but no success!
any help will be highly appreciated.
My code is as follows for these classes.
public class Service
{
public int ServiceID { get; set; }
public string Problem { get; set; }
public virtual ICollection<Attendie> AttendedBy { get; set; }
}
public class Attendie
{
public int AttendieID { get; set; }
public string Name { get; set; }
public virtual Service Service { get; set; }
}
public class ServiceAttendiesDBContext: DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
public DbSet<Service> Services { get; set; }
public DbSet<Attendie> Attendies { get; set; }
}
public class ServiceAttendiesInitializer : DropCreateDatabaseAlways<ServiceAttendiesDBContext>
{
protected override void Seed(ServiceAttendiesDBContext context)
{
base.Seed(context);
var attendies = new List<Attendie>
{
new Attendie{AttendieID=1,Name="Attendie1"},
new Attendie{AttendieID=2,Name="Attendie2"},
new Attendie{AttendieID=3,Name="Attendie3"}
};
attendies.ForEach(at => context.Attendies.Add(at));
context.SaveChanges();
var services = new List<Service>
{
new Service{ ServiceID=1,Problem="Problem1",AttendedBy=new List<Attendie>()},
new Service{ ServiceID=2,Problem="Problem2",AttendedBy=new List<Attendie>()},
new Service{ ServiceID=3,Problem="Problem3",AttendedBy=new List<Attendie>()}
};
services.ForEach(ser => context.Services.Add(ser));
context.SaveChanges();
services[0].AttendedBy.Add(attendies[0]);
services[0].AttendedBy.Add(attendies[1]);
services[1].AttendedBy.Add(attendies[2]);
services[1].AttendedBy.Add(attendies[2]);
services[2].AttendedBy.Add(attendies[1]);
context.SaveChanges();
}
Related
So I have the following entities defined.
internal class DeliveryArea
{
public string Postcode { get; set; }
public string State { get; set; }
public string Country { get; set; }
public ICollection<DeliveryPrice> HasDeliveryPrices { get; set; }
}
internal class DeliveryPrice
{
public uint Id { get; set; }
public DeliveryArea ForDeliveryArea { get; set; }
public string DeliveryAreaPostcode { get; set; }
public string DeliveryAreaState { get; set; }
public string DeliveryAreaCountry { get; set; }
}
and my DbContext is as follow
internal class MyDbContext : DbContext
{
// DbSets.
public DbSet<DeliveryArea> DeliveryAreas { get; set; }
public DbSet<DeliveryPrice> DeliveryPrices { get; set; }
// Overrides.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(#"Data Source=Test.EFCore.db;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#region DeliveryArea.
{
var entity = modelBuilder.Entity<DeliveryArea>();
// Setup case-insensitive columns.
entity.Property(i => i.Postcode).HasColumnType("TEXT COLLATE NOCASE");
entity.Property(i => i.State).HasColumnType("TEXT COLLATE NOCASE");
entity.Property(i => i.Country).HasColumnType("TEXT COLLATE NOCASE");
// Setup composite PK.
entity.HasKey(nameof(DeliveryArea.Postcode), nameof(DeliveryArea.State), nameof(DeliveryArea.Country));
}
#endregion
#region DeliveryPrice.
{
var entity = modelBuilder.Entity<DeliveryPrice>();
// DeliveryPrice x DeliveryArea | many-to-one
entity.HasOne(left => left.ForDeliveryArea)
.WithMany(right => right.HasDeliveryPrices)
.HasForeignKey(left => new { left.DeliveryAreaPostcode, left.DeliveryAreaState, left.DeliveryAreaCountry });
}
#endregion
}
}
When the database is generated, EF Core manage to generate appropriate FK that connects both table using the composite key. Everything looks fine and the diagram looks great.
Now, I added the following entity
internal class Currency
{
public uint Id { get; set; }
public ICollection<DeliveryPrice> ForDeliveryPrices { get; set; }
}
and updated DeliveryPrice class as follow
internal class DeliveryPrice
{
public uint Id { get; set; }
// Add the following
public Currency HasCurrency { get; set; }
public uint HasCurrencyId { get; set; }
public DeliveryArea ForDeliveryArea { get; set; }
public string DeliveryAreaPostcode { get; set; }
public string DeliveryAreaState { get; set; }
public string DeliveryAreaCountry { get; set; }
}
My DbContext is updated on top of existing, as follow
internal class MyDbContext : DbContext
{
// DbSets.
public DbSet<Currency> Currencies { get; set; }
// Existing codes remain...
// Overrides.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#region Currency.
{
var entity = modelBuilder.Entity<Currency>();
// Currency x DeliveryPrice | one-to-many
entity.HasMany(left => left.ForDeliveryPrices)
.WithOne(right => right.HasCurrency)
.HasForeignKey(right => right.HasCurrencyId);
}
#endregion
// Existing codes remain...
}
}
When the new database is generated, the FK that connects both DeliveryArea and DeliveryPrice table is kinda splitted into 2, as follow
The funny thing is that when the Currencies table is renamed to, say Foo, the FK that connects both DeliveryArea and DeliveryPrice table looks OK.
UPDATE 01:
Normal looking FK
Here's a screenshot of the generated FK that splitted into 2
UPDATE 02:
Upon looking further into the issue, I've found that this is specific to DBeaver only. Viewing the same database file with other database viewer (e.g. DbSchema) does not have the issue.
Any idea what's going on?
I have the following DbContext in a .net5 winforms EF XAF 21.2.5 standard security application created with the wizard and it runs OK.
[TypesInfoInitializer(typeof(DXApplication42ContextInitializer))]
public class DXApplication42EFCoreDbContext : DbContext {
public DXApplication42EFCoreDbContext(DbContextOptions<DXApplication42EFCoreDbContext> options) : base(options) {
}
public DbSet<ModuleInfo> ModulesInfo { get; set; }
public DbSet<ModelDifference> ModelDifferences { get; set; }
public DbSet<ModelDifferenceAspect> ModelDifferenceAspects { get; set; }
public DbSet<PermissionPolicyRole> Roles { get; set; }
public DbSet<DXApplication42.Module.BusinessObjects.ApplicationUser> Users { get; set; }
public DbSet<DXApplication42.Module.BusinessObjects.ApplicationUserLoginInfo> UserLoginInfos { get; set; }
public DbSet<ReportDataV2> ReportDataV2 { get; set; }
//public DbSet<DtoNum> TempNums { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<DXApplication42.Module.BusinessObjects.ApplicationUserLoginInfo>(b => {
b.HasIndex(nameof(DevExpress.ExpressApp.Security.ISecurityUserLoginInfo.LoginProviderName), nameof(DevExpress.ExpressApp.Security.ISecurityUserLoginInfo.ProviderUserKey)).IsUnique();
});
//modelBuilder.Entity<DtoNum>().HasNoKey();
}
}
public class DtoNum
{
public int Id { get; set; }
}
However if I uncomment the 2 commented lines I get an error
System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=DevExpress.ExpressApp.EFCore.v21.2
StackTrace:
at DevExpress.ExpressApp.EFCore.EFCoreTypeInfoSource.SetupKeyMembers(TypeInfo typeInfo)
at DevExpress.ExpressApp.EFCore.EFCoreTypeInfoSource.RegisterEntity(Type type)
at DevExpress.ExpressApp.EFCore.TypeInfoSourceHelper.InitTypeInfoSource(EFCoreObjectSpaceProvider objectSpaceProvider)
at DevExpress.ExpressApp.EFCore.EFCoreObjectSpaceProvider..ctor(IDbContextFactory`1 dbContextFactory, ITypesInfo typesInfo)
at DevExpress.EntityFrameworkCore.Security.SecuredEFCoreObjectSpaceProvider..ctor(ISelectDataSecurityProvider selectDataSecurityProvider, IDbContextFactory`1 dbContextFactory, ITypesInfo typesInfo)
at DXApplication42.Win.DXApplication42WindowsFormsApplication.CreateDefaultObjectSpaceProvider(CreateCustomObjectSpaceProviderEventArgs args) in C:\Users\kirst\source\repos\DXApplication42\DXApplication42.Win\WinApplication.cs:line 34
Shown as
Looking inside EFCoreTypeInfoSources I see
private void SetupKeyMembers(TypeInfo typeInfo) {
IKey key = EntityTypes[typeInfo.FullName].FindPrimaryKey();
typeInfo.KeyMember = null;
foreach(IProperty property in key.Properties) {
typeInfo.AddKeyMember(typeInfo.FindMember(property.Name));
}
}
I want to use the non table based DbSet as explained here.
I posted the code to GitHub
I think the problem is that not all entities have a key so FindPrimaryKey would return null causing SetupKeyMembers to fail.
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 the ASP Net Core 2.
I have a test model:
public class Player
{
public int Id { get; set; }
public string Name { get; set; }
public string Position { get; set; }
public int Age { get; set; }
[IgnoreDataMember]
public ICollection<PlayerTeam> PlayerTeams { get; set; }
public Player()
{
PlayerTeams = new List<PlayerTeam>();
}
}
public class PlayerTeam
{
public int PlayerId { get; set; }
public Player Player { get; set; }
public int TeamId { get; set; }
public Team Team { get; set; }
}
public class Team
{
public int Id { get; set; }
public string Name { get; set; } // название команды
// [IgnoreDataMember]
public ICollection<PlayerTeam> PlayerTeams { get; set; }
public Team()
{
PlayerTeams = new List<PlayerTeam>();
}
}
this is my DBcontext:
public class FootbollContext: DbContext
{
public DbSet<Player> Players { get; set; }
public DbSet<Team> Teams { get; set; }
public FootbollContext(DbContextOptions<FootbollContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PlayerTeam>()
.HasKey(t => new { t.TeamId, t.PlayerId });
}
}
I have a query in my controller:
FootbollContext db;
var teams = db.Teams.Select(team => new {
TeamName = team.Name,
PlayersOlder20 = team.PlayerTeams.Where(pt => pt.Player.Age > 20).Select(s => s.Player)
});
and it works fine, but I want to use the Include()/ThenInclude() methods for this query, and I want to get the same equal results ie.
var teams = db.Teams.Include(p => p.PlayerTeams).ThenInclude(d => d.Player)
but I don't want to load all data! and I don't know how I can filter results by property "Players age (> 20)" in the relative table (not in the selectable!!) in one SQL Query.
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 :)