How to restart postgres sequence with Entity framework seeding migration? - postgresql

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.

Related

EF Core - duplicate entry in table index when adding record to table with composite keys

I have a class with a composite key. When I try to save a record where a part of that key is contained in another record's key, I am getting an exception although the composite keys as a whole are unique.
Data type with navigation:
public class ProxyInfo
{
[Key, Column(Order = 0, TypeName = "varchar")]
[StringLength(80)]
public string AccountID { get; set; } = string.Empty;
[Key, Column(Order = 1, TypeName = "varchar")]
[StringLength(80)]
public string ProxyID { get; set; } = string.Empty;
[ForeignKey(nameof(ProxyID))]
public virtual UserInfo? Proxy { get; set; }
// a few more properties
}
OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProxyInfo>()
.HasKey(e => new { e.AccountID, e.ProxyID })
.HasName("PK_ProxyInfo");
modelBuilder.Entity<ProxyInfo>()
.HasOne(p => p.Proxy)
.WithOne()
.OnDelete(DeleteBehavior.NoAction);
// more code follows
}
Here's the relevant controller code:
[Route(Routes.Base + "[controller]")]
[ApiController]
public class ProxyInfoApiController : ControllerBase
{
private readonly DS2DbContext _context;
public ProxyInfoApiController(DS2DbContext context)
{
_context = context;
}
[HttpPost("Add")]
[AllowAnonymous]
public async Task<IActionResult> Add([FromBody] ProxyInfo proxyInfo)
{
try
{
_context.ProxyInfo.Add(proxyInfo);
await _context.SaveChangesAsync();
}
catch (Exception e)
{
ServerGlobals.Logger?.Error($"ProxyInfoController.Add: Error '{e.Message}' occurred.");
}
return Ok(); // Created(proxyInfo.ID.ToString(), proxyInfo);
}
}
The error message reads:
A row with a duplicate key cannot be inserted in the dbo.ProxyInfo
object with the unique IX_ProxyInfo_ProxyID-Index. The duplicate
key value is "X".
The complex key I tried to insert was {"B","X"}. The only other record in the ProxyInfo table has key {"A", "X"}; So there should be two different ProxyInfo records referencing the same UserInfo record.
The problem seems to be that an index is being tried to be updated with a value it already contains. However, the indices of both records can be identical, as multiple ProxyInfos can reference the same UserInfo. So actually, no duplicate entry is created. It's just that a 2nd ProxyInfo record uses the same user as the 1st one.
I just found out that the relevant index is created during initial migration with a unique:true attribute. The question is whether I can make EF Core skip updating the index when it already contains an index that it is trying to add again.
I found the problem. It was this statement:
modelBuilder.Entity<ProxyInfo>()
.HasOne(p => p.Proxy)
.WithOne()
.OnDelete(DeleteBehavior.NoAction);
which should have been
modelBuilder.Entity<ProxyInfo>()
.HasOne(p => p.Proxy)
.WithMany()
.OnDelete(DeleteBehavior.NoAction);
What I also didn't know is that when I make changes like this one, I need to create and execute a migration. Once I did that, the index got changed into a non unique one.
I am only doing EF Core/Blazor for a year and I am dealing more with application development than with the framework around it, so this is all new to me and it took me a while to figure it out.

Entity Framework Core (6.0.8) indirect Many to Many relationship with 2 db contexts

