19

Can some One guide me I want primeryKey of a table as guid having db generated value on insert.

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

but it's giving error

The seed entity for entity type 'User' cannot be added because there was no value provided for the required property 'Id'.

Here is my actual model classes and DbContxt class:

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

     [Required]
     [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
     public DateTime CreatedOn { get; set; } = DateTime.UtcNow;


     [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
     public DateTime? UpdatedOn { get; set; }

     [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
     public DateTime LastAccessed { get; set; }
 }

 public class User : BaseModel
 {
        [Required]
        [MinLength(3)]
        public string Name { get; set; }

        [Required]
        [MinLength(3)]
        [EmailAddress]
        public string Email { get; set; }

        [Required]
        [MinLength(6)]
        public string Password { get; set; }
  }

Then in the MyDbContext:

public class MyDbContext: DbContext
    {
        public MyDbContext(DbContextOptions<MyDbContext> options)
            : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder mb)
        {
            base.OnModelCreating(mb);
            
            mb.Entity<User>().HasData(
                new User() { Email = "Mubeen@gmail.com", Name = "Mubeen", Password = "123123" },
                new User() { Email = "Tahir@gmail.com", Name = "Tahir", Password = "321321" },
                new User() { Email = "Cheema@gmail.com", Name = "Cheema", Password = "123321" }
                );
        }

        public DbSet<User> User { get; set; }
    }

Any help please!

ViRuSTriNiTy
  • 5,017
  • 2
  • 32
  • 58
Mubeen
  • 351
  • 1
  • 2
  • 15
  • GUID primary keys are usually required, when you need meaningful primary keys *before* inserting data in database (e.g., there are client apps, that later synchronize data with main database). In other words, the only advantage from GUID PK is ability to generate it at client side. Everything else are disadvantages: GUIDs require more disk space, can't be clustered PKs, etc. But you want to throw this advantage away and generate GUID at server side. Why? – Dennis Nov 27 '18 at 06:49
  • 1
    If you *insist* on have Guid as primary key, at least have the clustered index in some other field, like an *int* and let that use an identity increment. Guids are not sequential, so having a clustered index on them is bad, f.ex. when inserting new rows, other rows often have to rearrange for the new guid in the middle of the old guids, not at the end, which is the case with an increasing int. (In sql server, the primary key field defaults to clustered index as well) – Richard Anderssen Nov 15 '19 at 13:51

8 Answers8

26

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"), ... }
    );
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
15

[DatabaseGenerated(DatabaseGeneratedOption.Identity)] on GUID field works on Entity Framework 6.x, may be not in EF Core yet!

So the solution is:

1) First write your BaseModel class as follows:

public class BaseModel
{
    [Key]
    public Guid Id { get; set; }

    public DateTime CreatedOn { get; set; } = DateTime.UtcNow;

    public DateTime? UpdatedOn { get; set; }

    public DateTime LastAccessed { get; set; }
}

2) Then OnModelCreating() method in your DbContext should be as follows:

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

      modelBuilder.Entity<YourEntity>().Property(x => x.Id).HasDefaultValueSql("NEWID()");

      modelBuilder.Entity<User>().HasData(
            new User() { Id  = Guid.NewGuid(), Email = "Mubeen@gmail.com", Name = "Mubeen", Password = "123123" },
            new User() { Id = Guid.NewGuid(), Email = "Tahir@gmail.com", Name = "Tahir", Password = "321321" },
            new User() { Id = Guid.NewGuid(),  Email = "Cheema@gmail.com", Name = "Cheema", Password = "123321" }
            );
 }

Now create a brand new migration and update the database accordingly. Hope your problem will be solved!

TanvirArjel
  • 30,049
  • 14
  • 78
  • 114
  • (out of curiosity) How does `Guid.NewGuid()` play with Ivan Stoev's comment above, about having hard-coded Guids? –  Jan 09 '20 at 15:33
10

