- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我将 Entity Framework 6 DB First 与 SQL Server 表一起使用,每个表都有一个 uniqueidentifier
主键。这些表在主键列上有一个默认值,将其设置为 newid()
。我相应地更新了我的 .edmx,将这些列的 StoreGeneratedPattern
设置为 Identity
。所以我可以创建新记录,将它们添加到我的数据库上下文中,然后自动生成 ID。但现在我需要保存一个具有特定 ID 的新记录。我读过 this article这表示在使用 int 标识 PK 列时,您必须在保存之前执行 SET IDENTITY_INSERT dbo.[TableName] ON
。因为我的是 Guid 而不是一个标识列,所以这基本上已经完成了。然而,即使在我的 C# 中我将 ID 设置为正确的 Guid,该值甚至没有作为参数传递给生成的 SQL 插入,并且 SQL Server 为主键生成了一个新 ID。
我需要能够做到:
我有 # 1. 如何插入具有特定主键的新记录?
编辑:
保存代码摘录(注意accountMemberSpec.ID是具体的Guid值我想做AccountMember的主键):
IDbContextScopeFactory dbContextFactory = new DbContextScopeFactory();
using (var dbContextScope = dbContextFactory.Create())
{
//Save the Account
dbAccountMember = CRMEntity<AccountMember>.GetOrCreate(accountMemberSpec.ID);
dbAccountMember.fk_AccountID = accountMemberSpec.AccountID;
dbAccountMember.fk_PersonID = accountMemberSpec.PersonID;
dbContextScope.SaveChanges();
}
--
public class CRMEntity<T> where T : CrmEntityBase, IGuid
{
public static T GetOrCreate(Guid id)
{
T entity;
CRMEntityAccess<T> entities = new CRMEntityAccess<T>();
//Get or create the address
entity = (id == Guid.Empty) ? null : entities.GetSingle(id, null);
if (entity == null)
{
entity = Activator.CreateInstance<T>();
entity.ID = id;
entity = new CRMEntityAccess<T>().AddNew(entity);
}
return entity;
}
}
--
public class CRMEntityAccess<T> where T : class, ICrmEntity, IGuid
{
public virtual T AddNew(T newEntity)
{
return DBContext.Set<T>().Add(newEntity);
}
}
这是为此记录的生成的 SQL:
DECLARE @generated_keys table([pk_AccountMemberID] uniqueidentifier)
INSERT[dbo].[AccountMembers]
([fk_PersonID], [fk_AccountID], [fk_FacilityID])
OUTPUT inserted.[pk_AccountMemberID] INTO @generated_keys
VALUES(@0, @1, @2)
SELECT t.[pk_AccountMemberID], t.[CreatedDate], t.[LastModifiedDate]
FROM @generated_keys AS g JOIN [dbo].[AccountMembers] AS t ON g.[pk_AccountMemberID] = t.[pk_AccountMemberID]
WHERE @@ROWCOUNT > 0
-- @0: '731e680c-1fd6-42d7-9fb3-ff5d36ab80d0' (Type = Guid)
-- @1: 'f6626a39-5de0-48e2-a82a-3cc31c59d4b9' (Type = Guid)
-- @2: '127527c0-42a6-40ee-aebd-88355f7ffa05' (Type = Guid)
最佳答案
一个解决方案可能是覆盖 DbContext SaveChanges。在此函数中,查找要指定其 Id 的 DbSets 的所有添加条目。
如果还没有指定Id,指定一个,如果已经指定:使用指定的。
覆盖所有 SaveChanges:
public override void SaveChanges()
{
GenerateIds();
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync()
{
GenerateIds();
return await base.SaveChangesAsync();
}
public override async Task<int> SaveChangesAsync(System.Threading CancellationToken token)
{
GenerateIds();
return await base.SaveChangesAsync(token);
}
GenerateIds 应该检查您是否已经为添加的条目提供了 Id。如果没有,请提供一个。
我不确定是否所有 DbSet 都应该具有请求的功能,或者只有一些。要检查主键是否已填充,我需要知道主键的标识符。
我在你的类(class) CRMEntity
中看到你知道每个 T
都有一个 Id,这是因为这个 Id 在 CRMEntityBase
中,或者在IGuid
中,我们假设它在IGuid
中。如果它在 CRMEntityBase
中,请相应地更改以下内容。
以下是小步骤;如果需要,您可以创建一个大的 LINQ。
private void GenerateIds()
{
// fetch all added entries that have IGuid
IEnumerable<IGuid> addedIGuidEntries = this.ChangeTracker.Entries()
.Where(entry => entry.State == EntityState.Added)
.OfType<IGuid>()
// if IGuid.Id is default: generate a new Id, otherwise leave it
foreach (IGuid entry in addedIGuidEntries)
{
if (entry.Id == default(Guid)
// no value provided yet: provide it now
entry.Id = GenerateGuidId() // TODO: implement function
// else: Id already provided; use this Id.
}
}
就是这样。因为您的所有 IGuid 对象现在都有一个非默认 ID(预定义或在 GenerateId 中生成)EF 将使用该 ID。
添加:HasDatabaseGeneratedOption
正如 xr280xr 在其中一条评论中指出的那样,我忘记了您必须告诉 Entity Framework Entity Framework 不应(始终)生成 Id。
例如,我对一个包含博客和帖子的简单数据库执行相同的操作。博客和帖子之间的一对多关系。为了表明思路不依赖GUID,主键是long。
// If an entity class is derived from ISelfGeneratedId,
// entity framework should not generate Ids
interface ISelfGeneratedId
{
public long Id {get; set;}
}
class Blog : ISelfGeneratedId
{
public long Id {get; set;} // Primary key
// a Blog has zero or more Posts:
public virtual ICollection><Post> Posts {get; set;}
public string Author {get; set;}
...
}
class Post : ISelfGeneratedId
{
public long Id {get; set;} // Primary Key
// every Post belongs to one Blog:
public long BlogId {get; set;}
public virtual Blog Blog {get; set;}
public string Title {get; set;}
...
}
现在是有趣的部分:通知 Entity Framework 主键值已经生成的流畅 API。
我更喜欢 Fluent API 避免使用属性,因为 Fluent API 的使用允许我在不同的数据库模型中重用实体类,只需重写 Dbcontext.OnModelCreating。
例如,在某些数据库中,我喜欢我的 DateTime 对象是 DateTime2,而在某些数据库中,我需要它们是简单的 DateTime。有时我想要自己生成的 ID,有时(比如在单元测试中)我不需要。
class MyDbContext : Dbcontext
{
public DbSet<Blog> Blogs {get; set;}
public DbSet<Post> Posts {get; set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Entity framework should not generate Id for Blogs:
modelBuilder.Entity<Blog>()
.Property(blog => blog.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
// Entity framework should not generate Id for Posts:
modelBuilder.Entity<Blog>()
.Property(blog => blog.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
... // other fluent API
}
SaveChanges 与我上面写的类似。 GenerateIds 略有不同。在这个例子中,我没有遇到有时 Id 已经填满的问题。每个添加的实现 ISelfGeneratedId 的元素都应该生成一个 Id
private void GenerateIds()
{
// fetch all added entries that implement ISelfGeneratedId
var addedIdEntries = this.ChangeTracker.Entries()
.Where(entry => entry.State == EntityState.Added)
.OfType<ISelfGeneratedId>()
foreach (ISelfGeneratedId entry in addedIdEntries)
{
entry.Id = this.GenerateId() ;// TODO: implement function
// now you see why I need the interface:
// I need to know the primary key
}
}
对于那些正在寻找简洁的 Id 生成器的人:我经常使用与 Twitter 使用的相同的生成器,一个可以处理多个服务器的生成器,没有每个人都可以从主键猜测添加了多少项的问题。
它在 Nuget IdGen package 中
关于c# - 如何使 Entity Framework 6 (DB First) 显式插入 Guid/UniqueIdentifier 主键?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46857417/
我是一名优秀的程序员,十分优秀!