Guid Comb Strategy in Ef

GUID COMB strategy in EF

I guess you are using SQL server as your database. This is nice example of inconsistency among different MS tools. SQL server team doesn't recommend using newid() as default value for UNIQUEIDENTIFIER columns and ADO.NET team use it if you specify Guid property as autogenerated in the database. They should use newsequentialid() instead!

If you want sequential Guids generated by database you must modify generated table and it is really complex because you must find autogenerated default constraint, drop it and create new constraint. This all can be done in custom database initializer. Here you have my sample code:

class Program
{

static void Main(string[] args)
{
Database.SetInitializer(new CustomInitializer());
using (var context = new Context())
{
context.TestEntities.Add(new TestEntity() { Name = "A" });
context.TestEntities.Add(new TestEntity() { Name = "B" });
context.SaveChanges();
}
}
}

public class CustomInitializer : DropCreateDatabaseAlways<Context>
{
protected override void Seed(Context context)
{
base.Seed(context);

context.Database.ExecuteSqlCommand(@"
DECLARE @Name VARCHAR(100)

SELECT @Name = O.Name FROM sys.objects AS O
INNER JOIN sys.tables AS T ON O.parent_object_id = T.object_id
WHERE O.type_desc LIKE 'DEFAULT_CONSTRAINT'
AND O.Name LIKE 'DF__TestEntities__Id__%'
AND T.Name = 'TestEntities'

DECLARE @Sql NVARCHAR(2000) = 'ALTER TABLE TestEntities DROP Constraint ' + @Name

EXEC sp_executesql @Sql

ALTER TABLE TestEntities
ADD CONSTRAINT IdDef DEFAULT NEWSEQUENTIALID() FOR Id");
}
}

public class TestEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
}

public class Context : DbContext
{
public DbSet<TestEntity> TestEntities { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

modelBuilder.Entity<TestEntity>()
.Property(e => e.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}

Generate Guid on Serverside Entity Framework 5?

If you want to generate the key on the server, simply do this in code:

public class TestObject 
{
public TestObject()
{
Id = Guid.NewGuid();
}
public Guid Id { get; set; }
}

If you want the database to generate the key, then use the DatabaseGenerated attribute:

public class TestObject 
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
}

If you're after using sequential GUIDs, then there's no easy answer at the moment. Some examples which get you along the right road:

  • Generating IDs in the SaveChanges method
  • Calling your own NewGuid method
  • Use a non-EF method to change the default value for the identity field from NEWID() to NEWSEQUENTIALID()

SQL Server Sequential Guid as a key when using Entity Framework Code First

Yes, you must map your Key property. Let's assume you have an entity like:

public class MyEntity
{
public virtual Guid Key { get; set; }
...
}

Then you can define DbContext derived class like:

public class Context : DbContext
{
public DbSet<MyEntity> MyEntities { get; private set; }

public Context()
: base("connection")
{
MyEntities = Set<MyEntity>();
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

modelBuilder.Entity<MyEntity>().HasKey(e => e.Key);
modelBuilder.Entity<MyEntity>()
.Property(e => e.Key)
.HasDatabaseGeneratedOption(DatabaseGenerationOption.Identity);

// Other mapping
}
}

Or you can simply define your entity with Data Annotations:

public class MyEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual Guid Key { get; set; }
...
}

Edit:

This works if the mapping is used with existing database but if you want EF code-first to create database for you it will use normal (not sequential) guids! Check this question for possible solutions in case of database generation.

Entity Framework 5 code first newsequentialid() as PK

If you are using migrations you should be able to simply modify code based migration to use newsequentialid() as DefaultValueSql.

public override void Up() {
CreateTable(
"SomeTable",
c => new {
Id = c.Guid(nullable: false, defaultValueSql: "newsequentialid()"),
})
.PrimaryKey(t => t.Id)
);
}

If you are not using migrations check this question.

Sequential GUID in Linq-to-Sql?

COMBs are generated the following way:

DECLARE @aGuid UNIQUEIDENTIFIER

SET @aGuid = CAST(CAST(NEWID() AS BINARY(10)) + CAST(GETDATE() AS BINARY(6)) AS UNIQUEIDENTIFIER)

