private async Task UpdateEntityAssociationAsync(
            EntityAssociation association,
            EntityInfo entityInfo,
            AbstractEntityPersister persister,
            EntityIdMap entitiesIdMap,
            DependencyGraph dependencyGraph,
            ConfiguredSessionProvider sessionProvider, CancellationToken cancellationToken = default(CancellationToken))
        {
            cancellationToken.ThrowIfCancellationRequested();
            var dbEntity        = entityInfo.Entity;
            var originalFkValue = await(GetClientForeignKeyValueAsync(association, entityInfo, entitiesIdMap, true, sessionProvider, cancellationToken)).ConfigureAwait(false);

            TryAddToGraphAndUpdateParent(entityInfo, entitiesIdMap, dependencyGraph, originalFkValue, association, true, true, sessionProvider, out _);

            var newFkValue = await(GetClientForeignKeyValueAsync(association, entityInfo, entitiesIdMap, false, sessionProvider, cancellationToken)).ConfigureAwait(false);

            TryAddToGraphAndUpdateParent(entityInfo, entitiesIdMap, dependencyGraph, newFkValue, association, false, true, sessionProvider, out var parentEntityInfo);

            // Set the entity association (e.g. Order = value)
            var associatedEntity = newFkValue != null
                ? parentEntityInfo?.Entity ?? await(LoadEntityAsync(association.EntityType, newFkValue, sessionProvider, cancellationToken)).ConfigureAwait(false)
                : null;

            persister.SetPropertyValue(
                dbEntity,
                persister.GetPropertyIndex(association.AssociationPropertyName),
                associatedEntity);
        }
 private static async Task FlushSessionsAsync(ConfiguredSessionProvider sessionProvider, CancellationToken cancellationToken = default(CancellationToken))
 {
     cancellationToken.ThrowIfCancellationRequested();
     foreach (var session in sessionProvider.GetSessions())
     {
         await(session.FlushAsync(cancellationToken)).ConfigureAwait(false);
     }
 }
        private Task <object> GetClientForeignKeyValueAsync(
            EntityAssociation association,
            EntityInfo entityInfo,
            EntityIdMap entitiesIdMap,
            bool original,
            ConfiguredSessionProvider sessionProvider, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled <object>(cancellationToken));
            }
            try
            {
                var metadata = entityInfo.EntityMetadata;
                if (association.IsOneToOne)
                {
                    // TODO: verify this block
                    if (original)
                    {
                        return(entityInfo.OriginalValuesMap != null && entityInfo.OriginalValuesMap.TryGetValue(metadata.EntityPersister.IdentifierPropertyName, out var originalValue)
                            ? Task.FromResult <object>(ConvertToType(originalValue, association.IdentifierType.ReturnedClass)
                                                       )                        : Task.FromResult <object>(null));
                    }

                    return(Task.FromResult <object>(metadata.EntityPersister.GetIdentifier(entityInfo.ClientEntity)));
                }

                var fkProperties = association.ForeignKeyPropertyNames;
                if (association.IdentifierType is ComponentType idComponentType)
                {
                    return(GetClientCompositeKeyValueAsync(association, idComponentType, entityInfo, metadata, entitiesIdMap, original, sessionProvider, cancellationToken));
                }

                var fkPropertyName = fkProperties[0];
                if (original)
                {
                    return(entityInfo.OriginalValuesMap != null && entityInfo.OriginalValuesMap.TryGetValue(fkPropertyName, out var originalValue)
                        ? Task.FromResult <object>(ConvertToType(originalValue, association.IdentifierType.ReturnedClass)
                                                   )                    : Task.FromResult <object>(null));
                }

                if (metadata.SyntheticForeignKeyProperties.ContainsKey(fkPropertyName))
                {
                    // The unmapped property may be ignored from metadata
                    return(entityInfo.UnmappedValuesMap != null && entityInfo.UnmappedValuesMap.TryGetValue(fkPropertyName, out var value)
                        ? Task.FromResult <object>(ConvertToType(value, association.IdentifierType.ReturnedClass)
                                                   )                    : Task.FromResult <object>(null));
                }

                var persister = metadata.EntityPersister;
                return(Task.FromResult <object>(persister.GetPropertyValue(entityInfo.ClientEntity, persister.GetPropertyIndex(fkPropertyName))));
            }
            catch (Exception ex)
            {
                return(Task.FromException <object>(ex));
            }
        }
        private static async Task ProcessSavesAsync(
            List <EntityInfo> saveOrder,
            PersistenceManager persistenceManager,
            SaveChangesContext context,
            ISaveChangesOptions saveChangesOptions,
            ConfiguredSessionProvider sessionProvider, CancellationToken cancellationToken = default(CancellationToken))
        {
            cancellationToken.ThrowIfCancellationRequested();
            foreach (var entityInfo in saveOrder)
            {
                await(persistenceManager.BeforeSaveEntityChangesAsync(entityInfo, context, cancellationToken)).ConfigureAwait(false);
                var beforeSaveEntityChangesTask = saveChangesOptions?.BeforeSaveEntityChangesAsync(entityInfo, context, cancellationToken);
                if (beforeSaveEntityChangesTask != null)
                {
                    await(beforeSaveEntityChangesTask).ConfigureAwait(false);
                }

                var session = sessionProvider.GetSession(entityInfo.EntityType);
                try
                {
                    switch (entityInfo.EntityState)
                    {
                    case EntityState.Modified:
                        await(session.UpdateAsync(entityInfo.Entity, cancellationToken)).ConfigureAwait(false);
                        break;

                    case EntityState.Added:
                        await(session.SaveAsync(entityInfo.Entity, cancellationToken)).ConfigureAwait(false);
                        break;

                    case EntityState.Deleted:
                        await(session.DeleteAsync(entityInfo.Entity, cancellationToken)).ConfigureAwait(false);
                        break;
                    }
                }
                catch (PropertyValueException e)
                {
                    // NH can throw this when a not null property is null or transient (e.g. not-null property references a null or transient value)
                    var errors = new[]
                    {
                        // KeyValues cannot be determined as the exception may reference another entity
                        new EntityError
                        {
                            EntityTypeName = e.EntityName,
                            ErrorMessage   = e.Message,
                            ErrorName      = "PropertyValueException",
                            PropertyName   = e.PropertyName
                        }
                    };

                    throw new EntityErrorsException(e.Message, errors);
                }
            }
        }
 private async Task SetupManyToOneIdentifierPropertiesAsync(
     List <EntityInfo> entitiesInfo,
     Dictionary <Type, List <EntityInfo> > saveMap,
     bool forClient,
     ConfiguredSessionProvider sessionProvider, CancellationToken cancellationToken = default(CancellationToken))
 {
     cancellationToken.ThrowIfCancellationRequested();
     foreach (var entityInfo in entitiesInfo)
     {
         await(SetManyToOneIdentifierAsync(entityInfo, entityInfo.ClientEntity, saveMap, forClient, sessionProvider, cancellationToken)).ConfigureAwait(false);
     }
 }
 private static Task <object> LoadEntityAsync(Type entityType, object id, ConfiguredSessionProvider sessionProvider, CancellationToken cancellationToken = default(CancellationToken))
 {
     if (cancellationToken.IsCancellationRequested)
     {
         return(Task.FromCanceled <object>(cancellationToken));
     }
     try
     {
         var session = sessionProvider.GetSession(entityType);
         return(session.LoadAsync(entityType, id, cancellationToken));
     }
     catch (Exception ex)
     {
         return(Task.FromException <object>(ex));
     }
 }
        private async Task ApplyChangesAsync(
            Type entityType,
            List <EntityInfo> entityInfos,
            DependencyGraph dependencyGraph,
            Dictionary <Type, List <EntityInfo> > saveMap,
            EntityIdMap entitiesIdMap,
            ConfiguredSessionProvider sessionProvider, CancellationToken cancellationToken = default(CancellationToken))
        {
            cancellationToken.ThrowIfCancellationRequested();
            var modelConfiguration = _breezeConfigurator.GetModelConfiguration(entityType);

            foreach (var entityInfo in entityInfos)
            {
                dependencyGraph.AddToGraph(entityInfo);
                if (entityInfo.ServerSide)
                {
                    AddToGraphAdditionalEntityAssociations(entityInfo, saveMap, dependencyGraph);
                    continue;
                }

                switch (entityInfo.EntityState)
                {
                case EntityState.Added:
                    await(ApplyChangesForAddedAsync(
                              entityInfo,
                              modelConfiguration,
                              entitiesIdMap,
                              dependencyGraph,
                              sessionProvider, cancellationToken)).ConfigureAwait(false);
                    break;

                case EntityState.Modified:
                case EntityState.Deleted:
                    await(ApplyChangesForModifiedAsync(
                              entityInfo,
                              modelConfiguration,
                              entitiesIdMap,
                              dependencyGraph,
                              sessionProvider, cancellationToken)).ConfigureAwait(false);
                    break;

                default:
                    continue;
                }
            }
        }
        private async Task <object> GetClientCompositeKeyValueAsync(
            EntityAssociation association,
            ComponentType idComponentType,
            EntityInfo entityInfo,
            EntityMetadata metadata,
            EntityIdMap entitiesIdMap,
            bool original,
            ConfiguredSessionProvider sessionProvider, CancellationToken cancellationToken = default(CancellationToken))
        {
            cancellationToken.ThrowIfCancellationRequested();
            var persister       = metadata.EntityPersister;
            var types           = idComponentType.Subtypes;
            var relatedMetadata = _entityMetadataProvider.GetMetadata(association.EntityType);
            var values          = new object[types.Length];
            var fkIndex         = 0;
            var counter         = 0;

            for (var i = 0; i < types.Length; i++)
            {
                var type           = types[i];
                var columnSpan     = type.GetColumnSpan(persister.Factory);
                var propertyValues = new object[columnSpan];
                for (var j = 0; j < columnSpan; j++)
                {
                    var fkProperty = association.ForeignKeyPropertyNames[fkIndex];
                    if (original && entityInfo.OriginalValuesMap != null && entityInfo.OriginalValuesMap.TryGetValue(fkProperty, out var originalValue))
                    {
                        propertyValues[j] = ConvertToType(originalValue, relatedMetadata.IdentifierPropertyTypes[fkIndex]);
                        counter++;
                    }
                    else if (metadata.SyntheticForeignKeyProperties.ContainsKey(fkProperty))
                    {
                        if (entityInfo.UnmappedValuesMap == null || !entityInfo.UnmappedValuesMap.TryGetValue(fkProperty, out var fkValue))
                        {
                            throw new InvalidOperationException($"Unable to retrieve the synthetic property '{fkProperty}' value for type {entityInfo.EntityType} from {nameof(EntityInfo.UnmappedValuesMap)}.");
                        }

                        propertyValues[j] = ConvertToType(fkValue, relatedMetadata.IdentifierPropertyTypes[fkIndex]);
                    }
                    else
                    {
                        propertyValues[j] = persister.GetPropertyValue(entityInfo.ClientEntity, persister.GetPropertyIndex(fkProperty));
                    }

                    fkIndex++;
                }

                if (type.IsAssociationType && type is EntityType entityType)
                {
                    var    relatedPersister = (IEntityPersister)entityType.GetAssociatedJoinable(persister.Factory);
                    object propertyKey;
                    if (relatedPersister.IdentifierType is ComponentType componentType)
                    {
                        // TODO: add support for a relation to another key-many-to-one
                        propertyKey = componentType.Instantiate();
                        componentType.SetPropertyValues(propertyKey, propertyValues);
                    }
                    else
                    {
                        propertyKey = propertyValues[0];
                    }

                    if (entitiesIdMap.TryGetValue(relatedPersister.MappedClass, out var idMap) && idMap.TryGetValue(propertyKey, out var relatedEntityInfo))
                    {
                        values[i] = relatedEntityInfo.Entity;
                    }
                    else
                    {
                        values[i] = await(LoadEntityAsync(relatedPersister.MappedClass, propertyKey, sessionProvider, cancellationToken)).ConfigureAwait(false);
                    }
                }
                else
                {
                    values[i] = propertyValues[0];
                }
            }

            if (original && counter == 0)
            {
                return(null); // The key was not changed
            }

            var key = idComponentType.Instantiate();

            idComponentType.SetPropertyValues(key, values);

            return(key);
        }
        private async Task RefreshFromSessionAsync(Dictionary <Type, List <EntityInfo> > saveMap, ConfiguredSessionProvider sessionProvider, CancellationToken cancellationToken = default(CancellationToken))
        {
            cancellationToken.ThrowIfCancellationRequested();
            foreach (var pair in saveMap)
            {
                var modelConfiguration = _breezeConfigurator.GetModelConfiguration(pair.Key);
                if (modelConfiguration.RefreshAfterSave != true && modelConfiguration.RefreshAfterUpdate != true)
                {
                    continue;
                }

                var session = sessionProvider.GetSession(pair.Key);
                foreach (var entityInfo in pair.Value)
                {
                    if (entityInfo.EntityState == EntityState.Added && modelConfiguration.RefreshAfterSave == true ||
                        entityInfo.EntityState == EntityState.Modified && modelConfiguration.RefreshAfterUpdate == true)
                    {
                        await(session.RefreshAsync(entityInfo.Entity, cancellationToken)).ConfigureAwait(false);
                    }
                }
            }
        }
