保存时出现EF错误:从属角色具有多个具有不同值的主体

时间:2019-04-22 17:00:55

标签: c# entity-framework

我试图在同一类型中同时保存我的实体SubEntity的多个记录,但是出现错误“保存时出现EF错误:从属角色具有多个具有不同值的主体”。

SubEntity看起来像这样:

public class SubEntityMap : EntityTypeConfiguration<SubEntity>
{
    public SubEntityMap()
    {
        //Primary key
        HasKey(t => t.Id);

        //Table & Column Mappings
        ToTable("SubEntity", "dbo");
        Property(t => t.Id).HasColumnName("Id").IsRequired();
        Property(t => t.UpdateUser).HasColumnName("UpdateUser");
        Property(t => t.CreatedDate).HasColumnName("CreatedDate").IsRequired();
        Property(t => t.UpdateDate).HasColumnName("UpdDate").IsRequired();
        Property(t => t.Del).HasColumnName("Del").IsRequired();
        Property(t => t.MainEntityId).HasColumnName("MainEntityId").IsRequired();
        Property(t => t.ValueRefId).HasColumnName("ValueRefId").IsRequired();

        //Relationships
        HasRequired(t => t.MainEntity)
            .WithMany(t => t.SubEntities)
            .HasForeignKey(d => d.MainEntityId);

        HasRequired(t => t.Ref)
             .WithMany()
             .HasForeignKey(d => d.ValueRefId);
    }
}

相关实体的映射为:

public class MainEntityMap : EntityTypeConfiguration<MainEntity>
{
    public MainEntityMap()
    {
        // Prim`enter code here`ary Key
        this.HasKey(t => t.Id);

        // Table & Column Mappings
        this.ToTable("MainEntity");
        this.Property(t => t.Id).HasColumnName("Id");


    }
}

public class RefMap : EntityTypeConfiguration<Ref>
{
    public RefMap()
    {
        // Primary Key
        this.HasKey(t => t.Id);

        // Table & Column Mappings
        this.ToTable("Ref", "dbo");
        this.Property(t => t.Id).HasColumnName("Id")
            .IsRequired();
        this.Property(t => t.Code).HasColumnName("Code")
            .IsRequired();
        this.Property(t => t.Description).HasColumnName("Description")
            .IsRequired();
    }
}

还有一个类似于MainEntity的SourceEntity类型,该类型还定义了与几乎是SubEntity精确副本的表的1:N关系。 (它们定义了业务流程的2个不同步骤,因此我们必须复制与每个步骤相对应的主要实体以及它们的集合)。

下面的代码将subEntity元素从SourceEntity(业务流程的步骤1)复制到MainEntity(业务流程的步骤2)

public void CopyToSubEntity(SourceEntity sourceEntity, MainEntity mainEntity, string userName)
{
    var sourceEntitySubEntities = GetSubEntities(sourceEntity.id);
    var mainEntitySubs = new List<SubEntity>();

    foreach (var sourceEntitySubEntity in sourceEntitySubEntities )
    {
        var subEntity = new subEntity
        {
            CreatedDate = DateTime.Now,
            mainEntity = mainEntity,
            ValueRef = sourceEntitySubEntity.ValueRef
        };
        _subEntityRepository.Add(subEntity);
    }
}

如果我对每个更改进行保存更改,则可以使其生效,但我想了解发生了什么。除了消息“从属角色有多个具有不同值的主体”以外,什么也不提供任何信息。 有什么建议吗?

编辑:

我一直在调试EF代码,问题的根源在EF的方法中

private Dictionary<CompositeKey, PropagatorResult> ProcessKeys(
    UpdateCompiler compiler, List<PropagatorResult> changes, Set<CompositeKey> keys)
{
    var map = new Dictionary<CompositeKey, PropagatorResult>(
        compiler.m_translator.KeyComparer);

    foreach (var change in changes)
    {
        // Reassign change to row since we cannot modify iteration variable
        var row = change;

        var key = new CompositeKey(GetKeyConstants(row));

        // Make sure we aren't inserting another row with the same key
        PropagatorResult other;
        if (map.TryGetValue(key, out other))
        {
            DiagnoseKeyCollision(compiler, change, key, other);
        }

        map.Add(key, row);
        keys.Add(key);
    }

    return map;
}

当我第一次遍历代码时,map为空,并且密钥已添加到map。

第二次,map.TryGetValue返回true。

地图的内容为{Preserve:Quote:{Id = Key:id21:ord0:0,Field1 = ForeignKey:id8:ord1:459,Field2 = ForeignKey:id10:ord2:0等}}

密钥的内容为{Key:id61:ord0:0}

EF定义了自己的相等比较器:

private class CompositeKeyComparer : IEqualityComparer<CompositeKey>
{
    private readonly KeyManager _manager;

