我有一个主键“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?
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
放在 public Guid ID {get; set;}
之前?
OnModelCreating
在这里被覆盖。
除了将这些属性添加到您的 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 post。
您可以将数据库中 Id 的默认值设置为 newsequentialid() 或 newid()。然后 EF 的身份配置应该可以工作。
这适用于我(没有 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);
}
}
它以前发生在我身上。
创建表并稍后在 .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
将具有唯一约束。数据库服务器将拒绝重复的主键。
实体框架 – 使用 Guid 作为主键
在使用 Entity Framework 时,使用 Guid 作为表的主键比使用整数时需要更多的努力。在您阅读/展示了如何操作之后,设置过程很简单。
对于 Code First 和 Database First 方法,该过程略有不同。这篇文章讨论了这两种技术。
代码优先
采用代码优先方法时,使用 Guid 作为主键很简单。创建实体时,将 DatabaseGenerated 属性添加到您的主键属性中,如下所示;
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
实体框架将按照您的预期创建具有主键和唯一标识符数据类型的列。
codefirst-defaultvalue
另请注意,非常重要的是,该列的默认值已设置为 (newsequentialid())
。这会为每一行生成一个新的顺序(连续)Guid。如果您愿意,可以将其更改为 newid()
),这将为每个新行生成一个完全随机的 Guid。每次删除和重新创建数据库时都会清除此信息,因此在采用 Database First 方法时效果更好。
数据库优先
数据库优先方法与代码优先方法类似,但您必须手动编辑模型才能使其工作。
确保在执行任何操作之前编辑主键列并将 (newsequentialid()) 或 (newid()) 函数添加为默认值。
接下来,打开 EDMX 图,选择适当的属性并打开属性窗口。确保 StoreGeneratedPattern 设置为标识。
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
根据 this,如果特定迁移在创建表之后添加 DatabaseGeneratedOption.Identity,则不会被特定迁移检测到,这就是我遇到的情况。所以我删除了数据库和那个特定的迁移并添加了一个新的迁移,最后更新了数据库,然后一切都按预期工作。我正在使用 EF 6.1、SQL2014 和 VS2013。
如果您执行 Code-First 并且已经有一个数据库:
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; }
}
如果要在不使用 DataAnnotations 的情况下自动生成兼容迁移,则必须在 DbContext 类的 OnModelCreating 方法覆盖中添加以下内容:
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()"
}
}
}
Id = c.Guid(nullable: false, identity: true, defaultValueSql: "newsequentialid()"),
”