I am unable to generate a migration for a many to many relationship that spans 2 different db contexts.
I have 3 entities:
AccountApp, SubscriptionDetail, SubscribedAccountApp
AccountApp and SubscribedAccountApp are the AccountDbContext while the SubscriptionDetail is in a SubscriptionsDbContext.
I want to set up an indirect many to many relationship as described here
Based on the documentation, I have created an EntityTypeConfig for the SubscribedAccountApp like this
public class SubscribedAccountAppConfig : IEntityTypeConfiguration<SubscribedAccountApp>
{
public void Configure(EntityTypeBuilder<SubscribedAccountApp> builder)
{
builder.ToTable("subscribed_account_apps");
builder.HasKey(m => new { m.SubscriptionDetailId, m.AccountAppId });
builder.HasOne(x => x.SubscriptionDetail)
.WithMany(x => x.SubscribedAccountApps)
.HasForeignKey(x => x.SubscriptionDetailId);
builder.HasOne(x => x.AccountApp)
.WithMany(x => x.SubscribedAccountApps)
.HasForeignKey(x => x.AccountAppId);
}
}
This config is then applied to the AccountDbContext like so
public class AccountDbContext : DbContext
{
public IQueryable<AccountApp> AccountApps { get; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new AccountAppConfig());
modelBuilder.ApplyConfiguration(new SubscribedAccountAppConfig());
}
}
The data model for SubsriptionsDetails looks like:
public class SubscriptionDetail
{
private readonly List<SubscribedAccountApp> _subscribedAccountApps;
private readonly List<ConsumptionComponent> _consumptionComponents;
public SubscriptionDetail(
Guid id,
IEnumerable<ConsumptionComponent> consumptionComponents = null,
IEnumerable<SubscribedAccountApp> subscribedAccountApps = null)
{
Id = id;
_consumptionComponents = consumptionComponents ?? new List<ConsumptionComponent>();
_subscribedAccountApps = subscribedAccountApps ?? new List<SubscribedAccountApp>();
}
public Guid Id { get; set; }
public IReadOnlyCollection<ConsumptionComponent> ConsumptionComponents => _consumptionComponents;
public IReadOnlyCollection<SubscribedAccountApp> SubscribedAccountApps => _subscribedAccountApps;
}
The data model for AccountApp looks like:
public class AccountApp
{
public readonly List<SubscribedAccountApp> _subscribedAccountApps;
public AccountApp(Guid id,
Guid accountId,
IEnumerable<SubscribedAccountApp> subscribedAccountApps = null)
{
Id = id;
_subscribedAccountApps = subscribedAccountApps.ToNavigationProperty();
}
public Guid Id { get; private set; }
public IReadOnlyCollection<SubscribedAccountApp> SubscribedAccountApps => _subscribedAccountApps;
}
This leaves me an error when I try to create a migration for the AccountDbContext:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
System.InvalidOperationException: No suitable constructor was found for entity type 'ConsumptionComponent'.
This error describes a ConsumptionComponent constructor for some reason, although this model has already existed and nothing has changed on that model. This model does appear as an IEnumerable in the SubscriptionDetail constructor but I am not sure why it is showing up as an error. All I want this migration to do is add support for the indirect-many-to-many-relationship which has nothing to do with ConsumptionComponent.

Location of Include method influences success or failure

I am writing a unit test to test a method that gets data using Entity Framework and LINQ. I'm using mocked DbSets in my test. The method is returning data from the Orders DbSet, along with a navigation property from the related Customers DbSet, using the extension System.Data.Entity.Include method.
I've run into a weird situation in which, depending on where I call Include, either A) the test succeeds or B) I get an exception. Therein lies my question.
(This is a simplification of the actual code. I realize that these tests, as written below, are silly and pointless.)
public class Order
{
// Primary key
public string OrderId { get; set; }
// Foreign key to Customers table
public string CustomerId { get; set; }
// Navigation property
public virtual Customer Customer {get; set; }
}
public class Customer
{
// Primary key
public string CustomerId { get; set; }
}
public class MyContext : DbContext
{
public virtual DbSet<Customer> Customers { get; set; }
public virtual DbSet<Order> Orders { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.HasKey(p => new { p.OrderId });
modelBuilder.Entity<Order>()
.HasRequired(p => p.Customer)
.WithMany();
modelBuilder.Entity<Customer>()
.HasKey(p => new { p.CustomerId });
}
// ...
}
[TestClass]
public class MyTests
{
// Creates a mock DbSet that can be used for Entity Framework contexts
private static Mock<DbSet<TEntity>> CreateMockDbSet<TEntity>(IEnumerable<TEntity> models) where TEntity : class
{
Mock<DbSet<TEntity>> dbSet = new Mock<DbSet<TEntity>>();
IQueryable<TEntity> queryable = models.AsQueryable();
dbSet.As<IQueryable<TEntity>>().Setup(e => e.ElementType).Returns(queryable.ElementType);
dbSet.As<IQueryable<TEntity>>().Setup(e => e.Expression).Returns(queryable.Expression);
dbSet.As<IQueryable<TEntity>>().Setup(e => e.GetEnumerator()).Returns(queryable.GetEnumerator());
dbSet.As<IQueryable<TEntity>>().Setup(e => e.Provider).Returns(queryable.Provider);
return dbSet;
}
// This test succeeds
[TestMethod]
public void GetOrders1()
{
Mock<DbSet<Customer>> customersDbSet = CreateMockDbSet(new List<Customer>
{
new Customer { CustomerId = "12345" }
});
Mock<DbSet<Order>> ordersDbSet = CreateMockDbSet(new List<Order>
{
new Order { OrderId = "0000000001", CustomerId = "12345" }
});
Mock<MyContext> context = new Mock<MyContext>();
context.Setup(e => e.Customers).Returns(customersDbSet.Object);
context.Setup(e => e.Orders).Returns(ordersDbSet.Object);
// This succeeds
List<Order> orders =
(from o in context.Object.Orders
select o).Include(p => p.Customer).ToList();
Assert.AreEqual(1, orders.Count);
}
// This test results in an exception that says "System.ArgumentNullException: Value cannot be null."
[TestMethod]
public void GetOrders2()
{
Mock<DbSet<Customer>> customersDbSet = CreateMockDbSet(new List<Customer>
{
new Customer { CustomerId = "12345" }
});
Mock<DbSet<Order>> ordersDbSet = CreateMockDbSet(new List<Order>
{
new Order { OrderId = "0000000001", CustomerId = "12345" }
});
Mock<MyContext> context = new Mock<MyContext>();
context.Setup(e => e.Customers).Returns(customersDbSet.Object);
context.Setup(e => e.Orders).Returns(ordersDbSet.Object);
// This fails
List<Order> orders =
(from o in context.Object.Orders.Include(p => p.Customer)
select o).ToList();
Assert.AreEqual(1, orders.Count);
}
}
The two tests are identical, except for the location of the Include method in my LINQ query. I've checked this with a real database attached, and both ways of writing the query result in the same SQL being executed.
Why does the first test method succeed, but the second one results in an exception?

