EF,Code First - 如何在插入时设置自定义的Guid标识值 [英] EF, Code First - How to set a custom Guid identity value on insert

查看:309
本文介绍了EF,Code First - 如何在插入时设置自定义的Guid标识值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在处理在具有 Guid 作为主键的DB中插入新实体时,我遇到以下问题 - 具有代码优先方法的EF 5。



我知道有很多类似的主题,因为我在这个问题上花了几个小时,但是我不能找到这个问题的主题。



例如,我的POCO类是:

  public class EntityRole:IAdminModel 
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id {get;组;

[必需]
[MaxLength(50)]
public string Name {get;组;

[必需]
[Display(Name =Role code)]
[MaxLength(20)]
public string RoleCode {get;组; }

[Display(Name =Entities Assigned)]
[InverseProperty(Role)]
public List< Entity>实体{get;组; }
}

RoleCode和Name只是文本数据,可以在管理面板,所以不要考虑这些字段名称。



添加新实体时,我不指定主键值。到目前为止这么好,一切都很好。作为一个主键字段,需要值并具有自动生成值,所以不应该始终指定Id,但是如果我设置了一个值,则应该保留它(如果启用了identity insert)。



但在某些情况下,我想指定一个主键值,例如使用值初始化我的数据库。我需要它来进行后续处理 - 只需说我需要具体的Guid就可以了。所以如果我在我的配置类中:

  //初始数据种子
protected override void Seed(WebPortalDbContext context)
{
context.MenuItems.AddOrUpdate(
m => m.Id,
new EntityRole {Id = new Guid(268bf332-910e-4ea1-92f8-1ac0611f4c62) ,Name =Some name,RoleCode =SN},
);
}

即使我常规添加,Guid键设置也不起作用: / p>

  using(var context = new MyDBContext())
{
context.MenuItems.Add(
new Entity(){Id = new Guid(< some guid>),Name =fooname/ *一些其他值* /}
);

context.SaveChanges();
}

我在SQL Server跟踪中有:

  exec sp_executesql N'declare @generated_keys表([Id] uniqueidentifier)
insert [dbo]。[EntityRoles]([Name],[ RoleCode])
输入插入[Id]到@generated_keys
值(@ 0,@ 1)
select t。[Id]
from @generated_keys as g join [dbo ]。[EntityRoles] as t on g。[Id] = t。[Id]
其中@@ ROWCOUNT> 0',N'@ 0 nvarchar(50),@ 1 nvarchar(20)',@ 0 = N'Chief Captain',@ 1 = N'CO1'

这里很明显,新的Guid值不是从EF SQL生成器发送到SQL Server,所以问题在EF中。



所以我删除了 DatabaseGeneratedOption.Identity 属性,然后它是好的,但我失去了自动生成的Id键,这对我来说不起作用是非常罕见的情况。



我现在唯一的解决方案是:



我最终是覆盖DBContext的SaveChanges()方法,并修改所有要添加的状态的实体(我从 here ):

  ///< summary>将实体保存在changetracker< / summary>中时的自定义处理
public override int SaveChanges()
{
//建议为适当的实体显式设置New Guid - http://msdn.microsoft.com/en-us/library/dd283139。 aspx
foreach(ChangeTracker.Entries()中的var项)其中(e => e.State == EntityState.Added))
{
var t = entry.Entity.GetType );
if(t.GetProperty(Id)== null)
continue;

var info = t.GetProperty(Id)。GetCustomAttributes(typeof(DatabaseGeneratedAttribute),true).Cast< DatabaseGeneratedAttribute>();
if(!info.Any()|| info.Single()。DatabaseGeneratedOption!= DatabaseGeneratedOption.Identity)
{
if(t.GetProperty(Id)。PropertyType == type(Guid)&(Guid)t.GetProperty(Id)GetValue(entry.Entity,null)== Guid.Empty)
t.GetProperty(Id)。SetValue .Entity,Guid.NewGuid(),null);
}
}
return base.SaveChanges();
}

与此相结合,所有的 DatabaseGeneratedOption 应该是除去。所有型号都有名为Id的主键,遵循命名约定的最佳实践主题之一。



但是这并不是非常优雅的工作原理,因为我想要EF5应该能够处理这种情况。如果身份插入,它适用于Int身份。



所以有人有一个想法如何在问题上实现更好的解决方案?

解决方案

这是一个简单的方法,因为它是一个GUID,是在你的类中有一个构造函数,如



