在处理在 Guid 作为主键的数据库中插入新实体时,我面临以下问题 - EF 5,代码优先方法。
我知道有很多类似的主题,因为我在这个问题上花了好几个小时,但我找不到这个问题的主题。
举个例子,我的POCO课程是:
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和Name只是文本数据,可以在管理面板中编辑,因此不要考虑这些字段名称。
添加新实体时,我不指定主键值。到目前为止一切都那么好,这里的一切都很好作为需要值并且具有自动生成值的主键字段,它应该不总是指定Id,但如果我设置了值,则应该保留它(如果启用了标识插入)。
但在某些情况下,我想指定一个主键值,例如我的数据库的初始种子值。我需要它以便以后处理 - 我只想说我需要那个特定的Guid。所以,如果我在我的Configuration类中:
// 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"},
);
}
即使我定期添加,Guid键设置也不起作用:
using (var context = new MyDBContext())
{
context.MenuItems.Add(
new Entity() {Id = new Guid("<some guid>"), Name = "fooname" /*some other values*/}
);
context.SaveChanges();
}
我在SQL Server跟踪中的内容是:
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'
很明显,新的Guid值不会从EF SQL生成器发送到SQL Server,因此问题出在EF中。
所以我删除了 DatabaseGeneratedOption.Identity 属性,然后就可以了,但是我丢失了自动生成的Id密钥,这对我来说并不适用,因为这种情况非常罕见。
我现在唯一的解决方案:
我最终会覆盖DBContext的SaveChanges()方法并修改所有要添加状态的实体(我从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();
}
与此相结合,应删除所有 DatabaseGeneratedOption 。所有模型都有名为“Id”的主键,遵循命名约定的最佳实践主题之一。
但这看起来并不是非常优雅的workaronud,因为我认为EF5应该能够处理这种情况。如果启用了标识插入,它适用于Int标识。
那么有人知道如何在问题上找到更好的解决方案吗?
答案 0 :(得分:3)
这是一个简单的方法,因为它是一个GUID,你可以在你的类中有一个构造函数,如
public EntityRole()
{
Id = Guid.NewGuid();
}
并删除数据库生成选项或将其更改为DatabaseGeneratedOption.None。
答案 1 :(得分:2)
您可以将Id标记为虚拟财产。因此,在您创建虚假数据的项目中,您可以使用覆盖Id的内部EntityRole,在这里您可以删除DatabaseGeneratedOption.Identity属性或在DatabaseGeneratedOption.None上更改它。
答案 2 :(得分:0)
Visual Studio 2017,C#,EntityFramework v6.0允许您通过在创建时通过控制器传递值来使用自定义值覆盖。不需要在模型bc中定义它,而已经在ApplicationUser中定义了它。
private string DateGuid()
{
string pre = "Ugly Users -";
return pre + Guid.NewGuid();
}
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { Id = DateGuid() };
var result = await UserManager.CreateAsync(user, model.Password);
...