Example #10
0
        private async Task ApplyChangesForAddedAsync(
            EntityInfo entityInfo,
            ModelConfiguration modelConfiguration,
            EntityIdMap entitiesIdMap,
            DependencyGraph dependencyGraph,
            ConfiguredSessionProvider sessionProvider, CancellationToken cancellationToken = default(CancellationToken))
        {
            cancellationToken.ThrowIfCancellationRequested();
            var dbEntity      = entityInfo.Entity;
            var metadata      = entityInfo.EntityMetadata;
            var persister     = metadata.EntityPersister;
            var clientEntity  = entityInfo.ClientEntity;
            var propertyNames = persister.PropertyNames;
            var propertyTypes = persister.PropertyTypes;

            // Identifier is set in SetupDatabaseEntities
            for (var i = 0; i < propertyTypes.Length; i++)
            {
                var propertyName = propertyNames[i];
                if (propertyTypes[i].IsCollectionType || persister.VersionProperty == i)
                {
                    continue;
                }

                if (!CanUpdateProperty(modelConfiguration, propertyName, clientEntity))
                {
                    continue;
                }

                if (metadata.Associations.TryGetValue(propertyName, out var association) && association is EntityAssociation entityAssociation)
                {
                    await(UpdateEntityAssociationAsync(
                              entityAssociation,
                              entityInfo,
                              persister,
                              entitiesIdMap,
                              dependencyGraph,
                              sessionProvider, cancellationToken)).ConfigureAwait(false);
                }
                else
                {
                    persister.SetPropertyValue(dbEntity, i, persister.GetPropertyValue(clientEntity, i));
                }
            }

            // Add key-many-to-one to graph and on the parent collection
            if (metadata.ManyToOneIdentifierProperties.Count > 0)
            {
                foreach (var pair in metadata.ManyToOneIdentifierProperties)
                {
                    var pkPropertyName    = pair.Key;
                    var entityAssociation = (EntityAssociation)metadata.Associations[pkPropertyName];
                    var fkValue           = await(GetClientForeignKeyValueAsync(entityAssociation, entityInfo, entitiesIdMap, false, sessionProvider, cancellationToken)).ConfigureAwait(false);
                    TryAddToGraphAndUpdateParent(entityInfo, entitiesIdMap, dependencyGraph, fkValue, entityAssociation, false, true, sessionProvider, out _);
                }
            }

            foreach (var additionalProperty in GetAdditionalProperties(metadata).Values)
            {
                additionalProperty.SetValue(dbEntity, additionalProperty.GetValue(clientEntity));
            }
        }
