patterncsharpMinor
Using T-SQL SEQUENCE with Entity Framework 6
Viewed 0 times
sqlwithsequenceusingframeworkentity
Problem
After reading many posts about how to handle sequences in EF 6 (see below) I found a way that was working for me. I'm using Code First and want to include the sequence from scratch, so without using migrations (tell me if I'm getting this wrong).
Task was to get an unique and gapless incrementing extra ID for my entity Car, which is not being used as primary key.
Here is what I'm doing now:
Entity:
Context:
```
public class Context : DbContext
{
public Context() : base()
{
SetDefaultConfiguration();
}
public Context(string connectionString) : base(connectionString)
{
SetDefaultConfiguration();
}
public Context(DbConnection existingConnection) : base(existingConnection, true)
{
SetDefaultConfiguration();
}
public override int SaveChanges()
{
SetCarNr();
return base.SaveChanges();
}
public override Task SaveChangesAsync()
{
SetCarNr();
return base.SaveChangesAsync();
}
public override Task SaveChangesAsync(CancellationToken cancellationToken)
{
SetCarNr();
return base.SaveChangesAsync(cancellationToken);
}
private void SetDefaultConfiguration()
{
Database.SetInitializer(new CreateSequenceInitializer());
}
public class CreateSequenceInitializer : DropCreateDatabaseIfModelChanges
{
protected override void Seed(Context context)
{
context.Database.ExecuteSqlCommand("CREATE SEQUENCE CarNrSequence AS INT START WITH 200000 NO CACHE;");
base.Seed(context);
}
}
private void SetCarNr()
{
var carsToSave = ChangeTracker.Entries().Select(e => e.Entity).OfType();
foreach (var c in carsToSave)
{
c.CarNr = GetNextSequenceValue();
}
}
public int GetNextSequence
Task was to get an unique and gapless incrementing extra ID for my entity Car, which is not being used as primary key.
Here is what I'm doing now:
Entity:
public class Car
{
public int Id { get; set; }
public int CarNr { get; set; }
public string Name { get; set; }
}Context:
```
public class Context : DbContext
{
public Context() : base()
{
SetDefaultConfiguration();
}
public Context(string connectionString) : base(connectionString)
{
SetDefaultConfiguration();
}
public Context(DbConnection existingConnection) : base(existingConnection, true)
{
SetDefaultConfiguration();
}
public override int SaveChanges()
{
SetCarNr();
return base.SaveChanges();
}
public override Task SaveChangesAsync()
{
SetCarNr();
return base.SaveChangesAsync();
}
public override Task SaveChangesAsync(CancellationToken cancellationToken)
{
SetCarNr();
return base.SaveChangesAsync(cancellationToken);
}
private void SetDefaultConfiguration()
{
Database.SetInitializer(new CreateSequenceInitializer());
}
public class CreateSequenceInitializer : DropCreateDatabaseIfModelChanges
{
protected override void Seed(Context context)
{
context.Database.ExecuteSqlCommand("CREATE SEQUENCE CarNrSequence AS INT START WITH 200000 NO CACHE;");
base.Seed(context);
}
}
private void SetCarNr()
{
var carsToSave = ChangeTracker.Entries().Select(e => e.Entity).OfType();
foreach (var c in carsToSave)
{
c.CarNr = GetNextSequenceValue();
}
}
public int GetNextSequence
Solution
Your C# code looks fine, but using non-cached sequences instead of IDENTITY (which would be much easier to implement in Entity Framework) does not ensure a gapless incrementing identifier.
According to this Microsoft article, sequences can generate gaps in some scenarios:
The sequence object generates numbers according to its definition, but
the sequence object does not control how the numbers are used.
Sequence numbers inserted into a table can have gaps when a
transaction is rolled back, when a sequence object is shared by
multiple tables, or when sequence numbers are allocated without using
them in tables.
So, error on saving changes will have consumed the sequence numbers and thus you obtain gaps.
On the database side, this thread approaches the generation of gapless incrementing identifier, but I am also thinking about generating identifiers within the application layer and restoring the value on save failure:
However, this solution may work only if identifiers are generated by the application layer only.
According to this Microsoft article, sequences can generate gaps in some scenarios:
The sequence object generates numbers according to its definition, but
the sequence object does not control how the numbers are used.
Sequence numbers inserted into a table can have gaps when a
transaction is rolled back, when a sequence object is shared by
multiple tables, or when sequence numbers are allocated without using
them in tables.
So, error on saving changes will have consumed the sequence numbers and thus you obtain gaps.
On the database side, this thread approaches the generation of gapless incrementing identifier, but I am also thinking about generating identifiers within the application layer and restoring the value on save failure:
private int PrevSequenceId;
private int SequenceId;
private void SetCarNr()
{
var carsToSave = ChangeTracker.Entries().Select(e => e.Entity).OfType();
foreach (var c in carsToSave)
{
c.CarNr = Interlocked.Increment(ref SequenceId);
}
}
public override int SaveChanges()
{
try
{
SetCarNr();
return base.SaveChanges();
// locking might be required here for thread safety
PrevSequenceId = SequenceId;
}
catch (Exception exc)
{
// log stuff if needed
// locking might be required here for thread safety
SequenceId = PrevSequenceId;
}
}However, this solution may work only if identifiers are generated by the application layer only.
Code Snippets
private int PrevSequenceId;
private int SequenceId;
private void SetCarNr()
{
var carsToSave = ChangeTracker.Entries().Select(e => e.Entity).OfType<Car>();
foreach (var c in carsToSave)
{
c.CarNr = Interlocked.Increment(ref SequenceId);
}
}
public override int SaveChanges()
{
try
{
SetCarNr();
return base.SaveChanges();
// locking might be required here for thread safety
PrevSequenceId = SequenceId;
}
catch (Exception exc)
{
// log stuff if needed
// locking might be required here for thread safety
SequenceId = PrevSequenceId;
}
}Context
StackExchange Code Review Q#150060, answer score: 3
Revisions (0)
No revisions yet.