EF Core many to zero relationship configuration - entity-framework-core

How can I prevent EF Core migrations from adding a shadow property to TimeZone:
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Core_TimeZone",
schema: "dbo",
columns: table => new
{
StateOrProvinceId = table.Column<int>(nullable: true),
...
},
constraints: table =>
{
table.PrimaryKey("PK_Core_TimeZone", x => x.Id);
table.ForeignKey(
name: "FK_Core_TimeZone_Core_StateOrProvince_StateOrProvinceId",
column: x => x.StateOrProvinceId,
principalSchema: "dbo",
principalTable: "Core_StateOrProvince",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
}
}
Here are the POCO classes:
public partial class TimeZone : BaseEntity
{
…
// no StateOrProvince related properties
}
public partial class StateOrProvince : BaseEntity
{
…
private ICollection<Dna.NetCore.Core.BLL.Entities.Common.TimeZone> _timeZones;
public virtual ICollection<Dna.NetCore.Core.BLL.Entities.Common.TimeZone> TimeZones
{
get { return _timeZones ?? (_timeZones = new List<Dna.NetCore.Core.BLL.Entities.Common.TimeZone>()); }
set { _timeZones = value; }
}
}
and the configuration class:
public class StateOrProvinceConfiguration : IEntityTypeConfiguration<StateOrProvince>
{
public void Map(EntityTypeBuilder<StateOrProvince> builder)
{
…
builder.HasMany(d => d.TimeZones)
.WithOne()
.OnDelete(DeleteBehavior.Restrict);
}
}
I'm getting the shadow property regardless of whether or not I include the HasMany().WithOne() configuration.
The full source code is located in this GitHub repository.

Related

How to configure one-to-one relationship in EF Core with FK on both ends

I have following entities:
public class Subscription
{
public int Id { get; set; }
public int? BillingContractId { get; set; }
public BillingContract BillingContract { get; set; }
//other properties
}
public class BillingContract
{
public int Id { get; set; }
public int SubscriptionId { get; set; }
public Subscription Subscription { get; set; }
//other properties
}
So each subscription might have only one billing contract and each billing contract belongs to a single subscription.
I'm trying to configure this relationship in my dbcontext:
builder.Entity<Subscription>()
.HasOne(subscription => subscription.BillingContract)
.WithOne(billingContract => billingContract.Subscription)
.HasForeignKey<BillingContract>(billingContract => billingContract.SubscriptionId)
.IsRequired(true);
builder.Entity<BillingContract>()
.HasOne(billingContract => billingContract.Subscription)
.WithOne(subscription => subscription.BillingContract)
.HasForeignKey<Subscription>(subscription => subscription.BillingContractId)
.IsRequired(false);
But from the generated migration(or from the snapshot or from the actual DB schema) I can tell that only FK in Subscription table is created. I cannot make EF to create a FK(and index) in the BillingContract table. I also tried to use annotation attributes with the same result.
Did I miss something? Or it's a bug in EF?
I'm using EF Core 2.2
To eliminate a possibility of a corrupted db snapshot I created a brand new console project using EF Core 3.1. After adding initial migration I have the same result with missing FK:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "BillingContracts",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
SubscriptionId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BillingContracts", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Subscriptions",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
BillingContractId = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Subscriptions", x => x.Id);
table.ForeignKey(
name: "FK_Subscriptions_BillingContracts_BillingContractId",
column: x => x.BillingContractId,
principalTable: "BillingContracts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Subscriptions_BillingContractId",
table: "Subscriptions",
column: "BillingContractId",
unique: true,
filter: "[BillingContractId] IS NOT NULL");
}
This is not an EF bug. Usually, two tables have an association relationship, and you only need to create one foreign key in one of the tables. The two-way foreign key is for the entity and does not exist in the database design. This docuement has give the detail example.

How to restart postgres sequence with Entity framework seeding migration?