Update optional FK to Required with Automatic Migrations

I have a table which has an optional FK to another table and want to change that FK to a required relationship.
I have Automatic Migrations enabled and enabled destructive changes for this update. All entities in the database also have this key populated.
I changed this:
modelBuilder.Entity<Blog>().HasOptional(b => b.AuthorSecurable).WithMany().Map(b => b.MapKey("AuthorSecurableId"));
to:
modelBuilder.Entity<Blog>().HasRequired(b => b.AuthorSecurable).WithMany().Map(b => b.MapKey("AuthorSecurableId"));
and got the following error:
'FK_dbo.Blogs_dbo.Securables_AuthorSecurableId' is not a constraint.
Could not drop constraint. See previous errors.
There are no previous errors I could see (no inner exception ect.)
This post says you can get around this error with the following:
ALTER TABLE [dbo].[Blogs] NOCHECK CONSTRAINT [FK_dbo.Blogs_dbo.Securables_AuthorSecurable_Id]
so i did:
public override void Up()
{
Sql("ALTER TABLE [dbo].[Blogs] NOCHECK CONSTRAINT [FK_dbo.Blogs_dbo.Securables_AuthorSecurable_Id]");
DropForeignKey("dbo.Blogs", "AuthorSecurableId", "dbo.Securables");
DropIndex("dbo.Blogs", new[] { "AuthorSecurableId" });
AlterColumn("dbo.Blogs", "AuthorSecurableId", c => c.Int(nullable: false));
AddForeignKey("dbo.Blogs", "AuthorSecurableId", "dbo.Securables", "Id", cascadeDelete: true);
CreateIndex("dbo.Blogs", "AuthorSecurableId");
}
But still got the same error
EDIT:
the full code is avaliable here and a minimal models are below:
public class Blog
{
public int Id { get; set; }
public Securable AuthorSecurable { get; set; }
}
public class Securable
{
public int Id { get; set; }
}

EF CTP5 failing to update optional navigation property

