实体框架6 GUID作为主键:无法将值NULL插入到列“Id”中,表“FileStore”;该列不允许为空。

84

我有一个主键为“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);
}
当我尝试插入一条记录时,出现以下错误:

无法将空值插入到列“Id”中,表“FileStore”;该列不允许为空。插入失败。\r\n已终止该语句。

我不想手动生成Guid。我只想插入一条记录,并从SQL Server中获取生成的 Id 。如果我设置.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity),则 Id 列在SQL Server中不是Identity列。
我如何配置Entity Framework在SQL Server中自动生成Guid?

你尝试在 public Guid ID {get; set;} 前面加上注释 [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 了吗? - user3383479
你在表格初始构建之后添加了配置吗? - user3411327
Inanikian,我认为流畅的API更受欢迎,因为在这里重写了OnModelCreating - Blaise
1
我看到你还没有接受任何答案。你对所有答案都不满意吗?如果是这样,请告诉我,我会发布另一个可行的答案。只是有点懒,如果我没有得到声望,就不想打字。 :) - Konrad Viltersten
11个回答

108

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

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

在迁移过程中,您应该更改CreateTable以向您的列添加defaultValueSQL属性,例如:

在迁移过程中,您应该更改CreateTable以向您的列添加defaultValueSQL属性,例如:

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

这将防止您不得不手动操作数据库,正如您在评论中指出的,这是您想通过 Code First 避免的事情。


12
那个Azure笔记确实为我节省了一些时间。谢谢。 - Stanley.Goldman
3
自 Azure SQL V12 版本开始支持使用 newsequentialid() 函数。详情请参考 https://msdn.microsoft.com/en-us/library/ms189786.aspx。 - DalSoft
1
这是什么,我们在哪里使用它?"Id = c.Guid(nullable: false, identity: true, defaultValueSql: "newsequentialid()")," - Hassan Faghihi
我知道已经有一段时间了,但是对于未来的读者来说,ID分配语句应该放在CodeFirst迁移文件中。 - Byrel Mitchell
当使用[key]时,如果未提供值,则会生成一个值,请参阅:EF生成值文档。至于使用[DatabaseGenerated(DatabaseGeneratedOption.Identity)],您可以查看此帖子 - Bojan
你也可以使用 newid 来获取一个随机的 Guid。当使用 newsequentialid 时,这意味着排序。在这种情况下,请注意 SqlServer 对 Guid 的排序方式与 C# 不同,请参见 https://dev59.com/nonda4cB1Zd3GeqPDcHG。 - Yahoo Serious

20

试一下这个:

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

你可以查看这个SO post

1
但我认为通过重写 OnModelCreating 方法,您可以设置与使用此属性相同的设置。我会阅读这篇文章。谢谢。 - Algirdas

9

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


2
谢谢您的回答,但我正在项目中使用Code First。因此,我不想在创建数据库时执行任何手动步骤。 - Algirdas
2
@Algirdas 为什么每个实体的构造函数中都要有一个Guid.NewGuid()呢? - Murdock
我没有找到其他解决方案,所以我在构造函数中添加了Guid.NewGuid()。谢谢。 - Algirdas
1
@Algirdas 只是提供信息,Guid.NewGuid() 不会在数据库中索引。你应该使用连续的 Guid。 - Josh Mouch

7
这对我很有效(不需要Azure),SQL 2008 R2在开发服务器上或localdb\mssqllocaldb在本地工作站上。注意:entity会添加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() 的解决方案,因为理论上你可能会意外获得重复的 Guid。


而且你不必担心直接在数据库中进行编辑。Entity Framework 只是自动化了我们的部分数据库工作。

翻译完成。

.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)

转换为

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

如果我们的EF遗漏了某个属性,并且没有为我们添加默认值,那么请手动添加。


2
如果是“主键”,那么该字段中不可能有重复的GUID键,因为“主键”将具有唯一约束。数据库服务器将拒绝重复的主键。 - AechoLiu

5

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

在使用实体框架时,将 Guid 用作表的主键需要比使用整数更多的努力。一旦你阅读/学会了如何做,设置过程就非常简单。

对于 Code First 和 Database First 方法,该过程略有不同。本文讨论了这两种技术。

输入图像描述

Code First

在采用 Code First 方法时,将 Guid 用作主键非常简单。创建实体时,在主键属性中添加 DatabaseGenerated 属性,如下所示:

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

Entity Framework会按照预期创建列,具有主键和唯一标识符数据类型。

codefirst-defaultvalue

请注意,非常重要的是,列的默认值已设置为(newsequentialid())。这将为每一行生成一个新的连续Guid。如果您愿意,可以将其更改为newid()),这将为每个新行生成完全随机的Guid。每次删除并重新创建数据库时,这将被清除,因此在采用“数据库优先”方法时,这种方法更有效。
“数据库优先”方法遵循类似于“代码优先”方法的线路,但您必须手动编辑模型才能使其正常工作。
在进行任何操作之前,请确保编辑主键列并添加(newsequentialid())或(newid())函数作为默认值。 输入图像描述 接下来,打开EDMX图表,选择适当的属性并打开属性窗口。确保StoreGeneratedPattern设置为identity。
databasefirst-model

无需在代码中为实体指定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将为您提供一个相当晦涩的错误消息!
摘要
在Entity Framework中可以轻松地使用Guid(全局唯一标识符)作为主键。具体取决于您采取的方法,可能需要额外的努力来实现这一点。在使用代码优先方法时,请向您的关键字段添加DatabaseGenerated属性。在采用数据库优先方法时,请在您的模型上明确设置StoredGeneratedPattern为Identity。
[1]: https://istack.dev59.com/IxGdd.webp
[2]: https://istack.dev59.com/Qssea.webp

4

根据这里的信息,在表创建后添加DatabaseGeneratedOption.Identity会导致特定迁移无法检测到,这也是我遇到的情况。因此,我删除了数据库和该特定迁移,并添加了一个新的迁移,最后更新了数据库,然后一切正常。我使用的是EF 6.1,SQL2014和VS2013。


仅供参考:在 ef7 RC1 中,[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 可以正常工作... 但是只测试过新数据库... - Miroslav Siska

2
如果您使用Code-First并且已经有了数据库:
public override void Up()
{
    AlterColumn("dbo.MyTable","Id", c =>  c.Guid(nullable: false, identity: true, defaultValueSql: "newsequentialid()"));
}

1
你不能这样做。你会破坏很多事情,比如关系。这些关系依赖于被拉回的数字,而EF无法按照你设置的方式进行操作。这是打破所有模式的代价。
在C#层生成GUID,以便关系可以继续工作。

0

这样的东西怎么样?

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

2
这会产生数据碎片,对于小型数据库可能不是问题,但大型数据库应该使用连续的GUID。 - Calimero100582
1
@Miroslav,我猜你是在谈论我的上面的回答,如果是的话,请不要添加构造函数,因为当实体被保存在数据库中时,数据库会创建一个顺序 guid。 - Allan
@Calimero100582 应该吗?我认为GUID冲突的机会是微乎其微的。 - Luke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接