public EntityRole()
{
Id = Guid.NewGuid();

  
}

并删除数据库生成的选项或将其更改为DatabaseGeneratedOption.None。 p>

I`m facing the following problem when dealing with inserting new entities in the DB that has Guid as primary keys - EF 5 with Code first approach.

I know there are a lot similar topics as I was roving for hours for this issue, but I couldn`t find topic with this problem.

As an example, my POCO class is:

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

    [Required]
    [MaxLength(50)]
    public string Name { get; set; }

    [Required]
    [Display(Name = "Role code")]
    [MaxLength(20)]
    public string RoleCode { get; set; }

    [Display(Name = "Entities Assigned")]
    [InverseProperty("Role")]
    public List<Entity> Entities { get; set; }
}

RoleCode and Name are just text data, that is editable in the admin panel, so don`t consider those field names.

When adding new entity I don`t specify primary key values. So far so good, all works fine here. As a primary key field that requires value and has auto-generation of values, it is supposed not to always specify Id, but if I set a value, it should be kept (if identity insert is enabled).

But in some cases I want to specify a primary key value, for example for initial seeding of my DB with values. I need it for later processing - lets just say I need that specific Guid to be there. So if I have in my Configuration class:

// Initial data seed
protected override void Seed(WebPortalDbContext context)
{
    context.MenuItems.AddOrUpdate(
        m => m.Id,
        new EntityRole {Id = new Guid("268bf332-910e-4ea1-92f8-1ac0611f4c62"), Name = "Some name", RoleCode = "SN"},
    );
}

The Guid key setting goes not work even if I do a regular add:

using (var context = new MyDBContext())
{
    context.MenuItems.Add(
        new Entity() {Id = new Guid("<some guid>"), Name = "fooname" /*some other values*/}
    );

    context.SaveChanges();
}

What I have in the SQL Server trace is:

exec sp_executesql N'declare @generated_keys table([Id] uniqueidentifier)
insert [dbo].[EntityRoles]([Name], [RoleCode])
output inserted.[Id] into @generated_keys
values (@0, @1)
select t.[Id]
from @generated_keys as g join [dbo].[EntityRoles] as t on g.[Id] = t.[Id]
where @@ROWCOUNT > 0',N'@0 nvarchar(50),@1 nvarchar(20)',@0=N'Chief Captain',@1=N'CO1'

Here it is obvious that the new Guid value is just not sent from the EF SQL generator to the SQL Server, so the problem is in EF.

So I removed the DatabaseGeneratedOption.Identity attribute, then it is ok, but I lose the auto generation of the Id key, which does not work for me as it is very rare case.

The only solution from me for now:

I ended up is to overwrite the SaveChanges() method of the DBContext and modify all entities that have state to be added (I took the idea from here):

/// <summary> Custom processing when saving entities in changetracker </summary>
public override int SaveChanges()
{
    // recommended to explicitly set New Guid for appropriate entities -- http://msdn.microsoft.com/en-us/library/dd283139.aspx
    foreach (var entry in ChangeTracker.Entries().Where(e => e.State == EntityState.Added))
    {
        var t = entry.Entity.GetType();
        if (t.GetProperty("Id") == null)
            continue;

        var info = t.GetProperty("Id").GetCustomAttributes(typeof (DatabaseGeneratedAttribute), true).Cast<DatabaseGeneratedAttribute>();
        if (!info.Any() || info.Single().DatabaseGeneratedOption != DatabaseGeneratedOption.Identity)
        {
            if (t.GetProperty("Id").PropertyType == typeof(Guid) && (Guid)t.GetProperty("Id").GetValue(entry.Entity, null) == Guid.Empty)
                t.GetProperty("Id").SetValue(entry.Entity, Guid.NewGuid(), null);
        }
    }
    return base.SaveChanges();
}

In combination with this, all DatabaseGeneratedOption should be removed. All models has primary keys named "Id", following one of the best practice topics for naming conventions.

But this does not looks very elegant workaronud, because I thnk EF5 should be able to handle such cases. It works for Int identities if identity insert is on.

So does someone has an idea how to achieve better solution on the problem?

解决方案

The simple way for you to do this since it is a GUID is to have one constructor in your class like

public EntityRole()
{
   Id = Guid.NewGuid();
}

and remove The database generated option or change it to DatabaseGeneratedOption.None.

这篇关于EF,Code First - 如何在插入时设置自定义的Guid标识值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