    internal CompositeKeyComparer(KeyManager manager)
    {
        DebugCheck.NotNull(manager);

        _manager = manager;
    }

    // determines equality by comparing each key component
    public bool Equals(CompositeKey left, CompositeKey right)
    {
        // Short circuit the comparison if we know the other reference is equivalent
        if (ReferenceEquals(left, right))
        {
            return true;
        }

        // If either side is null, return false order (both can't be null because of
        // the previous check)
        if (null == left
            || null == right)
        {
            return false;
        }

        Debug.Assert(
            null != left.KeyComponents && null != right.KeyComponents,
            "(Update/JoinPropagator) CompositeKey must be initialized");

        if (left.KeyComponents.Length
            != right.KeyComponents.Length)
        {
            return false;
        }

        for (var i = 0; i < left.KeyComponents.Length; i++)
        {
            var leftValue = left.KeyComponents[i];
            var rightValue = right.KeyComponents[i];

            // if both side are identifiers, check if they're the same or one is constrained by the
            // other (if there is a dependent-principal relationship, they get fixed up to the same
            // value)
            if (leftValue.Identifier
                != PropagatorResult.NullIdentifier)
            {
                if (rightValue.Identifier == PropagatorResult.NullIdentifier
                    ||
                    _manager.GetCliqueIdentifier(leftValue.Identifier) != _manager.GetCliqueIdentifier(rightValue.Identifier))
                {
                    return false;
                }
            }
            else
            {
                if (rightValue.Identifier != PropagatorResult.NullIdentifier
                    ||
                    !ByValueEqualityComparer.Default.Equals(leftValue.GetSimpleValue(), rightValue.GetSimpleValue()))
                {
                    return false;
                }
            }
        }

        return true;
    }

EF表示他们是同一条记录的事实有何道理?

1 个答案:

答案 0 :(得分:0)

根据添加的用法代码,我认为根本原因是跨上下文的实体引用周围,或者设置与实体相对的引用ID。

查看以下代码:

public void MoveMainEntityCodesToSubEntity(MainEntity mainEntity, SubEntity subEntity)
{
    var mainEntityClasses = _mainEntityClassRetriever.GetBymainEntityId(mainEntity.id);
    subEntity.subEntityClasses = new List<SubEntityClass>();

    foreach (var mainEntityClass in mainEntityClasses)
    {
        var subEntityClass = new subEntityClass
        {
            CreatedDate = DateTime.Now,
            ValueRefId = mainEntityClass.ValueRefId
        };

        subEntity.subEntityClasses.Add(subEntityClass);
    }       
}

...有些细节让人有些困惑。您似乎正在将Entities传递给此方法,一个父级,并且我假设一个子级是已经与该父级关联的子级。使用父ID,您将转到上下文以按ID加载父实体。问题1:为什么这个电话希望返回多个父母?您选择的检索器模式是否正在返回IQueryable<MainEntity>?还是对所有检索器都使用某种形式的通用模式,以便返回IEnumerable<MainEntity>

subEntity.subEntityClasses = new List<SubEntityClass>();

此行在此值得关注。如果subEntity是一个跟踪的实体,则永远不要通过设置新的List来清除其集合引用。要进行完全删除和替换,您应该在集合上使用.Clear()。但是,由于您正在考虑更新数据关系,因此也不建议这样做,而是加载子集合,然后通过将其与传入的数据进行比较来确定添加还是删除了哪些项目。

实体定义与您的代码似乎并没有真正匹配,但是看起来您想将子实体从一个主实体移动到另一个主实体。 (因为子实体没有子实体的集合)

第一个问题是这两个传入的上下文是什么,以及每个的子集合。通过将一个跟踪实体的子集合设置为列表的新实例,您可以删除数据库中的那些子引用。 EF可以/仍将跟踪孩子与老父母。我相信这就是为什么您会遇到错误的原因。

假设源和目标是通过相同的上下文加载的:

public void MoveMainEntityCodes(MainEntity source, MainEntity target)
{
    // if lazy loading is enabled...
    var subEntities = new List<SubEntity>(source.SubEntityClasses);
    // or if lazy loading is disabled and you don't know if the sub entities were loaded.
    //if(source.SubEntityClasses == null || !source.SubEntityClasses.Any())
    //    _context.Entity(source).Collection(x => x.SubEntityClasses).Load();
    //var subEntities = new List<SubEntity>(source.SubEntityClasses);

    foreach(var subEntity in subEntities)
    {
       source.SubEntityClasses.Remove(subEntity);
       target.SubEntityClasses.Add(subEntity);
       subEntity.CreatedDate = DateTime.Now; // if you want to refresh the created date.
    }
}

这当前不能解决的是目标实体是否已经具有子实体,以及那些子实体应该如何避免重复。通常,我更喜欢采用一种方法,即检查源和目标是否存在差异(要添加的项目与要删除的项目),然后根据该差异进行添加/删除。