实体框架 6 GUID 作为主键:无法将值 NULL 插入到列“Id”,表“文件存储”;列不允许空值

本文关键字:Id 文件 不允许 空值 文件存储 存储 插入 NULL GUID 框架 实体 | 更新日期: 2023-09-27 17:58:27

我有一个主键为"Id"的实体,它是 Guid:

public class FileStore
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Path { get; set; }
}

和一些配置:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<FileStore>().Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    base.OnModelCreating(modelBuilder);
}

当我尝试插入记录时,出现以下错误:

不能将值 NULL 插入到列 'Id' 列、表 'FileStore' 中;列不允许空值。插入失败。''r'语句已终止。

我不想手动生成 Guid。我只想插入一条记录并获取由SQL Server生成的Id。如果我设置.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)Id列不是 SQL Server 中的标识列。

如何将实体框架配置为在 SQL Server 中自动生成 Guid?

实体框架 6 GUID 作为主键:无法将值 NULL 插入到列“Id”,表“文件存储”;列不允许空值

除了将这些属性添加到 Id 列之外:

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

在迁移中,您应该更改CreateTable以将defaultValueSQL属性添加到列中,即:

Id = c.Guid(nullable: false, identity: true, defaultValueSql: "newsequentialid()"),

这将防止您不得不手动接触数据库,正如您在注释中指出的那样,这是您希望使用 Code First 避免的事情。

试试这个:

public class FileStore
 {
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public Guid Id { get; set; }
   public string Name { get; set; }
   public string Path { get; set; }
 }

您可以查看此SO帖子。