Rather than manually updating the migration, you could also implement an update in the OnModelCreating that adds it into the migration for you, e.g.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // guid identities
    foreach (var entity in modelBuilder.Model.GetEntityTypes()
        .Where(t =>
            t.ClrType.GetProperties()
                .Any(p => p.CustomAttributes.Any(a => a.AttributeType == typeof(DatabaseGeneratedAttribute)))))
    {
        foreach (var property in entity.ClrType.GetProperties()
            .Where(p => p.PropertyType == typeof(Guid) && p.CustomAttributes.Any(a => a.AttributeType == typeof(DatabaseGeneratedAttribute))))
        {
            modelBuilder
                .Entity(entity.ClrType)
                .Property(property.Name)
                .HasDefaultValueSql("newsequentialid()");
        }
    }

}  
cmorgan091
  • 511
  • 6
  • 7
  • Thanks for the inspiration, this really helped me to migrate faster my .net framework code. I also made an extension using this an other custom properties https://gist.github.com/DiomedesDominguez/797711e093fd15eff4265ae9d5dc27b9#file-modelbuilderextensions-cs – Diomedes Domínguez Jan 09 '20 at 15:09
8

You can use defaultValueSql: "newid()" in your Code First Migration file.

For Example;

 public override void Up()
    {
        CreateTable(
            "dbo.ExampleTable",
            c => new
            {
                Id = c.Guid(nullable: false, identity: true, defaultValueSql: "newid()"),               
                RowGuid = c.Guid(nullable: false, defaultValueSql: "newid()"),

            })
            .PrimaryKey(t => t.Id);           
    }
Kıvanç B.
  • 152
  • 4
4

GUID is handled by Identity framework and not by EF, so to use GUID you need to do as the following

[Key]
public Guid Id { get; set; }

and then prepare the model before insert into table

new User() { Id = Guid.NewGuid(), Email = "Mubeen@gmail.com", Name = "Mubeen", Password = "123123" },
Saif
  • 2,611
  • 3
  • 17
  • 37
2

If all you need is getting an automatic value when creating and saving a new entity, you can also do it in the constructor:

public class BaseModel
{
   public BaseModel()
   {
      Id = new Guid();
   }

   [Key]
   public Guid Id { get; set; }

   public DateTime CreatedOn { get; set; } = DateTime.UtcNow;

   public DateTime? UpdatedOn { get; set; }

   public DateTime LastAccessed { get; set; }
}
Marc Annous
  • 417
  • 5
  • 13
1
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
        {
            b.Property<string>("Id").HasMaxLength(36)
                .ValueGeneratedOnAdd();
});
NamPT
  • 49
  • 5
  • .ValueGeneratedOnAdd() could bring problems when updating-database. Should try this proposed scenario first to make sure it doesn't. – Rick Dec 19 '21 at 04:25
1
  1. Create an extension method for easy use:
namespace Microsoft.EntityFrameworkCore
{
    using System;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Linq;
    using System.Reflection;
    public static class ModelBuilderExtensions
    {
        public static void SetDefaultValuesTableName(this ModelBuilder modelBuilder)
        {
            foreach (var entity in modelBuilder.Model.GetEntityTypes().Where(x => x.ClrType.GetCustomAttribute(typeof(TableAttribute)) != null))
            {
                var entityClass = entity.ClrType;

                foreach (var property in entityClass.GetProperties().Where(p => (p.PropertyType == typeof(DateTime) || p.PropertyType == typeof(Guid)) && p.CustomAttributes.Any(a => a.AttributeType == typeof(DatabaseGeneratedAttribute))))
                {
                    var defaultValueSql = "GetDate()";
                    if (property.PropertyType == typeof(Guid))
                    {
                        defaultValueSql = "newsequentialid()";
                    }
                    modelBuilder.Entity(entityClass).Property(property.Name).HasDefaultValueSql(defaultValueSql);
                }
            }
        }
    }
}
  1. Modify your OnModelCreating method
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.SetDefaultValuesTableName();
    }
  1. Profit

Source

Diomedes Domínguez
  • 1,093
  • 12
  • 22