Which transcribed into C# would look like this:

    public static unsafe Guid CombGuid()
{
Guid guid = Guid.NewGuid();
byte[] bytes = guid.ToByteArray();
long ticks = DateTime.Now.Ticks;
fixed( byte* pByte = bytes )
{
int* pFirst = (int *)(pByte + 10);
short* pNext = (short*)(pByte + 14);
*pFirst = (int)(ticks & 0xFFFFFF00);
*pNext = (short)ticks;
}

return new Guid( bytes );
}

Does Entity Framework 4 Code First have support for identity generators like NHibernate?

No, Entity framework code-first is still just nice wrapper around EFv4. There are no NHibernate like generators. If you want client side Id generator you will have to override SaveChanges in derived DbContext and implement your own logic of assigning Ids to new entities.

Edit:

Some high level example:

public class Context : DbContext
{
// Helper for example
// DO NOT USE IN REAL SCENARIOS!!!
private static int i = 0;

public DbSet<MyEntity> MyEntities { get; private set; }

public Context()
: base("connection")
{
MyEntities = Set<MyEntity>();
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

modelBuilder.Entity<MyEntity>().HasKey(e => e.Id);
// Turn off autogeneration in database
modelBuilder.Entity<MyEntity>()
.Property(e => e.Id)
.HasDatabaseGeneratedOption(HasDatabaseGeneratedOption.None);

// Other mapping
}

public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<MyEntity>()
.Where(e => e.State == EntityState.Added))
{
// Here you have to add some logic to generate Id
// I'm using just static field
entry.Entity.Id = ++i;
}

return base.SaveChanges();
}
}

public class MyEntity
{
public int Id { get; set; }
// Other properties
}

Does Entity Framework 4 Code First have support for identity generators like NHibernate?

No, Entity framework code-first is still just nice wrapper around EFv4. There are no NHibernate like generators. If you want client side Id generator you will have to override SaveChanges in derived DbContext and implement your own logic of assigning Ids to new entities.

Edit:

Some high level example:

public class Context : DbContext
{
// Helper for example
// DO NOT USE IN REAL SCENARIOS!!!
private static int i = 0;

public DbSet<MyEntity> MyEntities { get; private set; }

public Context()
: base("connection")
{
MyEntities = Set<MyEntity>();
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

modelBuilder.Entity<MyEntity>().HasKey(e => e.Id);
// Turn off autogeneration in database
modelBuilder.Entity<MyEntity>()
.Property(e => e.Id)
.HasDatabaseGeneratedOption(HasDatabaseGeneratedOption.None);

// Other mapping
}

public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<MyEntity>()
.Where(e => e.State == EntityState.Added))
{
// Here you have to add some logic to generate Id
// I'm using just static field
entry.Entity.Id = ++i;
}

return base.SaveChanges();
}
}

public class MyEntity
{
public int Id { get; set; }
// Other properties
}

Entity Framework Core Auto Generated guid

The problem you are experiencing is not specific for autogenerated Guids. The same happens for any autogenerated key values, including the commonly used auto increment (identity) columns.

It's caused by a specific Data Seeding (HasData) requirement:

This type of seed data is managed by migrations and the script to update the data that's already in the database needs to be generated without connecting to the database. This imposes some restrictions:

  • The primary key value needs to be specified even if it's usually generated by the database. It will be used to detect data changes between migrations.
  • Previously seeded data will be removed if the primary key is changed in any way.

Note the first bullet. So while for normal CRUD your PK will be auto generated, you are required to specify it when using HasData fluent API, and the value must be constant (not changing), so you can't use Guid.NewGuid(). So you need to generate several Guids, take their string representation and use something like this:

mb.Entity<User>().HasData(
new User() { Id = new Guid("pre generated value 1"), ... },
new User() { Id = new Guid("pre generated value 2"), ... },
new User() { Id = new Guid("pre generated value 3"), ... }
);

Entity Framework 4.1: Name constraints

Could you run some raw sql to rename them after creation? You may need to disable the EdmMetadata convention.

context.Database.ExecuteSqlCommand(
@"DECLARE @pk sysname
SELECT @pk = name FROM sysobjects WHERE parent_obj = object_id('users') and xtype = 'pk'
EXEC sp_rename @pk, 'pk_users_UserId'
");


Related Topics



Leave a reply



Submit