How do I make Entity Framework 6 (DB First) explicitly insert a Guid/UniqueIdentifier primary key? How do I make Entity Framework 6 (DB First) explicitly insert a Guid/UniqueIdentifier primary key? sql-server sql-server

How do I make Entity Framework 6 (DB First) explicitly insert a Guid/UniqueIdentifier primary key?


A solution could be to override DbContext SaveChanges. In this function find all added entries of the DbSets of which you want to specify the Id.

If the Id is not specified yet, specify one, if it is already specified: use the specified one.

Override all SaveChanges:

public override void SaveChanges(){    GenerateIds();    return base.SaveChanges();}public override async Task<int> SaveChangesAsync(){    GenerateIds();    return await base.SaveChangesAsync();}public override async Task<int> SaveChangesAsync(System.Threading CancellationToken token){    GenerateIds();    return await base.SaveChangesAsync(token);}

GenerateIds should check if you already provided an Id for your added entries or not. If not, provide one.

I'm not sure if all DbSets should have the requested feature, or only some. To check whether the primary key is already filled, I need to know the identifier of the primary key.

I see in your class CRMEntity that you know that every T has an Id, this is because this Id is in CRMEntityBase, or in IGuid, let's assume it is in IGuid. If it is in CRMEntityBase change the following accordingly.

The following is in small steps; if desired you can create one big LINQ.

private void GenerateIds(){    // fetch all added entries that have IGuid    IEnumerable<IGuid> addedIGuidEntries = this.ChangeTracker.Entries()        .Where(entry => entry.State == EntityState.Added)        .OfType<IGuid>()    // if IGuid.Id is default: generate a new Id, otherwise leave it    foreach (IGuid entry in addedIGuidEntries)    {        if (entry.Id == default(Guid)            // no value provided yet: provide it now            entry.Id = GenerateGuidId() // TODO: implement function        // else: Id already provided; use this Id.    }}

That is all. Because all your IGuid objects now have a non-default ID (either pre-defined, or generated inside GenerateId) EF will use that Id.

Addition: HasDatabaseGeneratedOption

As xr280xr pointed out in one of the comments, I forgot that you have to tell entity framework that entity framework should not (always) generate an Id.

As an example I do the same with a simple database with Blogs and Posts. A one-to-many relation between Blogs and Posts. To show that the idea does not depend on GUID, the primary key is a long.

// If an entity class is derived from ISelfGeneratedId,// entity framework should not generate Idsinterface ISelfGeneratedId{    public long Id {get; set;}}class Blog : ISelfGeneratedId{    public long Id {get; set;}          // Primary key    // a Blog has zero or more Posts:    public virtual ICollection><Post> Posts {get; set;}    public string Author {get; set;}    ...}class Post : ISelfGeneratedId{    public long Id {get; set;}           // Primary Key    // every Post belongs to one Blog:    public long BlogId {get; set;}    public virtual Blog Blog {get; set;}    public string Title {get; set;}    ...}

Now the interesting part: The fluent API that informs Entity Framework that the values for primary keys are already generated.

I prefer fluent API avobe the use of attributes, because the use of fluent API allows me to re-use the entity classes in different database models, simply by rewriting Dbcontext.OnModelCreating.

For example, in some databases I like my DateTime objects a DateTime2, and in some I need them to be simple DateTime. Sometimes I want self generated Ids, sometimes (like in unit tests) I don't need that.

class MyDbContext : Dbcontext{    public DbSet<Blog> Blogs {get; set;}    public DbSet<Post> Posts {get; set;}    protected override void OnModelCreating(DbModelBuilder modelBuilder)    {         // Entity framework should not generate Id for Blogs:         modelBuilder.Entity<Blog>()             .Property(blog => blog.Id)             .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);         // Entity framework should not generate Id for Posts:         modelBuilder.Entity<Blog>()             .Property(blog => blog.Id)             .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);         ... // other fluent API    }

SaveChanges is similar as I wrote above. GenerateIds is slightly different. In this example I have not the problem that sometimes the Id is already filled. Every added element that implements ISelfGeneratedId should generate an Id

private void GenerateIds(){    // fetch all added entries that implement ISelfGeneratedId    var addedIdEntries = this.ChangeTracker.Entries()        .Where(entry => entry.State == EntityState.Added)        .OfType<ISelfGeneratedId>()    foreach (ISelfGeneratedId entry in addedIdEntries)    {        entry.Id = this.GenerateId() ;// TODO: implement function        // now you see why I need the interface:        // I need to know the primary key    }}

For those who are looking for a neat Id generator: I often use the same generator as Twitter uses, one that can handle several servers, without the problem that everyone can guess from the primary key how many items are added.

It's in Nuget IdGen package


I see 2 challenges:

  1. Making your Id field an identity with auto generated value will prevent you from specifying your own GUID.
  2. Removing the auto generated option may create duplicate key exceptions if the user forgets to explicitly create a new id.

Simplest solution:

  1. Remove auto generated value
  2. Ensure Id is a PK and is required
  3. Generate a new Guid for your Id in the default constructor of your models.

Example Model

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

Usage

// "Auto id"var person1 = new Person();// Manualvar person2 = new Person{    Id = new Guid("5d7aead1-e8de-4099-a035-4d17abb794b7")}

This will satisfy both of your needs while keeping the db safe. The only down side of this is you have to do this for all models.

If you go with this approach, I'd rather see a factory method on the model which will give me the object with default values (Id populated) and eliminate the default constructor. IMHO, hiding default value setters in the default constructor is never a good thing. I'd rather have my factory method do that for me and know that the new object is populated with default values (with intention).

public class Person{    public Guid Id { get; set; }    public static Person Create()    {        return new Person { Id = Guid.NewGuid() };    }}

Usage

// New person with default values (new Id)var person1 = Person.Create();// Empty Guid Idvar person2 = new Person();// Manually populated Idvar person3 = new Person { Id = Guid.NewGuid() };


I don't think there is a real answer for this one...

As said here How can I force entity framework to insert identity columns? you can enable the mode #2, but it'll break #1.

using (var dataContext = new DataModelContainer())using (var transaction = dataContext.Database.BeginTransaction()){  var user = new User()  {    ID = id,    Name = "John"  };  dataContext.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[User] ON");  dataContext.User.Add(user);  dataContext.SaveChanges();  dataContext.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[User] OFF");  transaction.Commit();}

you should change value of StoreGeneratedPattern property of identity column from Identity to None in model designer.

Note, changing of StoreGeneratedPattern to None will fail inserting of object without specified id

As you can see, you're no longer able to insert without setting by yourself an ID.

But, if you look on the bright side : Guid.NewGuid() will allow you to make a new GUID without the DB generation function.