我在EF的帮助下在ASP.NET中编写MVC应用程序,我正在尝试为我的数据库播种。我有以下型号:
public class Team
{
[ScaffoldColumn(false)]
public int Id { get; set; }
[ForeignKey("ParentTeam")]
public int? ParentTeamId { get; set; }
[Required(ErrorMessage = "Cannot create a Team without a name")]
[Index(IsUnique = true)]
[MaxLength(30)]
public string Name { get; set; }
public IEnumerable<string> Members { get; set; }
public virtual Team ParentTeam { get; set; }
public Team() { }
public Team(string name)
{
Name = name;
}
}
我的迁移说:
var team = new Team("Admin");
var team2 = new Team("Test Team");
var team3 = new Team("Test Team 2");
context.Teams.AddOrUpdate(t => t.Name, team, team2, team3);
context.SaveChanges();
然后,当我运行Update-Database
时,我得到:
System.Data.SqlClient.SqlException:无法插入重复的键行 具有唯一索引'IX_Name'的对象'dbo.Teams'。重复的密钥 值是(管理员)。
这有点令人困惑 - 我以为我告诉AddOrUpdate
识别要按名字更新的行,但这不会发生。我无法将Name
添加到Team
的主键,因为它有一个自引用外键(我可以添加ParentTeamName
作为属性,但我觉得它不应该是必要的)。我误解了AddOrUpdate
的行为吗?我是否指定了错误的条件?
答案 0 :(得分:0)
我有完全相同的原因。就我而言,它工作正常,直到它坏了之前我需要使用唯一索引。
我的解决方案是创建一个CustomAddOrUpdate
方法,在该方法中,我尝试首先基于Where predicate
查找现有实例。如果找到它,则只更新属性,否则,将其添加到上下文中。
但是,在更新实例之前,我必须将键值从原始实例复制到新实例,以避免EF异常告诉您不能更改键属性。
以下是代码段:
1)首先在上下文类中编写代码
public void CustomAddOrUpdate<TEntity>(Expression<Func<TEntity, bool>> whereExpression, TEntity entity) where TEntity : class
{
var entitySet = this.EntitySet<TEntity>();
var foundInstance = entitySet.Where(whereExpression).FirstOrDefault();
if (foundInstance != null)
{
CopyKeyProperties<TEntity>(foundInstance, entity);
Entry(foundInstance).CurrentValues.SetValues(entity);
}
else
{
entitySet.Add(entity);
}
}
private void CopyKeyProperties<TEntity>(TEntity source, TEntity target) where TEntity : class
{
string[] keys = this.GetKeyNames<TEntity>();
foreach(var keyName in keys)
{
Entry(target).Property(keyName).CurrentValue = Entry(source).Property(keyName).CurrentValue;
}
}
2)然后在我的种子代码上:
var entityList = new List<MyExempleEntity>()
{
new MyExampleEntity { Prop1 = "a p1", Prop2 = "a p2" },
new MyExampleEntity { Prop1 = "b p1", Prop2 = "b p2" },
new MyExampleEntity { Prop1 = "c p1", Prop2 = "c p2" },
}
foreach(var item in entityList)
{
context.CustomAddOrUpdate<MyExampleEntity>(x => x.Prop1 == item.Prop1 && x.Prop2 == item.Prop2, item);
}
context.SaveChanges()
3)最后,这里是从实体获取KeyProperties的代码:
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Linq;
namespace System.Data.Entity
{
public static class DbContextExtensions
{
public static string[] GetKeyNames<TEntity>(this DbContext context)
where TEntity : class
{
return context.GetKeyNames(typeof(TEntity));
}
public static string[] GetKeyNames(this DbContext context, Type entityType)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MetadataWorkspace metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
// Get the mapping between CLR types and metadata OSpace
var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));
// Get metadata for given CLR type
var entityMetadata = metadata
.GetItems<EntityType>(DataSpace.OSpace)
.Single(e => objectItemCollection.GetClrType(e) == entityType);
return entityMetadata.KeyProperties.Select(p => p.Name).ToArray();
}
}
}
以上代码是从此博客中获取的: https://romiller.com/2014/10/07/ef6-1-getting-key-properties-for-an-entity/