In this post, let's see how we can use Hi/Lo algorithm
as the key generation strategy in EF Core. This can be achieved using UseHiLo
method.
As always let's go by an example.
Consider the below code.
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int CategoryId { get; set; }
}
And MyDbContext is setup like this.
public class MyDbContext : DbContext
{
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer("ConnectionString")
.LogTo(Console.WriteLine, LogLevel.Information);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>(builder =>
{
builder
.HasMany(x => x.Products)
.WithOne()
.HasForeignKey(x => x.CategoryId);
});
}
}
And now I am adding a Category and a Product.
using var context = new MyDbContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
var category = new Category() { Name = "Some Category" };
context.Categories.Add(category);
var product = new Product { Name = "Some Product", CategoryId = category.Id };
context.Products.Add(product);
await context.SaveChangesAsync()
The SaveChanges here will fail here with the following error: "The INSERT
statement conflicted with the FOREIGN KEY constraint
"FK_Products_Categories_CategoryId"". The reason is, we are setting the
CategoryId of the Product, but it's still 0 because the
Category record is still not saved to the database.
In this kind of a scenario, using HiLo algorithm to determine the Id before
the record is actually saved in the database is quite handy.
Enabling HiLo is pretty easy. You can either enable it for all the entities or
for a specific entity.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// HiLo Id generation For all the entities
//modelBuilder.UseHiLo();
modelBuilder.Entity<Category>(builder =>
{
builder
.HasMany(x => x.Products)
.WithOne()
.HasForeignKey(x => x.CategoryId);
// HiLo Id generation only for Category
builder
.Property(x => x.Id)
.UseHiLo("categoryseq");
});
}
After this change, if you run the example, you are not going to see the
previous error, instead, you should see Category now has a valid Id.
Valid CategoryId |
Here something important to note is if you do UseHilo() for all the
entities, the Ids are going to be kind of messed up, if you take the above
example, the Id of the Product here will be 2.
Now let's try 2 simultaneous adding. While the control is waiting on
SaveChanges, I am starting another instance of the application.
Now how is this working?
If you add in database migration, you will see EF is creating a Sequence,
something like below.
migrationBuilder.CreateSequence(
name: "categoryseq",
incrementBy: 10);
And once the database is updated, you can see the created sequence here.
Sequences |
categoryseq |
Now we are creating a new database context and adding a category to context (context.Categories.Add(category)), EF will run a query like this.
SELECT NEXT VALUE FOR [categoryseq]
This will return 1, and the category will be assigned the value of 1 and it has now blocked Ids from 1 to 10. If we add in another category in the same database context, EF won't run the above query again, it will increment the Id by 1 and when we have added 10 categories, EF will run the above query again to get the next value, which will be 11. But imagine while we are adding these 10 categories, some other user creates a new database context and starts adding categories, then that process will also call the above query, so this time he will get 11 (because so far we have only blocked from 1 to 10). And when we call for next value, we will get 21, because that process has now blocked 11 to 20.
Isn't it nice? But there is a kind of downside here. This can cause missing Ids (if you are worried about the order of the Ids), but generally, it shouldn't be an issue.
Hope this helps!
Happy Coding.
Regards,
Jaliya