Ошибка EF при сохранении: у зависимой роли есть несколько принципалов с разными значениями - PullRequest
1 голос
/ 22 апреля 2019

Я пытаюсь сохранить несколько записей моей сущности сущности одного и того же времени в одном и том же типе, но получаю ошибку «Ошибка 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();
    }
}

Существует также тип SourceEntity, который похож на MainEntity и который также определяет отношение 1: N к таблице, которая является почти точной копией SubEntity. (они определяют 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);
    }
}

Если я сделаю сохранение изменений для каждого из них, я смогу заставить его работать, но я хочу понять, что происходит. Исключение не дает никакой информации, кроме сообщения «Зависимая роль» имеет несколько принципалов с разными значениями. Любое предложение?

EDIT:

Я отлаживал код 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.TryGetValue возвращает true.

Содержимое карты: {Сохранить: Цитата: {Id = Ключ: 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 голосов
/ 23 апреля 2019

Основываясь на добавленном коде использования, я полагаю, что основной причиной будет что-то вокруг ссылок на сущности в разных контекстах или установки идентификаторов ссылок в отличие от сущностей.

Глядя на этот код:

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);
    }       
}

... есть некоторые детали, которые немного сбивают с толку. Вы, кажется, передаете сущности этому методу, родителю, и я предполагаю, что это дочерний элемент, который уже связан с этим родителем. С помощью Parent ID вы идете в контекст, чтобы загрузить родительские объекты по ID. Q1: Почему этот звонок ожидает возвращения нескольких родителей? Это то, что выбранный вами шаблон ретривера возвращает IQueryable<MainEntity>? Или вы используете какую-то форму общего шаблона для всех ретриверов, поэтому вы возвращаете IEnumerable<MainEntity>?

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

эта строка вызывает беспокойство. Если subEntity является отслеживаемым объектом, вы никогда не должны очищать ссылки на его коллекции, устанавливая новый список. Для полного удаления и замены вы должны использовать .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.
    }
}

То, что в настоящее время не учитывается, может ли целевой объект уже иметь дочерние объекты и что должно происходить с ними, чтобы избежать дублирования. Как правило, я предпочитаю использовать подход, который проверяет исходные и целевые значения на наличие различий (элементы, которые нужно добавить, и элементов, которые нужно удалить), а затем добавлять / удалять на основе этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...