您可以将数据库中 Id 的默认值设置为 newsequentialid(( 或 newid((。然后,EF 的标识配置应该可以工作。

这适用于我(没有 Azure(,开发服务器上的 SQL 2008 R2 或本地工作站上的 localdb''mssqllocaldb。注意:实体添加"创建者"、"创建者"、"修改者"、"修改者"和"版本"列。

public class Carrier : Entity
{
    public Guid Id { get; set; }
    public string Code { get; set; }
    public string Name { get; set; }
}

然后创建映射配置类

public class CarrierMap : EntityTypeConfiguration<Carrier>
{
    public CarrierMap()
    {
        HasKey(p => p.Id);
        Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        Property(p => p.Code)
            .HasMaxLength(4)
            .IsRequired()
            .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute { IsClustered = true, IsUnique = true }));
        Property(p => p.Name).HasMaxLength(255).IsRequired();
        Property(p => p.Created).HasPrecision(7).IsRequired();
        Property(p => p.Modified)
            .HasColumnAnnotation("IX_Modified", new IndexAnnotation(new IndexAttribute()))
            .HasPrecision(7)
            .IsRequired();
        Property(p => p.CreatedBy).HasMaxLength(50).IsRequired();
        Property(p => p.ModifiedBy).HasMaxLength(50).IsRequired();
        Property(p => p.Version).IsRowVersion();
    }
}

当您执行如下所示的添加迁移时,这会在初始 DbMigration 中创建一个 Up 方法。

        CreateTable(
            "scoFreightRate.Carrier",
            c => new
                {
                    Id = c.Guid(nullable: false, identity: true),
                    Code = c.String(nullable: false, maxLength: 4),
                    Name = c.String(nullable: false, maxLength: 255),
                    Created = c.DateTimeOffset(nullable: false, precision: 7),
                    CreatedBy = c.String(nullable: false, maxLength: 50),
                    Modified = c.DateTimeOffset(nullable: false, precision: 7,
                        annotations: new Dictionary<string, AnnotationValues>
                        {
                            { 
                                "IX_Modified",
                                new AnnotationValues(oldValue: null, newValue: "IndexAnnotation: { }")
                            },
                        }),
                    ModifiedBy = c.String(nullable: false, maxLength: 50),
                    Version = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
                })
            .PrimaryKey(t => t.Id)
            .Index(t => t.Code, unique: true, clustered: true);

注意:Id 列没有获得默认值,不用担心

现在执行 Update-Database,你最终应该在你的数据库中得到一个表定义,如下所示:

CREATE TABLE [scoFreightRate].[Carrier] (
    [Id]         UNIQUEIDENTIFIER   DEFAULT (newsequentialid()) NOT NULL,
    [Code]       NVARCHAR (4)       NOT NULL,
    [Name]       NVARCHAR (255)     NOT NULL,
    [Created]    DATETIMEOFFSET (7) NOT NULL,
    [CreatedBy]  NVARCHAR (50)      NOT NULL,
    [Modified]   DATETIMEOFFSET (7) NOT NULL,
    [ModifiedBy] NVARCHAR (50)      NOT NULL,
    [Version]    ROWVERSION         NOT NULL,
    CONSTRAINT [PK_scoFreightRate.Carrier] PRIMARY KEY NONCLUSTERED ([Id] ASC)
);

GO
CREATE UNIQUE CLUSTERED INDEX [IX_Code]
    ON [scoFreightRate].[Carrier]([Code] ASC);

注意:我们有一个覆盖的 SqlServerMigrationSqlGenerator,以确保它不会使主键成为聚集索引,因为我们鼓励我们的开发人员在表上设置更好的聚集索引

public class OurMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
    protected override void Generate(AddPrimaryKeyOperation addPrimaryKeyOperation)
    {
        if (addPrimaryKeyOperation == null) throw new ArgumentNullException("addPrimaryKeyOperation");
        if (!addPrimaryKeyOperation.Table.Contains("__MigrationHistory"))
            addPrimaryKeyOperation.IsClustered = false;
        base.Generate(addPrimaryKeyOperation);
    }
    protected override void Generate(CreateTableOperation createTableOperation)
    {
        if (createTableOperation == null) throw new ArgumentNullException("createTableOperation");
        if (!createTableOperation.Name.Contains("__MigrationHistory"))
            createTableOperation.PrimaryKey.IsClustered = false;
        base.Generate(createTableOperation);
    }
    protected override void Generate(MoveTableOperation moveTableOperation)
    {
        if (moveTableOperation == null) throw new ArgumentNullException("moveTableOperation");
        if (!moveTableOperation.CreateTableOperation.Name.Contains("__MigrationHistory")) moveTableOperation.CreateTableOperation.PrimaryKey.IsClustered = false;
        base.Generate(moveTableOperation);
    }
}

它以前发生在我身上。

创建表并在稍后添加.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)表后,代码迁移不知何故无法为 Guid 列分配默认值。

修复:

我们所需要做的就是转到数据库,选择 Id 列并手动将newsequentialid()添加到 Default Value or Binding 中。

无需更新dbo.__MigrationHistory表。

希望对您有所帮助。


添加New Guid()的解决方案通常不是首选,因为理论上您可能会意外获得重复项。


而且您不必担心直接在数据库中进行编辑。实体框架所做的只是自动化部分数据库工作。

正在翻译

.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)

[Id] [uniqueidentifier] NOT NULL DEFAULT newsequentialid(),

如果我们的EF以某种方式错过了一件事并且没有为我们添加默认值,请继续手动添加它。

实体框架 – 使用 Guid 作为主键

使用实体框架时,使用 Guid 作为表主键比使用整数时需要付出更多的努力。 在您阅读/了解如何操作之后,设置过程很简单。

代码优先和数据库优先方法的过程略有不同。 这篇文章讨论了这两种技术。

在此处输入图像描述

代码优先

采用代码优先方法时,使用 Guid 作为主键很简单。 创建实体时,将 DatabaseGenerated 属性添加到主键属性,如下所示;

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

实体框架将按预期创建具有主键和唯一标识符数据类型的列。

codefirst-defaultvalue

另请注意,非常重要的列上的默认值已设置为 (newsequentialid()) 。 这将为每一行生成一个新的顺序(连续(Guid。 如果您愿意,可以将其更改为 newid() (,这将导致每个新行的 Guid 完全随机。 每次删除并重新创建数据库时,都会清除此值,因此在采用"数据库优先"方法时效果更好。

数据库优先

数据库优先方法

遵循与代码优先方法类似的思路,但必须手动编辑模型才能使其正常工作。

在执行任何操作之前,请确保编辑主键列并添加 (newsequentialid((( 或 (newid((( 函数作为默认值。

在此处输入图像描述

接下来,打开 EDMX 关系图,选择适当的属性并打开属性窗口。 确保将"存储生成的模式"设置为"标识"。

databasefirst-model

无需在代码中为您的实体提供 ID,该 ID 将在实体提交到数据库后自动填充;

using (ApplicationDbContext context = new ApplicationDbContext())
{
    var person = new Person
                     {
                         FirstName = "Random",
                         LastName = "Person";
                     };
    context.People.Add(person);
    context.SaveChanges();
    Console.WriteLine(person.Id);
}

重要说明:您的 Guid 字段必须是主键,否则这不起作用。 实体框架会给你一个相当神秘的错误消息!

总结

Guid(全局唯一标识符(可以轻松地用作实体框架中的主键。 执行此操作需要一些额外的努力,具体取决于您采用的方法。 使用代码优先方法时,将 DatabaseGenerated 属性添加到键字段。 采用数据库优先方法时,请在模型上将"存储生成的模式"显式设置为"标识"。

[1]: https://i.stack.imgur.com/IxGdd.png
[2]: https://i.stack.imgur.com/Qssea.png

据此,如果在创建表添加 DatabaseGenerated Option.Identity,则特定迁移不会检测到它,这就是我遇到的情况。所以我删除了数据库和特定的迁移,并添加了一个新的迁移,最后更新了数据库,然后一切都按预期工作。我正在使用EF 6.1,SQL2014和VS2013。

如果您执行代码优先并且已经有一个数据库:

public override void Up()
{
    AlterColumn("dbo.MyTable","Id", c =>  c.Guid(nullable: false, identity: true, defaultValueSql: "newsequentialid()"));
}

你不能。你会/确实破坏了很多东西。比如人际关系。我们依赖于被拉回的数字,EF 无法以您设置的方式执行此操作。打破每一种模式的代价。

在 C# 层中生成 GUID,以便关系可以继续工作。

还有这样的事情?

public class Carrier : Entity
{
    public Carrier()
    {
         this.Id = Guid.NewGuid();
    }  
    public Guid Id { get; set; }
    public string Code { get; set; }
    public string Name { get; set; }
}

如果要在不使用数据注释的情况下自动生成兼容的迁移,则必须在 DbContext 类的 OnModelCreate 方法重写中添加以下内容:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    //This is implicit when property is called Id or <ClassName>Id
    modelBuilder.Entity<FileStore>(x => x
      .HasKey(f => f.Id)
      .IsClustered());
    modelBuilder.Entity<FileStore>(x => x
        .Property(f => f.Id)
        .IsRequired()                     //Set column as not nullable
        .ValueGeneratedOnAdd()            //Optional (but recommended)
        .HasDefaultValueSql("newid()"));  //Or: "newsequentialid()"
}

如果要使用抽象类或接口在多个类之间共享 [(Guid( Id] 属性...

public interface IEntity
{
  public Guid Id { get; set; }
}
public class FileStore : IEntity
{
  public Guid Id { get; set; }
  public string Name { get; set; }
  public string Path { get; set; }
}
public class FolderStore : IEntity
{
  public Guid Id { get; set; }
  public string Name { get; set; }
}

您可以用这种通用方式定义相同的指令:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  base.OnModelCreating(modelBuilder);
  foreach (var t in modelBuilder.Model.GetEntityTypes())
  {
    if (typeof(IEntity).IsAssignableFrom(t.ClrType))
    {
      //This is implicit when property is called Id or <ClassName>Id
      modelBuilder.Entity(t.ClrType, x => x
        .HasKey(nameof(IEntity.Id))
        .IsClustered());
      modelBuilder.Entity(t.ClrType, x => x
        .Property(nameof(IEntity.Id))
        .IsRequired()
        .ValueGeneratedOnAdd()
        .HasDefaultValueSql("newid()"));  //Or: "newsequentialid()"
    }
  }
}