Example #11
0
        internal async Task <List <KeyMapping> > FetchAndApplyChangesInternalAsync(
            PersistenceManager persistenceManager,
            SaveChangesContext context,
            ISaveChangesOptions saveChangesOptions, CancellationToken cancellationToken = default(CancellationToken))
        {
            cancellationToken.ThrowIfCancellationRequested();
            await(persistenceManager.BeforeFetchEntitiesAsync(context, cancellationToken)).ConfigureAwait(false);
            var beforeFetchEntitiesTask = saveChangesOptions?.BeforeFetchEntitiesAsync(context, cancellationToken);

            if (beforeFetchEntitiesTask != null)
            {
                await(beforeFetchEntitiesTask).ConfigureAwait(false);
            }

            var saveMap = context.SaveMap;

            using var sessionProvider = new ConfiguredSessionProvider(_sessionProvider);
            var entitiesIdMap = new EntityIdMap(saveMap.Count);
            var saveMapList   = saveMap.ToList();

            // Make sure that entity types that have all key-many-to-one are sorted so that the associated entities will be processed before them
            saveMapList.Sort(CompareSaveMapTypes);
            foreach (var pair in saveMapList)
            {
                await(SetupDatabaseEntitiesAsync(pair.Key, pair.Value, saveMap, entitiesIdMap, sessionProvider, cancellationToken)).ConfigureAwait(false);
            }

            await(persistenceManager.BeforeApplyChangesAsync(context, cancellationToken)).ConfigureAwait(false);
            var beforeApplyChangesTask = saveChangesOptions?.BeforeApplyChangesAsync(context, cancellationToken);

            if (beforeApplyChangesTask != null)
            {
                await(beforeApplyChangesTask).ConfigureAwait(false);
            }

            AddAdditionalEntities(context.AdditionalEntities, context.SaveMap);
            context.AdditionalEntities?.Clear();
            var dependencyGraph = new DependencyGraph(saveMap.Count);

            foreach (var pair in saveMap)
            {
                await(ApplyChangesAsync(pair.Key, pair.Value, dependencyGraph, saveMap, entitiesIdMap, sessionProvider, cancellationToken)).ConfigureAwait(false);
            }

            persistenceManager.ValidateDependencyGraph(dependencyGraph, context);
            saveChangesOptions?.ValidateDependencyGraph(dependencyGraph, context);

            var saveOrder = dependencyGraph.GetSaveOrder();

            await(persistenceManager.BeforeSaveChangesAsync(saveOrder, context, cancellationToken)).ConfigureAwait(false);
            var beforeSaveChangesTask = saveChangesOptions?.BeforeSaveChangesAsync(saveOrder, context, cancellationToken);

            if (beforeSaveChangesTask != null)
            {
                await(beforeSaveChangesTask).ConfigureAwait(false);
            }

            await(ProcessSavesAsync(saveOrder, persistenceManager, context, saveChangesOptions, sessionProvider, cancellationToken)).ConfigureAwait(false);

            await(persistenceManager.AfterSaveChangesAsync(saveOrder, context, cancellationToken)).ConfigureAwait(false);
            var afterSaveChangesTask = saveChangesOptions?.AfterSaveChangesAsync(saveOrder, context, cancellationToken);

            if (afterSaveChangesTask != null)
            {
                await(afterSaveChangesTask).ConfigureAwait(false);
            }

            await(FlushSessionsAsync(sessionProvider, cancellationToken)).ConfigureAwait(false);
            await(RefreshFromSessionAsync(saveMap, sessionProvider, cancellationToken)).ConfigureAwait(false);
            var keyMappings = GetKeyMappings(saveMap).ToList();

            await(persistenceManager.AfterFlushChangesAsync(context, keyMappings, cancellationToken)).ConfigureAwait(false);
            var afterFlushChangesTask = saveChangesOptions?.AfterFlushChangesAsync(context, keyMappings, cancellationToken);

            if (afterFlushChangesTask != null)
            {
                await(afterFlushChangesTask).ConfigureAwait(false);
            }

            UpdateClientEntities(context);

            return(keyMappings);
        }
