-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Insert record sequentially in DB without identity on PK #26480
Comments
No - databases definitely aren't locked while transactions are in progress. If two of your transactions are running in parallel, they may get the same "next ID", and one will fail. The proper tool for this is likely a database sequence - have you considered this? Sequences guarantees unique returned values via atomic operations in the database. You would use simple raw SQL to get the next value from the sequence ( Otherwise I believe you can somehow configure for the whole table to be locked, but I'd really recommend avoiding that unless absolutely necessary. |
@roji Currently thinking about these 3 things as sequences:
and I am already doing it inside my raw SQL code. Sorry for this kind of a strange question, I am just confused a bit. I haven't dabbed this much into DB work until this issue arose. |
No - although they're somewhat similar, sequences and SQL Server IDENTITY columns are two different things. Sequences are independent database objects which you create with
No, you'd be fetching the next ID by doing BTW, above I was assuming that you cannot change your database schema in any way. If you can add default values to your ID columns, then you can make them populate automatically for the sequence; you would no longer send the next ID yourself, and since it's missing, the column's default value would executethe |
@roji Thank you a lot Shay (if I may refer to you as that). You helped me a lot. Thanks a lot for pushing me in a right direction with this. |
@roji Here is update. I managed to make it work. I want to thank you once again million times for helping me deal with this issue. What didn't work:I followed this documentation: https://docs.microsoft.com/en-us/ef/core/modeling/sequences, I was ecstatic to find this could actually be "automated" (read, I could just add them to context and commit changes using my UoW) and I won't be having the need to write the SQL queries when inserting data all the time. My
But since my Oid (PK) is not nullable, whenever I tried to insert my document like so:
It produced this SQL (beautified for visibility purposes):
And naturally I got exception: Since it is default value of long (non-nullable) OID value to 0, when creating doc variable, as it is not nullable. NOTE: Be very very cautious if you are using this approach even if you can. It will alter all of your Stored procedures where this OID is used.
and I had to manually ALTER each table to drop these constrains. What worked:Again, thanks to a really awesome human being from EF Core team, Roji, I managed to implement this to work.
Added migration that produced this code:
Updated database, and for DB work that was it. with: Full code of my method is here:
and SQL it generates is:
|
@Uraharadono when you configure a property with HasDefaultValueSql, and you don't explicitly set a value when inserting, then EF Core should not be sending a value to the database (the zero above), letting the configured database default happen. I'm not sure exactly what's going on in your code, but see the following minimal sample: Program codeawait using var ctx = new BlogContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
ctx.Blogs.Add(new() { Name = "foo" });
await ctx.SaveChangesAsync();
public class BlogContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0")
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasSequence<long>("KlantDocumentSeq")
.StartsAt(2000)
.IncrementsBy(1)
.HasMin(2000);
modelBuilder.Entity<Blog>().Property(b => b.Id).HasDefaultValueSql("NEXT VALUE FOR KlantDocumentSeq");
}
}
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
} Running the above produces the following INSERT SQL: SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([Id] int);
INSERT INTO [Blogs] ([Name])
OUTPUT INSERTED.[Id]
INTO @inserted0
VALUES (@p0);
SELECT [i].[Id] FROM @inserted0 i; As you can see, Check your exact code to see what is different, and if you're still having issues getting this to work, please try posting a minimal code sample (like the above - you can even tweak it) to reproduce the problematic behavior. |
Hello @roji, Setup is as follows:
I have setup 3 ways to try to insert new Blog record, and every one of 3 fails:
produces:
produces:
produces:
I don't know what the deal is, as in 2nd migration there is |
@Uraharadono you have |
@roji Thanks a lot for coming back to me once again. Can I just ask another thing: Or to put more perspective and sense into this whole mess of a question. My code first entities are created using EF Scaffold command on existing (17 y.o. DB), and it generated this code for this table:
So it has automatically generated |
Yes - you probably want to take a look at our docs, a lot of this stuff is covered there.
I assume you're asking why the ValueGeneratedNever is in the scaffolded model... So I'm guessing that the database which was scaffolded simply did not have IDENTITY columns or default SQL (you added that later). Since Oid was the key, and by convention integer keys are IDENTITY, the scaffolding set ValueGeneratedNever to make your model actually match your database. Basically the model matches the database at the point where it was scaffolded, but you're now making adjustments to it. |
I asked this on SO as well, unfortunately I didn't get any number of notable responses:
https://stackoverflow.com/questions/69750232/ef-core-insert-record-sequentially-in-db-without-identity-on-pk
Disclaimer: This question is regarding "modernization" of almost 17 years old system. ORM that was used 17 years ago required that PK's don't use Identity.
I know how bad it is, and I can't change anything regarding this.
So I have (simplified) following table in database:
As you can see, table doesn't have Identity set on PK, so it has to be inserted manually.
Project is being rebuilt in Web Api .Net Core 5, that is going to do all of the CRUD operations.
It is using EF Core as ORM, and it is agreed that Unit of Work pattern is going to be used here (please do keep reading, UoW is not issue here).
For those curious or for what it's worth, you can take a look at it here (https://pastebin.com/58bSDkUZ) ( this is by no means full UoW, just partially and without comments).
Controller Action:
When I am inserting record I am doing it like so:
I get "Duplicate key" exception because 2 of the records are overlapping when inserting.
I have tried to wrap it into the transaction like so:
and it does not work. I still get "Duplicate key" exception.
From what I know, shouldn't EF by default lock database until transaction is complete?
I tried to manually write insert SQL like so:
Same thing.
I did a lot research to try to tackle this issue:
As noted above, ORM at the time did some magic where it did sequentially insert records in database and I would like to keep it that way.
Is there any way that I can achieve sequential insert when dealing with given scenario?
The text was updated successfully, but these errors were encountered: