AddOrUpdate违反了唯一索引

Hax*_*ale 8 entity-framework

我在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我的迁移说:

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();
Run Code Online (Sandbox Code Playgroud)

然后,当我跑步时Update-Database,我得到:

System.Data.SqlClient.SqlException:无法在具有唯一索引"IX_Name"的对象"dbo.Teams"中插入重复键行.重复键值为(Admin).

这有点令人困惑 - 我以为我告诉AddOrUpdate要根据他们的名字识别要更新的行,但这不会发生.我无法添加NameTeam主键,因为它有一个自引用外键(我可以添加ParentTeamName为属性,但我觉得它不应该是必要的).我误解了这种行为AddOrUpdate吗?我是否指定了错误的条件?

Fer*_*ira 0

我也有同样的原因。就我而言,它工作得很好,直到我需要使用唯一索引时,它崩溃了。

我的解决方案是创建一个CustomAddOrUpdate方法,我尝试首先根据Where predicate. 如果找到它,我只需更新属性,如果没有,它就会添加到上下文中。

但是,在更新实例之前,我必须将键值从原始实例复制到新实例,以避免 EF 异常告诉您无法更改键属性。

以下是代码片段:

1)首先是context类中的代码

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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()
Run Code Online (Sandbox Code Playgroud)

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码是从这个博客中获取的: https: //romiller.com/2014/10/07/ef6-1-getting-key-properties-for-an-entity/