Example #12
0
        private async Task ApplyChangesForModifiedAsync(
            EntityInfo entityInfo,
            ModelConfiguration modelConfiguration,
            EntityIdMap entitiesIdMap,
            DependencyGraph dependencyGraph,
            ConfiguredSessionProvider sessionProvider, CancellationToken cancellationToken = default(CancellationToken))
        {
            cancellationToken.ThrowIfCancellationRequested();
            var dbEntity      = entityInfo.Entity;
            var clientEntity  = entityInfo.ClientEntity;
            var metadata      = entityInfo.EntityMetadata;
            var persister     = metadata.EntityPersister;
            var propertyTypes = persister.PropertyTypes;
            HashSet <string>      skipProperties      = null;
            HashSet <Association> updatedAssociations = null;

            // Update modified values
            var additionalProperties = GetAdditionalProperties(metadata);

            foreach (var pair in entityInfo.OriginalValuesMap)
            {
                var propertyName = pair.Key;
                if (metadata.VersionPropertyName == propertyName)
                {
                    // JSON.NET converts integers to long
                    var oldVersion     = ConvertToType(pair.Value, metadata.VersionPropertyType);
                    var currentVersion = persister.GetPropertyValue(dbEntity, persister.VersionProperty);
                    if (!Equals(oldVersion, currentVersion))
                    {
                        var errors = new[]
                        {
                            new EntityError
                            {
                                EntityTypeName = entityInfo.EntityType.FullName,
                                ErrorMessage   = "Cannot update an old version",
                                ErrorName      = "ConcurrencyException",
                                KeyValues      = entityInfo.GetIdentifierValues(),
                                PropertyName   = propertyName
                            }
                        };

                        throw new EntityErrorsException("Cannot update an old version", errors);
                    }

                    continue;
                }

                if (skipProperties?.Contains(propertyName) == true ||
                    !CanUpdateProperty(metadata, modelConfiguration, propertyName, clientEntity))
                {
                    continue;
                }

                // Find FK property relation (e.g. OrderId -> Order)
                if (metadata.ForeignKeyAssociations.TryGetValue(propertyName, out var association))
                {
                    updatedAssociations ??= new HashSet <Association>();
                    updatedAssociations.Add(association);
                    await(UpdateEntityAssociationAsync(
                              association,
                              entityInfo,
                              persister,
                              entitiesIdMap,
                              dependencyGraph,
                              sessionProvider, cancellationToken)).ConfigureAwait(false);

                    if (association.ForeignKeyPropertyNames.Count > 1)
                    {
                        skipProperties ??= new HashSet <string>();
                        foreach (var fkProperty in association.ForeignKeyPropertyNames)
                        {
                            skipProperties.Add(fkProperty);
                        }
                    }
                }
                else
                {
                    var index = persister.EntityMetamodel.GetPropertyIndexOrNull(propertyName);
                    if (!index.HasValue)
                    {
                        if (additionalProperties.TryGetValue(propertyName, out var additionalProperty))
                        {
                            additionalProperty.SetValue(dbEntity, additionalProperty.GetValue(clientEntity));
                        }

                        continue; // Not mapped property
                    }

                    var propertyType  = propertyTypes[index.Value];
                    var propertyValue = persister.GetPropertyValue(clientEntity, index.Value);
                    if (propertyType is IAbstractComponentType componentType)
                    {
                        var dbValue = persister.GetPropertyValue(dbEntity, index.Value);
                        persister.SetPropertyValue(
                            dbEntity,
                            index.Value,
                            UpdateComponentProperties(componentType, propertyValue, dbValue, (JObject)pair.Value));
                    }
                    else
                    {
                        persister.SetPropertyValue(dbEntity, index.Value, propertyValue);
                    }
                }
            }

            var remove = entityInfo.EntityState == EntityState.Deleted;

            // Add dependencies to graph
            foreach (var association in metadata.Associations.Values)
            {
                if (updatedAssociations?.Contains(association) == true ||
                    !(association is EntityAssociation entityAssociation) ||
                    !entityAssociation.IsChild)
                {
                    continue;
                }

                var newFkValue = await(GetClientForeignKeyValueAsync(entityAssociation, entityInfo, entitiesIdMap, false, sessionProvider, cancellationToken)).ConfigureAwait(false);
                // Update association only when removing, otherwise only add it to the graph
                TryAddToGraphAndUpdateParent(entityInfo, entitiesIdMap, dependencyGraph, newFkValue, entityAssociation, remove, remove, sessionProvider, out _);
            }
        }