I have a many to one association between "Project" and "Template".
Project has a property of type "Template".
The association is not bidirectional ("Template" has no knowledge of "Project").
My entity mapping for the association on "Project" is:
this.HasOptional(p => p.Template);
If I create a "Project" without specifying a template then null is correctly inserted into the "TemplateId" column of the "Projects" table.
If I specify a template then the template's Id is correctly inserted. The SQL generated:
update [Projects]
set [Description] = '' /* #0 */,
[UpdatedOn] = '2011-01-16T14:30:58.00' /* #1 */,
[ProjectTemplateId] = '5d2df249-7ac7-46f4-8e11-ad085c127e10' /* #2 */
where (([Id] = '8c1b2d30-b83e-4229-b0c3-fed2e36bf396' /* #3 */)
and [ProjectTemplateId] is null)
However, if I try to change the template or even set it to null, the templateId is not updated. The SQL generated:
update [Projects]
set [UpdatedOn] = '2011-01-16T14:32:14.00' /* #0 */
where ([Id] = '8c1b2d30-b83e-4229-b0c3-fed2e36bf396' /* #1 */)
As you can see, TemplateId is not updated.
This just does not make sense to me. I have even tried explicitly setting the "Template" property of "Project" to null in my code and when stepping through the code you can see it has absolutely no effect!
Thanks,
Ben
[Update]
Originally I thought this was caused by me forgetting to add the IDbSet property on my DbContext. However, now that I've tested it further I'm not so sure. Below is a complete test case:
public class PortfolioContext : DbContext, IDbContext
{
public PortfolioContext(string connectionStringName) : base(connectionStringName) { }
public IDbSet<Foo> Foos { get; set; }
public IDbSet<Bar> Bars { get; set; }
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder) {
modelBuilder.Configurations.Add(new FooMap());
modelBuilder.Configurations.Add(new BarMap());
base.OnModelCreating(modelBuilder);
}
public new IDbSet<TEntity> Set<TEntity>() where TEntity : class {
return base.Set<TEntity>();
}
}
public class Foo {
public Guid Id { get; set; }
public string Name { get; set; }
public virtual Bar Bar { get; set; }
public Foo()
{
this.Id = Guid.NewGuid();
}
}
public class Bar
{
public Guid Id { get; set; }
public string Name { get; set; }
public Bar()
{
this.Id = Guid.NewGuid();
}
}
public class FooMap : EntityTypeConfiguration<Foo>
{
public FooMap()
{
this.ToTable("Foos");
this.HasKey(f => f.Id);
this.HasOptional(f => f.Bar);
}
}
public class BarMap : EntityTypeConfiguration<Bar>
{
public BarMap()
{
this.ToTable("Bars");
this.HasKey(b => b.Id);
}
}
And the test:
[Test]
public void Template_Test()
{
var ctx = new PortfolioContext("Portfolio");
var foo = new Foo { Name = "Foo" };
var bar = new Bar { Name = "Bar" };
foo.Bar = bar;
ctx.Set<Foo>().Add(foo);
ctx.SaveChanges();
object fooId = foo.Id;
object barId = bar.Id;
ctx.Dispose();
var ctx2 = new PortfolioContext("Portfolio");
var dbFoo = ctx2.Set<Foo>().Find(fooId);
dbFoo.Bar = null; // does not update
ctx2.SaveChanges();
}
Note that this is using SQL CE 4.
Ok, you just need to load the navigation property before doing anything to it. By loading it you essentially register it with ObjectStateManager which EF looks into to generate the update statement as a result of SaveChanges().
using (var context = new Context())
{
var dbFoo = context.Foos.Find(fooId);
((IObjectContextAdapter)context).ObjectContext.LoadProperty(dbFoo, f => f.Bar);
dbFoo.Bar = null;
context.SaveChanges();
}
This code will result in:
exec sp_executesql N'update [dbo].[Foos]
set [BarId] = null
where (([Id] = #0) and ([BarId] = #1))
',N'#0 uniqueidentifier,#1 uniqueidentifier',#0='A0B9E718-DA54-4DB0-80DA-C7C004189EF8',#1='28525F74-5108-447F-8881-EB67CCA1E97F'
If this is a bug in EF CTP5 (and not my code :p) there are two workarounds that I came up with.
1) Make the association Bi-Directional. In my case this meant adding the following to my ProjectTemplate class:
public virtual ICollection<Project> Projects {get;set;}
With this done, in order to set the "Template" property of project to null, you can just remove the project from the template - a little backward but it works:
var project = repo.GetById(id);
var template = project.Template;
template.Projects.Remove(project);
// save changes
2) The second option (which I preferred but it still smells) is to add the foreign key on your domain object. In my case I had to add the following to Project:
public Guid? TemplateId { get; set; }
public virtual ProjectTemplate Template { get; set; }
Make sure the Foreign key is a nullable type.
I then had to change my mapping like so:
this.HasOptional(p => p.Template)
.WithMany()
.HasForeignKey(p => p.TemplateId);
Then, in order to set the Template to null, I added a helper method to Project (it does actually work just by setting the foreign key to null):
public virtual void RemoveTemplate() {
this.TemplateId = null;
this.Template = null;
}
I can't say that I'm happy about polluting my domain model with foreign keys but I couldn't find any alternatives.
Hope this helps.
Ben