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
Related
I'm new to C# / .NET Core and I'm trying to create a Web API. I have 2 models; Community & Rank
Community:
public class Community
{
public long Id { get; set; }
public long FrontId { get; set; }
public string Name { get; set; }
[ForeignKey("CommunityId")]
public virtual ICollection<Rank> Ranks { get; set; }
}
Rank:
public class Rank
{
public long Id { get; set; }
public long CommunityId { get; set; } [Required]
public string Name { get; set; } [Required]
public string Prefix { get; set; }
public virtual Community Community { get; set; }
}
I have my context set up like so:
public class CommunityContext : DbContext
{
public DbSet<Community> Communities { get; set; }
public DbSet<Rank> Ranks { get; set; }
public CommunityContext(DbContextOptions<CommunityContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
//Communities
builder.Entity<Community>(entity =>
{
entity.HasKey(c => c.Id);
entity.Property(c => c.Name).IsRequired();
entity.Property(c => c.FrontId).IsRequired();
// entity.Property(c => c.Ranks).IsRequired(false);
entity.HasMany(c => c.Ranks).WithOne().HasForeignKey(c => c.CommunityId).IsRequired();
});
//Ranks
builder.Entity<Rank>(entity =>
{
entity.HasKey(r => r.Id);
entity.Property(r => r.Name).IsRequired();
entity.Property(r => r.Prefix).IsRequired();
entity.Property(r => r.CommunityId).HasColumnName("CommunityId").IsRequired();
// entity.HasOne(r => r.Community).WithMany(s => s.Ranks);
});
}
}
I've done my database migrations etc, and when creating new entries, all works as expected currently, however trying to GET either communities or ranks returns the error:
MySql.Data.MySqlClient.MySqlException (0x80004005): Unknown column 'r.CommunityId1' in 'field list'
Any suggestions would be appreciated!
Thanks
Your Configurations should be like this:
...
//Communities
builder.Entity<Community>(entity =>
{
builder.ToTable("Community");
builder.HasKey(c => c.Id);
builder.Property(c => c.Id).HasColumnName("CommunityId").ValueGeneratedOnAdd();
entity.Property(c => c.Name).IsRequired();
entity.Property(c => c.FrontId).IsRequired();
// entity.Property(c => c.Ranks).IsRequired(false);
entity.HasMany(c => c.Ranks).WithOne().HasForeignKey(c => c.CommunityId).IsRequired();
});
//Ranks
builder.Entity<Rank>(entity =>
{
builder.ToTable("Rank");
builder.HasKey(r => r.Id);
builder.Property(r => r.Id).HasColumnName("RankId").ValueGeneratedOnAdd();
entity.Property(r => r.Name).IsRequired();
entity.Property(r => r.Prefix).IsRequired();
entity.Property(r => r.CommunityId).HasColumnName("CommunityId").IsRequired();
// entity.HasOne(r => r.Community).WithMany(s => s.Ranks);
});
...
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?
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
};
}
}
I'm trying to wrap basic actions fro model(like get by id, etc.) in repositories. However I'm facing with two problems:
1. Connection String : Is there any solution to put it in config, and not to hard code it in class, like I did?
public const string ConnectionString = "Server = (localdb)\\mssqllocaldb;Database=pinchdb;Trusted_Connection=True;MultipleActiveResultSets=true";// can I get rid of it?
public DbSet<Department> Departments { get; set; }
public DbSet<Employee> Employees { get; set; }
public DbSet<Project> Projects { get; set; }
public DbSet<ProjectDepartments> ProjectsDepartments { get; set; }
public DbSet<Roles> Roles { get; set; }
public effMercContext(DbContextOptions<effMercContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProjectDepartments>()
.HasKey(t => new { t.ProjectId, t.DepartmentId });
modelBuilder.Entity<ProjectDepartments>()
.HasOne(pt => pt.Project)
.WithMany(p => p.ProjectDepartment)
.HasForeignKey(pt => pt.ProjectId);
modelBuilder.Entity<ProjectDepartments>()
.HasOne(pt => pt.Department)
.WithMany(t => t.ProjectDepartment)
.HasForeignKey(pt => pt.DepartmentId);
}
}
public class EffMercDbContextFactory : IDbContextFactory<effMercContext>
{
public effMercContext Create(DbContextFactoryOptions options)
{
var builder = new DbContextOptionsBuilder<effMercContext>();
builder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=pinchdb;Trusted_Connection=True;MultipleActiveResultSets=true");
return new effMercContext(builder.Options);
}
}
2. Can I call Dbcontext without options to implement methods?
public Employee GetByID(int id)
{
var optionsBuilder = new DbContextOptionsBuilder<effMercContext>();// how can I get rid of this and just call effMercContext
optionsBuilder.UseSqlServer(effMercContext.ConnectionString);
using (effMercContext db = new effMercContext(optionsBuilder.Options))
{
return db.Employees.Where(x => x.Id == id).FirstOrDefault();
}
}
You can put your connectionstring in appsettings.json as shown below.
Also, you can have config transforms for the appsettings.json based on the environments.
in the Startup.cs
Access the above config while creating the DbContext.
services.AddDbContext<ApplicationDbContext>(
options =>
options.UseSqlServer(
configuration["Data:DefaultConnection:ConnectionString"], b =>
b.MigrationsAssembly("MyProj.Web"))
Add a default constructor to effMercContext. Then you shouldn't need to pass DbContextOptions.
I'm developing an Entity Framework 6.1.2 Code First library with C# and .NET Framework 4.5.1.
I get this error:
A specified Include path is not valid. The EntityType
'MyProject.Data.SqlServer.Concrete.AGGREGATION_CHILDS' does not
declare a navigation property with the name 'Code'.
Note that MyProject.Data.SqlServer.Concrete namespace is incorrect. I have DbContext declared in MyProject.Data.SqlServer.Concrete namespace.
This is AGGREGATION_CHILDSclass declaration:
namespace MyProject.Data
{
public class AGGREGATION_CHILDS
{
public string CODE { get; set; }
public string PARENT_CODE { get; set; }
public int POSITION { get; set; }
public virtual AGGREGATIONS Aggregation { get; set; }
public virtual CODES Code { get; set; }
}
}
And CODES class:
namespace MyProject.Data
{
public class CODES
{
public string CODE { get; set; }
public byte CODE_LEVEL { get; set; }
[ ... ]
public virtual AGGREGATION_CHILDS AggregationChild { get; set; }
}
}
And their configuration files:
namespace MyProject.Data.SqlServer.Configurations
{
class AGGREGATION_CHILDSConfiguration : EntityTypeConfiguration<AGGREGATION_CHILDS>
{
public AGGREGATION_CHILDSConfiguration()
{
HasKey(ag_ch => ag_ch.CODE);
Property(ag_ch => ag_ch.CODE)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Property(ag_ch => ag_ch.CODE)
.HasMaxLength(20)
.IsRequired();
Property(ag_ch => ag_ch.PARENT_CODE)
.HasMaxLength(20)
.IsRequired();
HasRequired(ag_ch => ag_ch.Aggregation)
.WithMany(ag => ag.AggregationChilds)
.HasForeignKey(ag_ch => ag_ch.PARENT_CODE);
HasRequired(ag_ch => ag_ch.Code)
.WithOptional(c => c.AggregationChild)
.WillCascadeOnDelete(false);
}
}
}
And the other configuration file:
namespace MyProject.Data.SqlServer.Configurations
{
class CODESConfiguration : EntityTypeConfiguration<CODES>
{
public CODESConfiguration()
{
HasKey(c => c.CODE);
Property(c => c.CODE)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Property(c => c.CODE)
.HasMaxLength(20)
.IsRequired();
[ ... ]
}
}
}
And this is where I get that error:
List<AGGREGATION_CHILDS> agChilds =
m_AggChildRepo
.SearchForWithInclude(agCH => agCH.PARENT_CODE == aggregation.PARENT_CODE, "Code")
.ToList<AGGREGATION_CHILDS>();
SearchForWithInclude implementation is:
public IQueryable<TEntity> SearchForWithInclude(
Expression<Func<TEntity, bool>> predicate,
string includePath)
{
return _dbSet.Where(predicate).Include(includePath);
}
CODES and AGGREGATION_CHILDS have a One-to–Zero-or-One relationship.
Do you know why is complaining about that Code navigation property doesn't exit? Maybe, I haven't create the zero-or-one-to-one relationship correctly.
The line that triggers the error evaluates to
m_AggChildRepo.Where(agCH => agCH.PARENT_CODE == aggregation.PARENT_CODE).Include("Code")
What is that include supposed to do? Even if you are using Dynamic Linq, what is "Code" supposed to be? All fragments you posted here use fully capitalized attribute and property names.
This how I have solved the problem. I think there is an Ambiguous problem.
namespace MyProject.Data
{
public class AGGREGATION_CHILDS
{
public string CHILD_CODE { get; set; }
public string PARENT_CODE { get; set; }
public int POSITION { get; set; }
public virtual AGGREGATIONS Aggregation { get; set; }
public virtual CODES Code { get; set; }
}
}
I have changed AGGREGATION_CHILDS.CODE property with AGGREGATION_CHILDS.CHILD_CODE.
namespace MyProject.Data.SqlServer.Configurations
{
class AGGREGATION_CHILDSConfiguration : EntityTypeConfiguration<AGGREGATION_CHILDS>
{
public AGGREGATION_CHILDSConfiguration()
{
HasKey(ag_ch => ag_ch.CHILD_CODE);
Property(ag_ch => ag_ch.CHILD_CODE)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Property(ag_ch => ag_ch.CHILD_CODE)
.HasMaxLength(20)
.IsRequired()
.HasColumnName("CODE");
Property(ag_ch => ag_ch.PARENT_CODE)
.HasMaxLength(20)
.IsRequired();
HasRequired(ag_ch => ag_ch.Aggregation)
.WithMany(ag => ag.AggregationChilds)
.HasForeignKey(ag_ch => ag_ch.PARENT_CODE);
HasRequired(ag_ch => ag_ch.Code)
.WithOptional(c => c.AggregationChild)
.WillCascadeOnDelete(false);
}
}
}
And set what column name will have on database:
Property(ag_ch => ag_ch.CHILD_CODE)
.HasMaxLength(20)
.IsRequired()
.HasColumnName("CODE");