Example #13
0
        private async Task SetupDatabaseEntitiesAsync(Type entityType,
                                                      List <EntityInfo> entitiesInfo,
                                                      Dictionary <Type, List <EntityInfo> > saveMap,
                                                      EntityIdMap entitiesIdMap,
                                                      ConfiguredSessionProvider sessionProvider, CancellationToken cancellationToken = default(CancellationToken))
        {
            cancellationToken.ThrowIfCancellationRequested();
            var modelConfiguration = _breezeConfigurator.GetModelConfiguration(entityType);
            var metadata           = _entityMetadataProvider.GetMetadata(entityType);

            if (metadata.ManyToOneIdentifierProperties.Count > 0)
            {
                await(SetupManyToOneIdentifierPropertiesAsync(entitiesInfo, saveMap, true, sessionProvider, cancellationToken)).ConfigureAwait(false);
            }

            var session     = sessionProvider.GetSession(entityType);
            var batchSize   = modelConfiguration.BatchFetchSize ?? session.GetSessionImplementation().Factory.Settings.DefaultBatchFetchSize;
            var persister   = metadata.EntityPersister;
            var existingIds = entitiesInfo.Where(o => o.EntityState != EntityState.Added)
                              .Select(o => persister.GetIdentifier(o.ClientEntity))
                              .ToList();
            var batchFetcher = metadata.BatchFetcher;
            var dbEntities   = existingIds.Count > 0 ? await(batchFetcher.BatchFetchAsync(session, existingIds, batchSize, cancellationToken)).ConfigureAwait(false) : null;

            var idMap = entitiesIdMap.GetTypeIdMap(entityType, entitiesInfo.Count);

            foreach (var entityInfo in entitiesInfo)
            {
                foreach (var identifierPropertyName in metadata.IdentifierPropertyNames)
                {
                    if (entityInfo.OriginalValuesMap.ContainsKey(identifierPropertyName))
                    {
                        var errors = new[]
                        {
                            new EntityError
                            {
                                EntityTypeName = entityInfo.EntityType.FullName,
                                ErrorMessage   = "Cannot update part of the entity's key",
                                ErrorName      = "KeyUpdateException",
                                KeyValues      = entityInfo.GetIdentifierValues(),
                                PropertyName   = identifierPropertyName
                            }
                        };

                        throw new EntityErrorsException("Cannot update part of the entity's key", errors);
                    }
                }

                var    id = persister.GetIdentifier(entityInfo.ClientEntity);
                object dbEntity;
                if (entityInfo.EntityState == EntityState.Added)
                {
                    dbEntity = metadata.CreateInstance();
                    if (metadata.AutoGeneratedKeyType != AutoGeneratedKeyType.Identity &&
                        !(persister.IdentifierGenerator is ForeignGenerator))
                    {
                        if (metadata.ManyToOneIdentifierProperties.Count > 0)
                        {
                            await(SetManyToOneIdentifierAsync(entityInfo, dbEntity, saveMap, false, sessionProvider, cancellationToken)).ConfigureAwait(false);
                        }
                        else
                        {
                            persister.SetIdentifier(dbEntity, id);
                        }
                    }
                }
                else if (dbEntities == null || !dbEntities.TryGetValue(id, out dbEntity) || dbEntity == null)
                {
                    throw new InvalidOperationException($"Entity {entityType} with id {id} was not found.");
                }

                entityInfo.Entity = dbEntity;
                idMap.Add(id, entityInfo);
                entitiesIdMap.AddToDerivedTypes(entityInfo, id, entitiesInfo.Count);
            }
        }
