【问题标题】:Entity Framework 6 GUID as primary key: Cannot insert the value NULL into column 'Id', table 'FileStore'; column does not allow nullsEntity Framework 6 GUID 作为主键:无法将值 NULL 插入列“Id”、表“FileStore”;列不允许空值
【发布时间】:2014-05-29 15:39:57
【问题描述】:

我有一个主键为“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”中;列不允许空值。 INSERT 失败。\r\n语句已终止。

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

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

【问题讨论】:

  • 你有没有尝试在public Guid ID {get; set;}之前添加注释[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  • 初始建表后是否添加了配置?
  • Inanikian,我认为 fluent api 是首选,因为这里覆盖了 OnModelCreating
  • 我看到你没有接受任何答案。你不满意吗?如果是这样,请告诉我,我会发布另一个,这是有效的。只是觉得有点懒惰,如果我没有得到代表就不想打字。 :)

标签: c# entity-framework sql-server-2008 entity-framework-6


【解决方案1】:

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

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

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

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

这将使您不必手动接触数据库,正如您在 cmets 中指出的那样,这是您希望通过 Code First 避免的事情。

【讨论】:

  • 那个 Azure 笔记确实为我节省了一些时间。谢谢。
  • newsequentialid() 从 Azure SQL V12 msdn.microsoft.com/en-us/library/ms189786.aspx开始受支持。
  • 这是什么,我们在什么地方使用它? "Id = c.Guid(nullable: false, identity: true, defaultValueSql: "newsequentialid()"),"
  • 我知道这已经有一段时间了,但对于未来的读者来说,id 赋值语句放在 codefirst 迁移文件中。
  • 使用 [key] 时,如果未提供值,则会生成一个值,请参阅:EF generated values docs。至于使用 [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 你可以检查这个post
【解决方案2】:

试试这个:

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

你可以查看这个SO post

【讨论】:

  • 但我认为通过覆盖 OnModelCreating 您可以设置与使用此属性相同的设置。我会阅读帖子。谢谢
【解决方案3】:

您可以将您的 db 中的 Id 的默认值设置为 newsequentialid() 或 newid()。那么EF的身份配置应该可以工作了。

【讨论】:

  • 感谢您的回答,但我在项目中首先使用代码。所以我不想在创建数据库时做任何手动步骤。
  • @Algirdas 为什么在每个实体的构造函数中都有一个 Guid.NewGuid()
  • 我没有找到任何其他解决方案,所以我将 Guid.NewGuid() 添加到构造函数中。谢谢
  • @Algirdas 仅供参考,Guid.NewGuid() 不会在数据库中编制索引。相反,您应该使用顺序 Guid。
【解决方案4】:

这适用于我(没有 Azure)、开发服务器上的 SQL 2008 R2 或本地工作站上的 localdb\mssqllocaldb。注意:实体添加了 Create、CreateBy、Modified、ModifiedBy 和 Version 列。

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();
    }
}

当您像这样执行 add-migration 时,这会在初始 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);
    }
}

【讨论】:

    【解决方案5】:

    我以前也遇到过。

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

    修复:

    我们只需要进入数据库,选择 Id 列并将newsequentialid() 手动添加到Default Value or Binding 中。

    无需更新 dbo.__MigrationHistory 表。

    希望对你有帮助。


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


    而且您不必担心直接在数据库中进行编辑。所有 Entity Framework 所做的都是自动化我们的部分数据库工作。

    翻译

    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
    

    进入

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

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

    【讨论】:

    • 如果是Primary Key,则该字段不可能有重复的guid键。因为Primary Key 会有一个唯一的约束。数据库服务器将拒绝重复的主键。
    【解决方案6】:

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

    【讨论】:

    • 仅供参考:在 ef7 RC1 工作 [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 好...但也仅使用新数据库进行了测试...
    【解决方案7】:

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

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

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

    enter image description here

    代码优先

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

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

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

    codefirst-defaultvalue
    

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

    数据库优先

    数据库优先方法与代码优先方法类似,但您必须手动编辑模型才能使其正常工作。

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

    enter image description here

    接下来,打开 EDMX 图,选择适当的属性并打开属性窗口。确保 StoreGeneratedPattern 设置为 identity。

    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 字段必须是主键,否则这不起作用。 Entity Framework 会给你一个相当神秘的错误信息!

    总结

    Guid(全局唯一标识符)可以很容易地用作实体框架中的主键。需要付出一点额外的努力才能做到这一点,具体取决于您采用的方法。使用代码优先方法时,将 DatabaseGenerated 属性添加到您的关键字段。采用 Database First 方法时,在模型上将 StoredGeneratedPattern 显式设置为 Identity。

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

    【讨论】:

      【解决方案8】:

      如果您使用 Code-First 并且已经有一个数据库:

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

      【讨论】:

        【解决方案9】:

        你不能。你会/确实会破坏很多东西。喜欢人际关系。这依赖于撤回的数字,而 EF 无法以您设置的方式进行操作。打破所有模式的代价。

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

        【讨论】:

          【解决方案10】:

          还有这样的东西?

          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; }
          }
          

          【讨论】:

          • 这会产生碎片,对于小DB来说这不是问题,但是大DB应该使用顺序guid
          • @Miroslav,我假设你在上面谈论我的答案,如果是这样,不,不要添加构造函数,当实体保存在数据库中时,数据库会创建一个顺序 guid .
          • @Calimero100582 应该吗?我认为 GUID 冲突的可能性很小
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-12-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多