Navigation property doesn't exit - c#

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");

Related

Web API - EF Core 6.0 Create one to many relationship GET with nested relationship

Using EF Power tools I have the following classes created:
Category.cs
public partial class Category
{
public Category()
{
Folders = new HashSet<Folder>();
Reports = new HashSet<Report>();
}
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public string CategoryDescription { get; set; }
public string ImagePath { get; set; }
public virtual ICollection<Folder> Folders { get; set; }
public virtual ICollection<Report> Reports { get; set; }
}
Folder.cs
public partial class Folder
{
public Folder()
{
Reports = new HashSet<Report>();
}
public int FolderId { get; set; }
public string FolderName { get; set; }
public int CategoryId { get; set; }
public string FolderDescription { get; set; }
public string FolderImagePath { get; set; }
public virtual Category Category { get; set; }
public virtual ICollection<Report> Reports { get; set; }
}
The underlying tables are linked one to many with Categoryid.
I believe the dbContext.cs was generated correctly based on the table schema.
dbContext.cs
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>(entity =>
{
entity.HasKey(e => e.CategoryId)
.IsClustered(false);
entity.ToTable("Category");
entity.Property(e => e.CategoryId)
.ValueGeneratedNever()
.HasColumnName("CategoryID");
entity.Property(e => e.CategoryDescription)
.HasMaxLength(300)
.IsUnicode(false);
entity.Property(e => e.CategoryName)
.HasMaxLength(100)
.IsUnicode(false);
entity.Property(e => e.ImagePath)
.HasMaxLength(250)
.IsUnicode(false);
} );
modelBuilder.Entity<Folder>(entity =>
{
entity.HasKey(e => e.FolderId)
.IsClustered(false);
entity.ToTable("Folder");
entity.Property(e => e.FolderId)
.ValueGeneratedNever()
.HasColumnName("FolderID");
entity.Property(e => e.CategoryId).HasColumnName("CategoryID");
entity.Property(e => e.FolderDescription)
.HasMaxLength(300)
.IsUnicode(false);
entity.Property(e => e.FolderImagePath)
.HasMaxLength(250)
.IsUnicode(false);
entity.Property(e => e.FolderName)
.HasMaxLength(100)
.IsUnicode(false);
entity.HasOne(d => d.Category)
.WithMany(p => p.Folders)
.HasForeignKey(d => d.CategoryId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Folder_01");
});
OnModelCreatingPartial(modelBuilder);
}
I then have the following in the controller:
[HttpGet]
public async Task<ActionResult<IEnumerable<Category>>> GetCategories()
{
return await _context.Categories.ToListAsync();
}
When I run the WebAPI I see the following:
[
{
"categoryId": 1,
"categoryName": "Some Name",
"folders": [],
"reports": []
},
{
"categoryId": 2,
"categoryName": "Another Name",
"folders": [],
"reports": []
},
]
How do I get the related data to populate in the folders array?
I also tried this:
public async Task<ActionResult<IEnumerable<Category>>> GetCategories()
{
var query = (from r in _context.Categories
from bu in _context.Folders
where r.CategoryId == bu.CategoryId
select r
).ToListAsync();
return await query;
I suspect that you application has the loading of relationship to lazy loading.
This means that every relation on the model specified wont be loaded until you ask to specifically see them.
In this specific case you could tell your application to load all the data you need in one go.
[HttpGet]
public async Task<ActionResult<IEnumerable<Category>>> GetCategories()
{
// note the .Include will tell you application context to load the relation Folders by default
return await _context.Categories.Include(x => x.Folders).ToListAsync();
}
Here is more information if you want to learn all the way's you could load your relationships by default

C# NPGSQL Connection is Busy

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

.NET Core API - Unknown Column in field list

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);
});
...

EF Core many to many unwanted columns

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?

Duplicate key and table field in one-to-zero-or-one relation

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);

Categories