I am trying to create a model which has two time many to many relation.
StockItem * - * QualityCheckDefinition
Article * - * QualityCheckDefinition
Many to many classes:
public class StockItemQualityCheckDefinition
{
public StockItem StockItem { get; set; }
public QualityCheckDefinition QualityCheckDefinition { get; set; }
public int StockItemId { get; set; }
public int QualityCheckDefinitionId { get; set; }
}
public class ArticleQualityCheckDefinition
{
public Article Article { get; set; }
public QualityCheckDefinition QualityCheckDefinition { get; set; }
public string ArticleId { get; set; }
public int QualityCheckDefinitionId { get; set; }
}
QualityCheckDefinition class:
public class QualityCheckDefinition : Entity<int>
{
public List<StockItemQualityCheckDefinition> StockItemQualityCheckDefinitions { get; set; }
public List<ArticleQualityCheckDefinition> ArticleQualityCheckDefinitions { get; set; }
}
Article class:
public class Article : Entity<string>
{
public ICollection<ArticleQualityCheckDefinition> ArticleQualityCheckDefinitions { get; set; }
}
StockItem class:
public class StockItem : Entity<int>
{
public List<StockItemQualityCheckDefinition> StockItemQualityCheckDefinitions { get; set; }
}
The mappings:
public class ArticleQualityCheckDefinitionMap : IEntityTypeConfiguration<ArticleQualityCheckDefinition>
{
public void Configure(EntityTypeBuilder<ArticleQualityCheckDefinition> builder)
{
builder.HasKey(x => new { x.ArticleId, x.QualityCheckDefinitionId });
builder.HasOne(x => x.Article)
.WithMany(x => x.ArticleQualityCheckDefinitions)
.HasForeignKey(x => x.ArticleId)
.HasPrincipalKey(x => x.Id)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(x => x.QualityCheckDefinition)
.WithMany(x => x.ArticleQualityCheckDefinitions)
.HasForeignKey(x => x.QualityCheckDefinitionId)
.HasPrincipalKey(x => x.Id)
.OnDelete(DeleteBehavior.Restrict);
}
}
public class StockItemQualityDefinitionMap : IEntityTypeConfiguration<StockItemQualityCheckDefinition>
{
public void Configure(EntityTypeBuilder<StockItemQualityCheckDefinition> builder)
{
builder.HasKey(x => new { x.StockItemId, x.QualityCheckDefinitionId });
builder.HasOne(x => x.StockItem)
.WithMany(x => x.StockItemQualityCheckDefinitions)
.HasForeignKey(x => x.StockItemId);
builder.HasOne(x => x.QualityCheckDefinition)
.WithMany(x => x.StockItemQualityCheckDefinitions)
.HasForeignKey(x => x.QualityCheckDefinitionId);
}
}
The relationships are configured fine and they are working. However migration creates additional unnesesary fields inside QualityCheckDefinition class. Migration wants to add both nullable StockItemId and ArticleId into QualityCheckDefinition class.
There is part of migration:
migrationBuilder.CreateTable(
name: "QualityCheckDefinitions",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
//other properties
ArticleId = table.Column<string>(nullable: true),
StockItemId = table.Column<int>(nullable: true)
},
Why does it add these unwanted keys?
PS. I am using EF Core 2.2
Where is your configuration mapping for StockItem and Article? And I think the way you used .HasPrincipalKey() inside ArticleQualityCheckDefinitionMap is not right.
Principal Key
If you want the foreign key to reference a property other than the primary key, you can use the Fluent API to configure the principal key property for the relationship. The property that you configure as the principal key will automatically be setup as an alternate key.
Your ArticleQualityCheckDefinitionMap
builder.HasOne(x => x.Article)
.WithMany(x => x.ArticleQualityCheckDefinitions)
.HasForeignKey(x => x.ArticleId) // <--
.HasPrincipalKey(x => x.Id) // <--
.OnDelete(DeleteBehavior.Restrict);
By conventions, .HasForeignKey(x => x.ArticleId) will target the primary key of the Article entity, string Id. And then you told Entity Framework that you don't want to setup the foreign key to the primary key. You want to target another property called Id by saying .HasPrincipalKey(x => x.Id).
Huh? Aren't they the same? That's why I think Entity Framework got confused. And since you don't have a configuration for Article, Entity Framework tried its best to generate a primary key for you, called "ArticleId"?
You can fix the problem by just adding the configurations back and removing .HasPrincipalKey() there.
StockItemConfiguration
using DL.SO.EFCore.Learning.Data.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace DL.SO.EFCore.Learning.Data.Configurations
{
public class StockItemConfiguration : IEntityTypeConfiguration<StockItem>
{
public void Configure(EntityTypeBuilder<StockItem> builder)
{
builder.HasKey(x => x.Id);
builder.ToTable(nameof(StockItem));
}
}
}
ArticleConfiguration
using DL.SO.EFCore.Learning.Data.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace DL.SO.EFCore.Learning.Data.Configurations
{
public class ArticleConfiguration : IEntityTypeConfiguration<Article>
{
public void Configure(EntityTypeBuilder<Article> builder)
{
builder.HasKey(x => x.Id);
builder.ToTable(nameof(Article));
}
}
}
You might need to configure for QualityCheckDefinition as well:
QualityCheckDefinitionConfiguration
using DL.SO.EFCore.Learning.Data.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace DL.SO.EFCore.Learning.Data.Configurations
{
public class QualityCheckDefinitionConfiguration : IEntityTypeConfiguration<QualityCheckDefinition>
{
public void Configure(EntityTypeBuilder<QualityCheckDefinition> builder)
{
builder.HasKey(x => x.Id);
builder.ToTable(nameof(QualityCheckDefinition));
}
}
}
Then remove .HasPrincipalKey():
ArticleQualityCheckDefinitionConfiguration
using DL.SO.EFCore.Learning.Data.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace DL.SO.EFCore.Learning.Data.Configurations
{
public class ArticleQualityCheckDefinitionConfiguration : IEntityTypeConfiguration<ArticleQualityCheckDefinition>
{
public void Configure(EntityTypeBuilder<ArticleQualityCheckDefinition> builder)
{
builder.HasKey(x => new { x.ArticleId, x.QualityCheckDefinitionId });
builder.HasOne(x => x.Article)
.WithMany(x => x.ArticleQualityCheckDefinitions)
.HasForeignKey(x => x.ArticleId);
builder.HasOne(x => x.QualityCheckDefinition)
.WithMany(x => x.ArticleQualityCheckDefinitions)
.HasForeignKey(x => x.QualityCheckDefinitionId);
builder.ToTable(nameof(ArticleQualityCheckDefinition));
}
}
}
Then you should be fine?
Related
I've got the following problem:
I'm using NPGSQL for the Connection to the Database. Furthermore I'm using Entity Framework Core.
This is how my Context looks like:
using System.IO;
using DatabaseLibrary.Models.Discount;
using DatabaseLibrary.Models.Gender;
using DatabaseLibrary.Models.Ranks;
using DatabaseLibrary.Models.Employee;
using DatabaseLibrary.Models.Items;
using DatabaseLibrary.Models.Settings;
using DatabaseLibrary.Models.Transaction;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace DatabaseLibrary
{
// ReSharper disable once InconsistentNaming
public class PGSQLConnectionManager : DbContext
{
//Variables
private IConfiguration Configuration { get; }
//public PGSQLConnectionManager(DbContextOptions<PGSQLConnectionManager> options) : base(options)
//{
// Configuration = (new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.Development.json").Build());
//}
public PGSQLConnectionManager()
{
Configuration = (new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.Development.json").Build());
}
public PGSQLConnectionManager(IConfiguration configuration)
{
Configuration = configuration;
}
//Database
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseNpgsql(
Configuration["ConnectionStrings:PGSQLConnectionManager"]
);
//Model Sets
public DbSet<Gender> Gender { get; set; }
public DbSet<Gender2Employee> Gender2Employee { get; set; }
public DbSet<EmployeeRank> EmployeeRank { get; set; }
public DbSet<Employee> Employee { get; set; }
public DbSet<Item> Item { get; set; }
public DbSet<ItemType> ItemType { get; set; }
public DbSet<ItemType2Item> ItemType2Items { get; set; }
public DbSet<EmployeeRank2Employee> EmployeeRank2Employees { get; set; }
public DbSet<Transaction> Transactions { get; set; }
public DbSet<Transaction2Items> Transaction2Items { get; set; }
public DbSet<TransactionNonItem> TransactionNonItem { get; set; }
public DbSet<TaxSettings> TaxSettings { get; set; }
public DbSet<Discount> Discount { get; set; }
public DbSet<Transaction2Discount> Transaction2Discount { get; set; }
public DbSet<EmployeeRank2AspRole> EmployeeRank2AspRoles { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Gender>().ToTable("Gender");
modelBuilder.Entity<Gender2Employee>().ToTable("Gender2Employee");
modelBuilder.Entity<EmployeeRank>().ToTable("EmployeeRank");
modelBuilder.Entity<Employee>().ToTable("Employee");
modelBuilder.Entity<EmployeeRank2Employee>().ToTable("EmployeeRank2Employee");
modelBuilder.Entity<Item>().ToTable("Item");
modelBuilder.Entity<ItemType>().ToTable("ItemType");
modelBuilder.Entity<ItemType2Item>().ToTable("ItemType2Item");
modelBuilder.Entity<Transaction>().ToTable("Transaction");
modelBuilder.Entity<Transaction2Items>().ToTable("Transaction2Items");
modelBuilder.Entity<TransactionNonItem>().ToTable("TransactionNonItem");
modelBuilder.Entity<TaxSettings>().ToTable("TaxSettings");
modelBuilder.Entity<Discount>().ToTable("Discount");
modelBuilder.Entity<Transaction2Discount>().ToTable("Transaction2Discount");
modelBuilder.Entity<EmployeeRank2AspRole>().ToTable("EmployeeRank2AspRole");
/*
* Primary Key auto increment
*/
modelBuilder.Entity<Gender>()
.Property(g => g.GenderId)
.UseIdentityColumn();
modelBuilder.Entity<Gender2Employee>()
.Property(g => g.Gender2EmployeeId)
.UseIdentityColumn();
modelBuilder.Entity<Item>()
.Property(i => i.ItemId)
.UseIdentityColumn();
modelBuilder.Entity<ItemType>()
.Property(it => it.ItemTypeId)
.UseIdentityColumn();
modelBuilder.Entity<ItemType2Item>()
.Property(iti => iti.ItemType2ItemId)
.UseIdentityColumn();
modelBuilder.Entity<EmployeeRank>()
.Property(er => er.EmployeeRankId)
.UseIdentityColumn();
modelBuilder.Entity<EmployeeRank2Employee>()
.Property(ere => ere.EmployeeRank2EmployeeId)
.UseIdentityColumn();
modelBuilder.Entity<Transaction>()
.Property(t => t.TransactionId)
.UseIdentityColumn();
modelBuilder.Entity<Transaction2Items>()
.Property(ti => ti.Transaction2ItemsId)
.UseIdentityColumn();
modelBuilder.Entity<TransactionNonItem>()
.Property(tni => tni.TransactionId)
.UseIdentityColumn();
modelBuilder.Entity<TaxSettings>()
.Property(ts => ts.TaxSettingsId)
.UseIdentityColumn();
modelBuilder.Entity<Discount>()
.Property(d => d.DiscountId)
.UseIdentityColumn();
modelBuilder.Entity<Transaction2Discount>()
.Property(t2d => t2d.Transaction2DiscountId)
.UseIdentityColumn();
modelBuilder.Entity<EmployeeRank2AspRole>()
.Property(e2a => e2a.EmployeeRank2AspRoleId)
.UseIdentityColumn();
/*
* Relations
*/
//Gender
modelBuilder.Entity<Gender2Employee>()
.HasOne<Employee>()
.WithMany()
.HasForeignKey(e => e.EmployeeId)
.HasPrincipalKey(e => e.EmployeeId);
modelBuilder.Entity<Gender2Employee>()
.HasOne<Gender>()
.WithMany()
.HasForeignKey(g => g.GenderId)
.HasPrincipalKey(g => g.GenderId);
//item
modelBuilder.Entity<ItemType2Item>()
.HasOne<Item>()
.WithMany()
.HasForeignKey(i => i.ItemId)
.HasPrincipalKey(i => i.ItemId);
modelBuilder.Entity<ItemType2Item>()
.HasOne<ItemType>()
.WithMany()
.HasForeignKey(i => i.ItemTypeId)
.HasPrincipalKey(i => i.ItemTypeId);
//transaction
//transaction2items
modelBuilder.Entity<Transaction2Items>()
.HasOne<Transaction>()
.WithMany()
.HasForeignKey(t => t.TransactionId)
.HasPrincipalKey(t => t.TransactionId);
modelBuilder.Entity<Transaction2Items>()
.HasOne<Item>()
.WithMany()
.HasForeignKey(i => i.ItemId)
.HasPrincipalKey(i => i.ItemId);
//transaction2discount
modelBuilder.Entity<Transaction2Discount>()
.HasOne<Transaction>()
.WithMany()
.HasForeignKey(t => t.TransactionId)
.HasPrincipalKey(t => t.TransactionId);
modelBuilder.Entity<Transaction2Discount>()
.HasOne<Discount>()
.WithMany()
.HasForeignKey(d => d.DiscountId)
.HasPrincipalKey(d => d.DiscountId);
//employeerank2asproleid
modelBuilder.Entity<EmployeeRank2AspRole>()
.HasOne<EmployeeRank>()
.WithMany()
.HasForeignKey(er => er.EmployeeRankId)
.HasPrincipalKey(er => er.EmployeeRankId);
}
}
}
In my Startup.cs I'm adding the context to the dependency injection like this:
//Database initialization
services.AddDbContext<PGSQLConnectionManager>(options =>
options.UseNpgsql(Configuration.GetConnectionString("PGSQLConnectionManager")));
services.AddDatabaseDeveloperPageExceptionFilter();
Now since my latest additional query I was using the Context like this:
In any .razor file I'm injecting the PGSQLConnectionManager:
#inject PGSQLConnectionManager Context
And later on in the .razor file in the #code part, I use the injected Context like this:
GetEmployee getAllEmployees = new GetEmployee(Context);
And every Class that needs a database connection is getting initialized like this:
public class GetEmployee
{
//Variables
private readonly PGSQLConnectionManager _connectionManager;
public GetEmployee(PGSQLConnectionManager context)
{
_connectionManager = context;
}
public List<Employee> GetAllEmployees()
{
try
{
//query all employees
List<Employee> allEmployees = _connectionManager.Employee.AsQueryable().ToList();
return allEmployees;
}
catch (Exception e)
{
#I know this is bad, but reporting is to come
return null;
}
}
}
And now, that I've done this like, >20 times I get an error: Connection is Busy
I tried using a static class and reference the PGSQLConnectionManager in there once, but then everytime I try to get the Context I get an error, that the Object has been disposed??? and can't get accessed anymore.
As far as I know, objects do not get disposed if they are in a static class.
I couldn't find where the database gets disposed in the static class thing.
So far, I think I'm using EntityFramework or the NPGSQL wrong. But I cannot find a good example, that represents my case.
Thanks ahead for any help
I've been playing around with this quickstart example & have been trying to see how far I can customise the database (I have an existing database I've been half-trying to replicate).
I've managed to trigger the exception below and am having trouble fixing it, partially because I don't understand what the message is telling me.
InvalidOperationException: Entity type
'Microsoft.AspNetCore.Identity.IdentityRole' is in shadow-state. A
valid model requires all entity types to have corresponding CLR type.
My ApplicationDbContext is as follows:
using IdentityServerWithAspIdAndEF.Models;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using System;
namespace IdentityServerWithAspIdAndEF.Data
{
public class ApplicationDbContext : IdentityDbContext<User, Role, int>
{
public ApplicationDbContext(
DbContextOptions<ApplicationDbContext> Options
) : base(Options) { }
protected override void OnModelCreating(ModelBuilder ModelBuilder)
{
base.OnModelCreating(ModelBuilder);
// Customisations
// "IdentityServer4AspNetIdentity.Models.ApplicationUser"
ModelBuilder.Entity<User>(B =>
{
B.Property<int>(P => P.Id)
.HasColumnName("AccountId")
.ValueGeneratedOnAdd();
B.Property<string>("ConcurrencyStamp")
.HasMaxLength(512)
.IsConcurrencyToken();
B.Property<string>("Email")
.HasMaxLength(512)
.IsRequired();
B.Property<bool>("EmailConfirmed")
.ValueGeneratedOnAdd();
B.Property<string>("NormalisedEmail")
.HasMaxLength(512)
.IsRequired();
B.Property<string>("NormalisedUserName")
.HasMaxLength(256)
.IsRequired();
B.Property<string>("PasswordHash");
B.Property<string>("SecurityStamp")
.IsRequired();
B.Property<bool>("TwoFactorEnabled")
.ValueGeneratedOnAdd();
B.Property<string>("UserName")
.HasMaxLength(256)
.IsRequired();
B.Property<DateTime>("Registered")
.ValueGeneratedOnAdd();
B.Property<DateTime>("LastVisit")
.IsRequired();
B.HasKey("AccountId");
B.HasIndex("NormalisedEmail")
.HasName("IX_Users_NormalisedEmail");
B.HasIndex("NormalisedUserName")
.IsUnique()
.HasName("IX_Users_NormalisedUserName");
B.ToTable("Users");
});
// "Microsoft.AspNetCore.Identity.IdentityRole"
ModelBuilder.Entity<Role>(B =>
{
B.Property<int>(P => P.Id)
.HasColumnName("RoleId")
.ValueGeneratedOnAdd();
B.Property<string>("ConcurrencyStamp")
.HasMaxLength(512)
.IsConcurrencyToken();
B.Property<string>("Name")
.HasMaxLength(256);
B.Property<string>("NormalisedName")
.HasMaxLength(256);
B.HasKey(P => P.Id);
B.HasIndex("NormalisedName")
.IsUnique()
.HasName("IX_Roles_NormalisedName");
B.ToTable("Roles");
});
// "Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>"
ModelBuilder.Entity<RoleClaim>(B =>
{
B.Property<int>(P => P.Id)
.HasColumnName("ClaimId")
.ValueGeneratedOnAdd();
B.Property<string>("ClaimType")
.HasMaxLength(128);
B.Property<string>("ClaimValue")
.HasMaxLength(128);
B.Property<int>("RoleId")
.IsRequired();
B.HasIndex(P => P.RoleId)
.HasName("IX_RoleClaims_RoleId");
B.HasOne(D => D.Claim)
.WithMany()
.HasForeignKey(P => P.RoleId)
.OnDelete(DeleteBehavior.Cascade);
B.ToTable("RoleClaims");
});
// "Microsoft.AspNetCore.Identity.IdentityUserClaim<string>"
ModelBuilder.Entity<UserClaim>(B =>
{
B.Property<int>(P => P.Id)
.HasColumnName("ClaimId")
.ValueGeneratedOnAdd();
B.Property<string>("ClaimType")
.HasMaxLength(128);
B.Property<string>("ClaimValue")
.HasMaxLength(128);
B.Property<int>(P => P.UserId)
.HasColumnName("AccountId")
.IsRequired();
B.HasIndex("AccountId")
.HasName("IX_UserClaims_AccountId");
B.HasOne(D => D.Account)
.WithMany()
.HasForeignKey(P => P.AccountId)
.OnDelete(DeleteBehavior.Cascade);
B.ToTable("UserClaims");
});
// "Microsoft.AspNetCore.Identity.IdentityUserLogin<string>"
ModelBuilder.Entity<Login>(B =>
{
B.Property<int>(P => P.UserId)
.HasColumnName("LoginId")
.ValueGeneratedOnAdd();
B.Property<string>("LoginProvider")
.HasMaxLength(450)
.IsRequired();
B.Property<string>("ProviderKey")
.HasMaxLength(450)
.IsRequired();
B.Property<string>("ProviderDisplayName");
B.Property<int>("AccountId")
.IsRequired();
B.HasIndex("LoginProvider")
.HasName("IX_Logins_LoginProvider");
B.HasIndex("ProviderKey")
.HasName("IX_Logins_ProviderKey");
B.HasIndex("AccountId")
.HasName("IX_Logins_AccountId");
B.HasOne(D => D.Account)
.WithMany()
.HasForeignKey(P => P.AccountId)
.OnDelete(DeleteBehavior.Cascade);
B.ToTable("Logins");
});
// "Microsoft.AspNetCore.Identity.IdentityUserRole<string>"
ModelBuilder.Entity<UserRole>(B =>
{
B.Property<int>(P => P.UserId)
.HasColumnName("AccountId")
.IsRequired();
B.Property<int>("RoleId")
.IsRequired();
B.HasIndex("AccountId")
.HasName("IX_RoleMap_AccountId");
B.HasIndex("RoleId")
.HasName("IX_RoleMap_RoleId");
B.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
B.HasOne(P => P.Account)
.WithMany()
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade);
B.ToTable("RoleMap");
});
// "Microsoft.AspNetCore.Identity.IdentityUserToken<string>"
ModelBuilder.Entity<Token>(B =>
{
B.Property<int>(P => P.UserId)
.HasColumnName("AccountId")
.IsRequired();
B.Property<string>("LoginProvider")
.HasMaxLength(128)
.IsRequired();
B.Property<string>("Name")
.HasMaxLength(64);
B.Property<string>("Value");
B.HasOne(P => P.Account)
.WithMany()
.HasForeignKey(P => P.AccountId)
.OnDelete(DeleteBehavior.Cascade);
B.ToTable("UserTokens");
});
// Non-identity extras
/* snipped */
}
}
}
The entities that correspond with these DbSets are as follows:
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
namespace IdentityServerWithAspIdAndEF.Models
{
public class User : IdentityUser<int>
{
public int AccountId
{
get => base.Id;
set => base.Id = value;
}
public string NormalisedEmail
{
get => base.NormalizedEmail;
set => base.NormalizedEmail = value;
}
public string NormalisedUserName
{
get => base.NormalizedUserName;
set => base.NormalizedUserName = value;
}
public DateTime Registered { get; set; }
public DateTime LastVisit { get; set; }
public AccountDetail UserDetails { get; set; }
public AccountLockout Lockout { get; set; }
public PasswordReset PasswordResetRequested { get; set; }
public Concierge ConciergeAccountFlag { get; set; }
public NotValidated AccountValidatation { get; set; }
public ICollection<UserRole> AssignedRoles { get; set; }
public ICollection<UserClaim> ClaimsCollection { get; set; }
public ICollection<Login> Logins { get; set; }
public ICollection<LoginAttempt> LoginAttempts { get; set; }
public ICollection<Token> TokenCollection { get; set; }
public new int Id => throw new NotImplementedException();
public override string NormalizedEmail => throw new NotImplementedException();
public override string NormalizedUserName => throw new NotImplementedException();
}
public class Role : IdentityRole<int>
{
public int RoleId
{
get => base.Id;
set => base.Id = value;
}
public string NormalisedName
{
get => base.NormalizedName;
set => base.NormalizedName = value;
}
public ICollection<RoleClaim> ClaimsCollection { get; set; }
private new int Id => throw new NotImplementedException();
private new int NormalizedName => throw new NotImplementedException();
}
public class RoleClaim : IdentityRoleClaim<int>
{
public int ClaimId
{
get => base.Id;
set => base.Id = value;
}
public Role Claim { get; set; }
private new int Id => throw new NotImplementedException();
}
public class UserClaim : IdentityUserClaim<int>
{
public int ClaimId
{
get => base.Id;
set => base.Id = value;
}
public int AccountId
{
get => base.UserId;
set => base.UserId = value;
}
public User Account { get; set; }
private new int Id => throw new NotImplementedException();
private new int UserId => throw new NotImplementedException();
}
public class Login : IdentityUserLogin<int>
{
public int AccountId
{
get => base.UserId;
set => base.UserId = value;
}
public User Account { get; set; }
private new int UserId => throw new NotImplementedException();
}
public class UserRole : IdentityUserRole<int>
{
public int AccountId
{
get => base.UserId;
set => base.UserId = value;
}
public User Account { get; set; }
private new int UserId => throw new NotImplementedException();
}
public class Token : IdentityUserToken<int>
{
public int AccountId
{
get => base.UserId;
set => base.UserId = value;
}
private new int UserId => throw new NotImplementedException();
public User Account { get; set; }
}
}
I have read the posts "What does it mean for an entity type to be in “shadow state”?" and "Entity type 'type' is in shadow-state. A valid model requires all entity types to have corresponding CLR type"
Judging from the documentation I think that I may have missed, or mis-referenced, the Role entity somewhere, but it's not clear to me where.
Thanks in advance!
Edit:
Re-reading the shadow property documentation, the line "By convention, shadow properties are only created when a relationship is discovered but no foreign key property is found in the dependent entity class. In this case, a shadow foreign key property will be introduced." seems to support that I've mucked up the entity.
I've tried to rule out a property name mismatch by changing all property references in the Role and RoleClaim ModelBuilder entity declarations to expressions, to the following to see if hard referencing will help:
// "Microsoft.AspNetCore.Identity.IdentityRole"
ModelBuilder.Entity<Role>(B =>
{
B.Property(P => P.RoleId)
.ValueGeneratedOnAdd();
B.Property(E => E.ConcurrencyStamp)
.HasMaxLength(512)
.IsConcurrencyToken();
B.Property(E => E.Name)
.HasMaxLength(256);
B.Property(E => E.NormalisedName)
.HasMaxLength(256);
B.HasKey(P => P.Id);
B.HasIndex(E => E.NormalisedName)
.IsUnique()
.HasName("IX_Roles_NormalisedName");
B.ToTable("Roles");
});
// "Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>"
ModelBuilder.Entity<RoleClaim>(B =>
{
B.Property(P => P.ClaimId)
.ValueGeneratedOnAdd();
B.Property(E => E.ClaimType)
.HasMaxLength(128);
B.Property(E => E.ClaimValue)
.HasMaxLength(128);
B.Property(E => E.RoleId)
.IsRequired();
B.HasIndex(P => P.RoleId)
.HasName("IX_RoleClaims_RoleId");
B.HasOne(D => D.Claim)
.WithMany()
.HasForeignKey(P => P.RoleId)
.OnDelete(DeleteBehavior.Cascade);
B.ToTable("RoleClaims");
});
But no luck so far.
It appears as though I'd made several mistakes, the most pertinent is that I hadn't actually provided code for my UserRole object within the ApplicationDbContext sample; this is actually where the error was originating...
The code still referenced the original IdentityRole model:
// "Microsoft.AspNetCore.Identity.IdentityUserRole<string>"
ModelBuilder.Entity<UserRole>(E =>
{
E.Property<int>(P => P.UserId)
.HasColumnName("AccountId")
.IsRequired();
E.Property<int>("RoleId")
.IsRequired();
E.HasIndex("AccountId")
.HasName("IX_RoleMap_AccountId");
E.HasIndex("RoleId")
.HasName("IX_RoleMap_RoleId");
E.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") // Argh!
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
E.HasOne(P => P.Account)
.WithMany()
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade);
E.ToTable("RoleMap");
});
This was updated to reference properties, instead of using magic strings to specify the field names; which highlighted the error I'd made.
// "Microsoft.AspNetCore.Identity.IdentityUserRole<string>"
ModelBuilder.Entity<UserRole>(E =>
{
E.Property(P => P.UserId)
.HasColumnName("AccountId")
.IsRequired();
E.Property(P => P.RoleId)
.IsRequired();
E.HasIndex(P => P.AccountId)
.HasName("IX_RoleMap_AccountId");
E.HasIndex(P => P.RoleId)
.HasName("IX_RoleMap_RoleId");
E.HasOne(P => P.Role) // 'UserRole' does not contain a definition for 'Role'
.WithMany()
.HasForeignKey(P => P.RoleId)
.OnDelete(DeleteBehavior.Cascade);
E.HasOne(P => P.Account)
.WithMany()
.HasForeignKey(P => P.AccountId)
.OnDelete(DeleteBehavior.Cascade);
E.ToTable("RoleMap");
});
And:
public class UserRole : IdentityUserRole<int>
{
public int AccountId
{
get => base.UserId;
set => base.UserId = value;
}
public User Account { get; set; }
public Role Role { get; set; } // Addition
}
At this point the exception informing me that IdentityRole was operating in a shadow-state seems to have disappeared and been replaced by others.
Additionally (and I'm not 100% sure that this contributed to the exception I was seeing) I had misconfigured my RoleStore within Startup->ConfigureServices.
Services.AddIdentity<User, Role>()
.AddUserStore<CustomerUserStore>()
.AddUserManager<CustomerManager>()
.AddRoleStore<Role>() // Should have been CustomerRoleStore
.AddRoleManager<RoleManager>()
.AddSignInManager<CustomerSignInManager>()
.AddDefaultTokenProviders();
CustomerRoleStore also required an override to allow IdentityServer to understand the roles after my customisations, which looks as so:
public class CustomerRoleStore
: RoleStore<Role, ApplicationDbContext, int, UserRole, RoleClaim>
{
public CustomerRoleStore(
ApplicationDbContext context,
IdentityErrorDescriber describer = null
) : base(
context,
describer
)
{ }
protected override RoleClaim CreateRoleClaim(Role role, Claim claim)
{
return new RoleClaim
{
RoleId = role.RoleId,
ClaimType = claim.Type,
ClaimValue = claim.Value
};
}
}
For a metaphor here, a person can have zero or one car, and one car belongs only to one person.
I have a database that looks like this:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Car Car { get; set; }
}
public class Car
{
public int PersonId { get; set; }
public virtual Person Person { get; set; }
public string Model { get; set; }
}
I want to have a link from the person to its car, if existent, and from the car to its person. So my EntityTypeConfigurations are like so:
public class PersonConfig : EntityTypeConfiguration<Person>
{
public PersonConfig()
{
ToTable("tblPerson");
HasKey(s => s.Id)
.Property(s => s.Id)
.HasColumnName("idPerson");
Property(s => s.Name)
.HasColumnName("strName")
.IsRequired();
HasOptional(a => a.Car)
.WithOptionalPrincipal();
}
}
public class CarConfig : EntityTypeConfiguration<Car>
{
public CarConfig()
{
ToTable("tblCar");
HasKey(s => s.PersonId)
.Property(s => s.PersonId)
.HasColumnName("idPerson");
Property(s => s.Model)
.HasColumnName("strModel")
.IsRequired();
HasRequired(a => a.Person)
.WithRequiredDependent();
}
}
I don't know what I'm getting wrong, but EF:
Creates an additional Person_Id field in tblCar
Creates two foreign keys in tblCar, one named idPerson and the other Person_Id
What am I forgetting or doing wrong?
You have to use WithRequired instead of WithOptionalPrincipal, and the relationship do not need to be configured in both sides.
public class PersonConfig : EntityTypeConfiguration<Person>
{
public TaskConfig()
{
ToTable("tblPerson");
HasKey(s => s.Id);
Property(s => s.Id)
.HasColumnName("idPerson");
Property(s => s.Name)
.HasColumnName("strName")
.IsRequired();
HasOptional(a => a.Car)
.WithRequired(s => s.Person);
}
}
public class CarConfig : EntityTypeConfiguration<Car>
{
public CarConfig()
{
ToTable("tblCar");
HasKey(s => s.PersonId)
.Property(s => s.PersonId)
.HasColumnName("idPerson");
Property(s => s.Model)
.HasColumnName("strModel")
.IsRequired();
//not necessary
//HasRequired(a => a.Person)
//.WithRequiredDependent();
}
}
What am I forgetting or doing wrong?
You are not configuring the Person - Car relationship correctly.
Let fix that. Note that you don't need to configure the relationship in both places.
Remove the following from the Person config:
HasOptional(a => a.Car)
.WithOptionalPrincipal();
and replace the following in the Car config:
HasRequired(a => a.Person)
.WithRequiredDependent();
with
HasRequired(c => c.Person)
.WithOptional(p => p.Car);
I have a table in the database that is used only for holding some ID's descriptions:
TABLE SomeClass
- Columns
- ClassTypeID INT CONSTRAINT etc
TABLE SomeClassTypes
- ClassTypeID INT IDENTITY
- Description NVARCHAR
It's done like this so it's easy for users to insert/remove new types.
I want to get a report of all of SomeClass, but I'd like to have a string property to hold the description from the other table:
public class SomeClass
{
public virtual int SomeClassID { get; set; }
public virtual int ClassTypeID { get; set; }
public virtual string DescriptionType { get; set; }
}
public class SomeClassMap : ClassMapping<SomeClass>
{
public SomeClassMap()
{
Table("SomeClassTable");
Property(p => p.SomeClassID, map =>
{
map.Column("SomeClassID");
map.Generator(Generators.Identity);
});
Property(p => p.ClassTypeID, map => map.Column("ClassTypeID"));
//Other properties here
Property(p => p.DescriptionType, ?); //This line
}
}
How can I do this?
Try to use joined table.
public class SomeClassMap : ClassMapping<SomeClass>
{
public SomeClassMap()
{
Table("SomeClassTable");
Property(p => p.SomeClassID, map =>
{
map.Column("SomeClassID");
map.Generator(Generators.Identity);
});
Property(p => p.ClassTypeID, map => map.Column("ClassTypeID"));
//Other properties here
Join("SomeClassTypes", m =>
{
m.KeyColumn("ClassTypeId");
m.Fetch.Join();
m.Map(x => x.DescriptionType).Column("Description");
})
}
}
EDITED
If you use fluent mapping built in NHibernate, try this:
public class SomeClassMap : ClassMapping<SomeClass>
{
public SomeClassMap()
{
Table("SomeClassTable");
Property(p => p.SomeClassID, map =>
{
map.Column("SomeClassID");
map.Generator(Generators.Identity);
});
Property(p => p.ClassTypeID, map => map.Column("ClassTypeID"));
//Other properties here
Property(p => p.DescriptionType, ?); //This line
Join("SomeClassTypes", m =>
{
m.Key(k => k.Column("ClassTypeId"));
m.Fetch(FetchKind.Join);
m.Property(x => x.DescriptionType).Column("Description");
});
}
}
I am trying to specify a column name to map a "Foreign Key" to using the Fluent API. I am connecting to an instance of SQL Express. I have searched Stack Overflow and Google, but many of the different configuration examples gave me the same result.
Product Class
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public virtual Product ParentProduct { get; set; }
public virtual ICollection<Product> ChildProducts { get; set; }
}
Product Map to Entity Framework
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap() {
HasKey(p => p.ProductId);
Property(p => p.Name)
.IsRequired();
// Self referencing foreign key association
Property(c => c.ParentId)
.IsOptional();
HasMany(c => c.ChildProducts)
.WithOptional(c => c.ParentProduct)
.HasForeignKey(c => c.ParentId);
}
}
The Issue
The result when I run the program and EF creates the db is that the ParentID column comes out NULL and it creates a column that's called ParentProduct_ProductId. This column contains the correct values for the ParentId.
I'm new to using the Fluent API with EF, so I'm chalking this one up to inexperience. How do I get the auto-generated column to fill the ParentId column instead?
Try this solution:
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap() {
HasKey(p => p.ProductId);
Property(p => p.Name)
.IsRequired();
// Self referencing foreign key association
Property(c => c.ParentId)
.IsOptional();
HasOptional(x => x.Parent)
.WithMany(x => x.ChildProducts)
.HasForeignKey(x => x.ParentId)
.WillCascadeOnDelete(false);
}
}