如何使Entity Framework 6(DB First)显式插入Guid/UniqueIdentifier主键?

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. 插入新记录并自动为其创建ID,
  2. 插入具有指定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)

Har*_*lse 1

解决方案可能是覆盖 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 使用的相同的生成器,它可以处理多个服务器,而不存在每个人都可以从主键猜测添加了多少项的问题。

它位于Nuget IdGen 包中