I have the following database seeder:
public partial class Seed_Languages : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "Languages",
columns: new[] { "LanguageId", "LangCode", "LangName", "Sort" },
values: new object[,]
{
{ 1, "AU", "Австралия", 0 },
{ 159, "CX", "Остров Рождества", 0 },
{ 160, "PN", "Острова Питкэрн", 0 },
{ 161, "SH", "Острова Святой Елены, Вознесения и Тристан-да-Кунья", 0 },
{ 162, "PK", "Пакистан", 0 },
{ 163, "PW", "Палау", 0 },
.... and so on ...
As you can see I'm going to populate some table, that contains language's names (on Russian, for showing on UI), language's codes, some additional field - Sort (not important here) and primary key. Simple, right?
Here is the table:
Then I create it inside my OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// many fluent api calls
LanguagesSeeder.SeedLanguages(modelBuilder);
}
Then I run $ dotnet ef database update and seeding works fine! But problems soon began.
When I try to insert new one language, .NET gives me:
Exception data:
Severity: ERROR
SqlState: 23505
MessageText: duplicate key value violates unique constraint "PK_Languages"
Detail: Key ("LanguageId")=(1) already exists.
SchemaName: public
TableName: Languages
ConstraintName: PK_Languages
File: nbtinsert.c
Line: 434
"Hmmmm lets try again" - I thought. And:
Exception data:
Severity: ERROR
SqlState: 23505
MessageText: duplicate key value violates unique constraint "PK_Languages"
Detail: Key ("LanguageId")=(2) already exists.
SchemaName: public
TableName: Languages
ConstraintName: PK_Languages
File: nbtinsert.c
Line: 434
Routine: _bt_check_unique
You see that? The same error but with another Primary key complaint! The first was: Key ("LanguageId")=(1) already exists. and the second Key ("LanguageId")=(2) already exists.
!
So, what to do? I know this way:
ALTER SEQUENCE <name of sequence> RESTART WITH <your number is here>;
But it's pretty uncomfortable to run this SQL in a console after seeding. Am I miss something? Maybe, there is a standard way for this, I mean using some EF API?
Update
I will show you my Language model:
namespace Domains
{
public class Language
{
public int LanguageId { get; set; }
public int Sort { get; set; }
public List<Customer> Customers { get; set; }
public List<PushMessageLang> PushMessageLangs { get; set; }
[NotMapped]
public IEnumerable<PushMessage> PushMessages
{
get => PushMessageLangs?.Select(r => r.PushMessage);
set => PushMessageLangs = value.Select(v => new PushMessageLang()
{
PushMessageId = v.PushMessageId
}).ToList();
}
public string LangName { get; set; }
public string LangCode { get; set; }
}
}
I make insert via my repository abstraction:
Base repository:
public class BaseRepository<T, C> : IRepository<T>
where T : class
where C : DbContext
{
protected C DataContext;
private readonly DbSet<T> _dbset;
public BaseRepository(C context)
{
DataContext = context;
_dbset = context.Set<T>();
}
public virtual IQueryable<T> All => _dbset;
public virtual async Task SaveAsync(T entity)
{
await _dbset.AddAsync(entity);
await DataContext.SaveChangesAsync();
}
public async Task SaveAsync(List<T> entity)
{
await _dbset.AddRangeAsync(entity);
await DataContext.SaveChangesAsync();
}
public virtual async Task UpdateAsync(T entity)
{
_dbset.Attach(entity).State = EntityState.Modified;
_dbset.Update(entity);
await DataContext.SaveChangesAsync();
}
public virtual async Task DeleteAsync(int id)
{
var dbEntity = await _dbset.FindAsync(id);
if (dbEntity != null)
{
_dbset.Remove(dbEntity);
await DataContext.SaveChangesAsync();
}
}
}
And in the controller:
public async Task<IActionResult> Create([FromForm] LanguageViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View(viewModel);
}
var newLanguage = new Language()
{
Sort = viewModel.Sort,
LangCode = viewModel.Code,
LangName = viewModel.Name
};
await _languageRepository.SaveAsync(newLanguage);
return RedirectToAction("Index");
}
Update 2
As asked in the comments I'll pin here all fluent api for Language model:
// many to many with `Message` entity
modelBuilder.Entity<PushMessageLang>()
.HasKey(bc => new { bc.PushLangId, bc.PushMessageId });
modelBuilder.Entity<PushMessageLang>()
.HasOne(bc => bc.Language)
.WithMany(b => b.PushMessageLangs)
.HasForeignKey(bc => bc.PushLangId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<PushMessageLang>()
.HasOne(bc => bc.PushMessage)
.WithMany(c => c.PushMessageLangs)
.HasForeignKey(bc => bc.PushMessageId)
.OnDelete(DeleteBehavior.Cascade);
// has unique language code
modelBuilder.Entity<Language>()
.HasIndex(x => x.LangCode).IsUnique();
Update 3
As asked #Roman Marusyk, I pine here SQL script for creating Languages table.
-- auto-generated definition
create table "Languages"
(
"LanguageId" integer generated by default as identity
constraint "PK_Languages"
primary key,
"LangName" text,
"LangCode" text,
"Sort" integer default 0 not null
);
alter table "Languages"
owner to makeapp_pushes;
create unique index "IX_Languages_LangCode"
on "Languages" ("LangCode");
Hmm, now I see that don't have anything about auto increment.
But my SQL client shows my:
Add HasKey to model configuration
modelBuilder.Entity<Language>()
.HasKey(x => x.LanguageId)
.HasIndex(x => x.LangCode).IsUnique();
as #IvanStoev mentioned, by convention, the property LanguageId is already the primary key
Try to specify
modelBuilder.Entity<Language>()
.Property(p => p.LanguageId)
.ValueGeneratedOnAdd();
In the migration I have added manually this string:
migrationBuilder.RestartSequence("Languages_LanguageId_seq", 251, "public");
where Languages_LanguageId_seq - name of sequence,
251 - number of starting of the sequence (PK value),
public - scheme name.
Here is documentation. Now I can insert without any errors.

EF Core HasMany vs OwnsMany

For one to many relationship, what's the difference between HasMany and OwnsMany? When should I use one over another?
For example:
public class xxx
{
public virtual IReadOnlyCollection<xxxHistoryEntity> Histories => _histories;
private readonly List<xxxHistoryEntity> _histories = new List<xxxHistoryEntity>();
}
public class xxxHistoryEntity : Entity<string>
{
public string State { get; set; }
public string NodeId { get; set; }
public string Message { get; set; }
}
The Entity Configuration:
class xxxConfiguration
: IEntityTypeConfiguration<xxx>
{
public void Configure(EntityTypeBuilder<xxx> builder)
{
builder.OwnsMany(itm => itm.Histories, collbuilder =>
{
collbuilder.HasForeignKey("xxxid");
});
}
}
class xxxHistoryConfiguration
: IEntityTypeConfiguration<xxxHistoryEntity>
{
public void Configure(EntityTypeBuilder<xxxHistoryEntity> builder)
{
builder.ToTable("xxx_histories");
builder.HasKey(itm => itm.Id);
builder.Property(itm => itm.Id)
.ValueGeneratedOnAdd();
}
}
The generated migration is below:
migrationBuilder.CreateTable(
name: "xxx_histories",
columns: table => new
{
id = table.Column<string>(nullable: false),
xxxid = table.Column<string>(nullable: false),
state = table.Column<string>(nullable: true),
nodeid = table.Column<string>(nullable: true),
message = table.Column<string>(nullable: true),
xmin = table.Column<uint>(type: "xid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_xxx_histories", x => new { x.id, x.xxxid });
table.ForeignKey(
name: "fk_xxxhistoryentity_xxx_xxxarid",
column: x => x.xxxid,
principalTable: "xxx",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
if I update the xxxConfiguration by replacing the OwnsMany with HasMany, like:
class xxxConfiguration
: IEntityTypeConfiguration<xxx>
{
public void Configure(EntityTypeBuilder<xxx> builder)
{
builder.HasMany(itm => itm.Histories)
.WithOne()
.HasForeignKey("xxxid");
}
}
The generated migration is below:
migrationBuilder.CreateTable(
name: "xxx_histories",
columns: table => new
{
id = table.Column<string>(nullable: false),
xxxid = table.Column<string>(nullable: false),
state = table.Column<string>(nullable: true),
nodeid = table.Column<string>(nullable: true),
message = table.Column<string>(nullable: true),
xmin = table.Column<uint>(type: "xid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_xxx_histories", x => new { x.id, x.xxxid });
table.ForeignKey(
name: "fk_xxxhistoryentity_xxx_xxxid",
column: x => x.xxxid,
principalTable: "xxx",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
As you can see, the migration generated by both are the same. So what's the point of OwnsMany?
From documentation:
EF Core allows you to model entity types that can only ever appear on
navigation properties of other entity types. These are called owned
entity types. The entity containing an owned entity type is its owner.
Owned entities are essentially a part of the owner and cannot exist
without it, they are conceptually similar to aggregates.
https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities
One of the differences is that relationships configured with OwnsMany() will include the owned entities by default when querying the owner from the database, whilst when using WithMany() you have to specify AutoInclude() manually if you want them to be included every time you get the owner entity form the database.
Also from documentation: Querying owned types

Upgrading From EF 5 to 6, New Migration Swaps Columns

When upgrading a code first project from Entity Framework 5 to 6.1.1, a Model with 2 foreign keys to the same table results in the Entity Framework detecting a schema change when no change should be happening.
Here's the Model in question.
public class UserActivity : Activity
{
public string UserMessage { get; set; }
public int? OriginatorId { get; set; }
public int UserId { get; set; }
public virtual User Originator { get; set; }
public virtual User User { get; set; }
public class Configuration : EntityTypeConfiguration<UserActivity>
{
public Configuration()
{
HasOptional(x => x.Originator).WithMany().HasForeignKey(x => x.OriginatorId).WillCascadeOnDelete(false);
HasRequired(x => x.User).WithMany().HasForeignKey(x => x.UserId).WillCascadeOnDelete(false);
}
}
}
Here's the Migration that's generated when running the command Add-Migration from the Package Manager Console.
public partial class EF6 : DbMigration
{
public override void Up()
{
DropForeignKey("dbo.UserActivities", "UserId", "dbo.Users");
RenameColumn(table: "dbo.UserActivities", name: "UserId", newName: "__mig_tmp__0");
RenameColumn(table: "dbo.UserActivities", name: "OriginatorId", newName: "UserId");
RenameColumn(table: "dbo.UserActivities", name: "__mig_tmp__0", newName: "OriginatorId");
AlterColumn("dbo.UserActivities", "OriginatorId", c => c.Int());
AlterColumn("dbo.UserActivities", "UserId", c => c.Int(nullable: false));
AddForeignKey("dbo.UserActivities", "OriginatorId", "dbo.Users", "ID");
AddForeignKey("dbo.UserActivities", "UserId", "dbo.Users", "ID");
}
public override void Down()
{
DropForeignKey("dbo.UserActivities", "UserId", "dbo.Users");
DropForeignKey("dbo.UserActivities", "OriginatorId", "dbo.Users");
AlterColumn("dbo.UserActivities", "UserId", c => c.Int());
AlterColumn("dbo.UserActivities", "OriginatorId", c => c.Int(nullable: false));
RenameColumn(table: "dbo.UserActivities", name: "OriginatorId", newName: "__mig_tmp__0");
RenameColumn(table: "dbo.UserActivities", name: "UserId", newName: "OriginatorId");
RenameColumn(table: "dbo.UserActivities", name: "__mig_tmp__0", newName: "UserId");
AddForeignKey("dbo.UserActivities", "UserId", "dbo.Users", "ID", cascadeDelete: true);
}
}
Is there a bug in EF 6.1.1 that would explain this behavior? It looks like the EF 6 doesn't detect the 2 foreign keys properly.
As a work around, doing 2 separate migrations, the first dropping the foreign keys and a second migration to add the foreign keys seems to resolve the issue.

Entity Framework - TPC - Override Primary Key Column Name

Here's my model:
public abstract class Entity
{
public long Id { get; set; }
}
public abstract class Audit : Entity
{}
public class UserAudit : Audit
{
public virtual User User { get; set; }
}
public class User : Entity
{}
Here's my DbContext:
public class TestDbContext : DbContext
{
static TestDbContext()
{
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new AuditConfiguration());
modelBuilder.Configurations.Add(new UserAuditConfiguration());
modelBuilder.Configurations.Add(new UserConfiguration());
}
}
And here's my mappings:
public abstract class EntityConfiguration<T> : EntityTypeConfiguration<T>
where T : Entity
{
protected EntityConfiguration()
{
HasKey(t => t.Id);
Property(t => t.Id)
.HasColumnName("Key");
}
}
public class AuditConfiguration : EntityConfiguration<Audit>
{}
public class UserAuditConfiguration : EntityTypeConfiguration<UserAudit>
{
public UserAuditConfiguration()
{
Map(m =>
{
m.MapInheritedProperties();
m.ToTable("UserAudits");
});
HasRequired(u => u.User)
.WithMany()
.Map(m => m.MapKey("UserKey"));
}
}
public class UserConfiguration : EntityConfiguration<User>
{}
When I try to generate a migration for this model, I get the following error:
error 2010: The Column 'Id' specified as part of this MSL does not exist in MetadataWorkspace.
If I comment out the ".HasColumnName" call in the constructor of EntityConfiguration, the migration generates correctly (except of course that the column name is Id and not Key).
Is TPC mappings supposed to support primary key columns that don't use the default column name?
The problem appears to be that Entity Framework does not expect me to map Audit. If I remove AuditConfiguration, this works as expected.