Example #14
0
        private async Task SetManyToOneIdentifierAsync(
            EntityInfo entityInfo,
            object entity,
            Dictionary <Type, List <EntityInfo> > saveMap,
            bool forClient,
            ConfiguredSessionProvider sessionProvider, CancellationToken cancellationToken = default(CancellationToken))
        {
            cancellationToken.ThrowIfCancellationRequested();
            var metadata        = entityInfo.EntityMetadata;
            var idComponentType = (ComponentType)metadata.EntityPersister.IdentifierType;
            var idValues        = idComponentType.GetPropertyValues(entityInfo.ClientEntity);

            foreach (var pkPropertyName in metadata.ManyToOneIdentifierProperties.Keys)
            {
                var association = (EntityAssociation)metadata.Associations[pkPropertyName];
                // Create PK
                var fkPropertyNames = association.ForeignKeyPropertyNames;
                var fkValues        = new object[association.ForeignKeyPropertyNames.Count];
                for (var i = 0; i < fkPropertyNames.Count; i++)
                {
                    var fkPropertyName = fkPropertyNames[i];
                    if (metadata.SyntheticForeignKeyProperties.TryGetValue(fkPropertyName, out var syntheticFkProperty))
                    {
                        fkValues[i] = ConvertToType(entityInfo.UnmappedValuesMap[fkPropertyName], syntheticFkProperty.IdentifierType);
                    }
                    else
                    {
                        var index = metadata.EntityPersister.GetPropertyIndex(fkPropertyName);
                        fkValues[i] = metadata.EntityPersister.GetPropertyValue(entityInfo.ClientEntity, index);
                    }
                }

                object fkValue;
                if (association.IdentifierType is ComponentType componentType)
                {
                    // TODO: add support for a relation to another key-many-to-one
                    fkValue = componentType.Instantiate();
                    componentType.SetPropertyValues(fkValue, fkValues);
                }
                else
                {
                    fkValue = fkValues[0];
                }

                // Try find the related entity in the save map
                object fkEntity    = null;
                var    fkPersister = _entityMetadataProvider.GetMetadata(association.EntityType).EntityPersister;
                if (saveMap.TryGetValue(association.EntityType, out var fkEntitiesInfo))
                {
                    var fkEntityInfo = fkEntitiesInfo.Find(info => Equals(fkPersister.GetIdentifier(info.ClientEntity), fkValue));
                    if (fkEntityInfo != null)
                    {
                        fkEntity = forClient ? fkEntityInfo.ClientEntity : fkEntityInfo.Entity;
                    }
                }

                if (fkEntity == null)
                {
                    fkEntity = await(LoadEntityAsync(association.EntityType, fkValue, sessionProvider, cancellationToken)).ConfigureAwait(false);
                }

                var pkIndex = idComponentType.GetPropertyIndex(pkPropertyName);
                idValues[pkIndex] = fkEntity;
            }

            idComponentType.SetPropertyValues(entity, idValues);
        }