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