xr2*_*0xr 9 c# sql sql-server entity-framework guid
我正在使用Entity Framework 6 DB First和SQL Server表,每个表都有一个uniqueidentifier主键.这些表在主键列上有一个默认值,用于设置它newid().我相应地更新了我的.edmx来设置StoreGeneratedPattern这些列Identity.所以我可以创建新记录,将它们添加到我的数据库上下文中,并自动生成ID.但现在我需要保存具有特定ID的新记录.我读过这篇文章,说明SET IDENTITY_INSERT dbo.[TableName] ON在使用int identity PK列时保存之前必须执行.由于我的是Guid而不是实际上是一个标识列,所以基本上已经完成了.然而,即使在我的C#中我将ID设置为正确的Guid,该值甚至不作为参数传递给生成的SQL插入,并且SQL Server为主键生成新的ID.
我需要能够兼得:
我有#1.如何使用特定主键插入新记录?
编辑:
保存代码摘录(注意accountMemberSpec.ID是我想成为AccountMember主键的特定Guid值):
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();
}
Run Code Online (Sandbox Code Playgroud)
-
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;
}
}
Run Code Online (Sandbox Code Playgroud)
-
public class CRMEntityAccess<T> where T : class, ICrmEntity, IGuid
{
public virtual T AddNew(T newEntity)
{
return DBContext.Set<T>().Add(newEntity);
}
}
Run Code Online (Sandbox Code Playgroud)
这是为此记录的生成的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)
Run Code Online (Sandbox Code Playgroud)
解决方案可能是覆盖 DbContext SaveChanges。在此函数中,查找要为其指定 Id 的 DbSet 的所有添加条目。
如果尚未指定 Id,则指定 1,如果已指定:使用指定的 1。
覆盖所有 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);
}
Run Code Online (Sandbox Code Playgroud)
GenerateIds 应检查您是否已为添加的条目提供了 ID。如果没有,请提供一份。
我不确定是否所有 DbSet 都应具有请求的功能,还是仅应具有部分功能。要检查主键是否已填充,我需要知道主键的标识符。
我在你的班级中看到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.
}
}
Run Code Online (Sandbox Code Playgroud)
就这些。因为所有 IGuid 对象现在都有一个非默认 ID(预定义的或在GenerateId 中生成的),EF 将使用该 Id。
添加:HasDatabaseGenelatedOption
正如 xr280xr 在评论之一中指出的那样,我忘记了您必须告诉实体框架实体框架不应(始终)生成 Id。
作为一个例子,我对一个包含博客和帖子的简单数据库做了同样的事情。博客和帖子之间的一对多关系。为了表明这个想法不依赖于 GUID,主键是很长的。
// 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;}
...
}
Run Code Online (Sandbox Code Playgroud)
现在有趣的部分是:流畅的 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
}
Run Code Online (Sandbox Code Playgroud)
SaveChanges 与我上面写的类似。GenerateIds 略有不同。在这个例子中,我没有遇到有时Id已经被填满的问题。每个实现 ISelfGenerateId 的添加元素都应该生成一个 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
}
}
Run Code Online (Sandbox Code Playgroud)
对于那些正在寻找简洁的 Id 生成器的人:我经常使用与 Twitter 使用的相同的生成器,它可以处理多个服务器,而不存在每个人都可以从主键猜测添加了多少项的问题。
| 归档时间: |
|
| 查看次数: |
1351 次 |
| 最近记录: |