private static bool?GetDefaultIsClustered([NotNull] IKey key, StoreObjectIdentifier storeObject) { var sharedTableRootKey = key.FindSharedObjectRootKey(storeObject); return(sharedTableRootKey?.IsClustered(storeObject)); }
/// <summary> /// Gets the foreign keys for the given entity type that point to other entity types /// sharing the same table-like store object. /// </summary> /// <param name="entityType"> The entity type. </param> /// <param name="storeObject"> The identifier of the store object. </param> public static IEnumerable <IConventionForeignKey> FindRowInternalForeignKeys( [NotNull] this IConventionEntityType entityType, StoreObjectIdentifier storeObject) => ((IEntityType)entityType).FindRowInternalForeignKeys(storeObject).Cast <IConventionForeignKey>();
public SaveContextQueryBase(DbContext context, string schema, string table, List <IProperty> propertiesToInsert, List <IProperty> propertiesToUpdate, List <List <IProperty> > propertiesForPivotSet, List <IProperty> propertiesToBulkLoad, List <IEntityType> entityTypes, CancellationToken cancellationToken, StoreObjectIdentifier storeObject) { this.StoreObject = storeObject; this.CancellationToken = cancellationToken; this.PropertiesToInsert = propertiesToInsert; this.PropertiesToUpdate = propertiesToUpdate; this.PropertiesForPivotSet = propertiesForPivotSet; this.PropertiesToBulkLoad = propertiesToBulkLoad; this.Schema = schema; this.Table = table; this.Context = context; this.EntityTypes = entityTypes; }
/// <summary> /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// </summary> protected override void ValidateSharedTableCompatibility( IReadOnlyList <IEntityType> mappedTypes, string tableName, string?schema, IDiagnosticsLogger <DbLoggerCategory.Model.Validation> logger) { var firstMappedType = mappedTypes[0]; var isMemoryOptimized = firstMappedType.IsMemoryOptimized(); foreach (var otherMappedType in mappedTypes.Skip(1)) { if (isMemoryOptimized != otherMappedType.IsMemoryOptimized()) { throw new InvalidOperationException( SqlServerStrings.IncompatibleTableMemoryOptimizedMismatch( tableName, firstMappedType.DisplayName(), otherMappedType.DisplayName(), isMemoryOptimized ? firstMappedType.DisplayName() : otherMappedType.DisplayName(), !isMemoryOptimized ? firstMappedType.DisplayName() : otherMappedType.DisplayName())); } } if (mappedTypes.Any(t => t.IsTemporal()) && mappedTypes.Select(t => t.GetRootType()).Distinct().Count() > 1) { // table splitting is only supported when all entites mapped to this table // have consistent temporal period mappings also var expectedPeriodStartColumnName = default(string); var expectedPeriodEndColumnName = default(string); foreach (var mappedType in mappedTypes.Where(t => t.BaseType == null)) { if (!mappedType.IsTemporal()) { throw new InvalidOperationException( SqlServerStrings.TemporalAllEntitiesMappedToSameTableMustBeTemporal( mappedType.DisplayName())); } var periodStartPropertyName = mappedType.GetPeriodStartPropertyName(); var periodEndPropertyName = mappedType.GetPeriodEndPropertyName(); var periodStartProperty = mappedType.GetProperty(periodStartPropertyName !); var periodEndProperty = mappedType.GetProperty(periodEndPropertyName !); var storeObjectIdentifier = StoreObjectIdentifier.Table(tableName, mappedType.GetSchema()); var periodStartColumnName = periodStartProperty.GetColumnName(storeObjectIdentifier); var periodEndColumnName = periodEndProperty.GetColumnName(storeObjectIdentifier); if (expectedPeriodStartColumnName == null) { expectedPeriodStartColumnName = periodStartColumnName; } else if (expectedPeriodStartColumnName != periodStartColumnName) { throw new InvalidOperationException( SqlServerStrings.TemporalNotSupportedForTableSplittingWithInconsistentPeriodMapping( "start", mappedType.DisplayName(), periodStartPropertyName, periodStartColumnName, expectedPeriodStartColumnName)); } if (expectedPeriodEndColumnName == null) { expectedPeriodEndColumnName = periodEndColumnName; } else if (expectedPeriodEndColumnName != periodEndColumnName) { throw new InvalidOperationException( SqlServerStrings.TemporalNotSupportedForTableSplittingWithInconsistentPeriodMapping( "end", mappedType.DisplayName(), periodEndPropertyName, periodEndColumnName, expectedPeriodEndColumnName)); } } } base.ValidateSharedTableCompatibility(mappedTypes, tableName, schema, logger); }
private void LoadData <T>(HookingDbContext context, Type type, IList <T> entities, bool loadOnlyPKColumn) { LoadOnlyPKColumn = loadOnlyPKColumn; var entityType = context.Model.FindEntityType(type); if (entityType == null) { type = entities[0].GetType(); entityType = context.Model.FindEntityType(type); HasAbstractList = true; } if (entityType == null) { throw new InvalidOperationException($"DbContext does not contain EntitySet for Type: { type.Name }"); } //var relationalData = entityType.Relational(); relationalData.Schema relationalData.TableName // DEPRECATED in Core3.0 var storeObjectIdent = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table).Value; bool isSqlServer = context.DataProvider.ProviderType == DataProviderType.SqlServer; string defaultSchema = isSqlServer ? "dbo" : null; Schema = entityType.GetSchema() ?? defaultSchema; TableName = entityType.GetTableName(); TempTableSufix = "Temp"; if (!BulkConfig.UseTempDB || BulkConfig.UniqueTableNameTempDb) { TempTableSufix += Guid.NewGuid().ToString().Substring(0, 8); // 8 chars of Guid as tableNameSufix to avoid same name collision with other tables } bool areSpecifiedUpdateByProperties = BulkConfig.UpdateByProperties?.Count > 0; var primaryKeys = entityType.FindPrimaryKey()?.Properties?.Select(a => a.Name)?.ToList(); HasSinglePrimaryKey = primaryKeys?.Count == 1; PrimaryKeys = areSpecifiedUpdateByProperties ? BulkConfig.UpdateByProperties : primaryKeys; var allProperties = entityType.GetProperties().AsEnumerable(); // load all derived type properties if (entityType.IsAbstract()) { var extendedAllProperties = allProperties.ToList(); foreach (var derived in entityType.GetDirectlyDerivedTypes()) { extendedAllProperties.AddRange(derived.GetProperties()); } allProperties = extendedAllProperties.Distinct(); } var ownedTypes = entityType.GetNavigations().Where(a => a.TargetEntityType.IsOwned()); HasOwnedTypes = ownedTypes.Any(); OwnedTypesDict = ownedTypes.ToDictionary(a => a.Name, a => a); IdentityColumnName = allProperties.SingleOrDefault(a => a.IsPrimaryKey() && a.ClrType.Name.StartsWith("Int") && a.ValueGenerated == ValueGenerated.OnAdd)?.GetColumnName(storeObjectIdent); // ValueGenerated equals OnAdd even for nonIdentity column like Guid so we only type int as second condition // timestamp/row version properties are only set by the Db, the property has a [Timestamp] Attribute or is configured in FluentAPI with .IsRowVersion() // They can be identified by the columne type "timestamp" or .IsConcurrencyToken in combination with .ValueGenerated == ValueGenerated.OnAddOrUpdate string timestampDbTypeName = nameof(TimestampAttribute).Replace("Attribute", "").ToLower(); // = "timestamp"; var timeStampProperties = allProperties.Where(a => (a.IsConcurrencyToken && a.ValueGenerated == ValueGenerated.OnAddOrUpdate) || a.GetColumnType() == timestampDbTypeName); TimeStampColumnName = timeStampProperties.FirstOrDefault()?.GetColumnName(storeObjectIdent); // can be only One var allPropertiesExceptTimeStamp = allProperties.Except(timeStampProperties); var properties = allPropertiesExceptTimeStamp.Where(a => a.GetComputedColumnSql() == null); // TimeStamp prop. is last column in OutputTable since it is added later with varbinary(8) type in which Output can be inserted OutputPropertyColumnNamesDict = allPropertiesExceptTimeStamp.Concat(timeStampProperties).ToDictionary(a => a.Name, b => b.GetColumnName(storeObjectIdent).Replace("]", "]]")); // square brackets have to be escaped ColumnNameContainsSquareBracket = allPropertiesExceptTimeStamp.Concat(timeStampProperties).Any(a => a.GetColumnName(storeObjectIdent).Contains("]")); bool AreSpecifiedPropertiesToInclude = BulkConfig.PropertiesToInclude?.Count() > 0; bool AreSpecifiedPropertiesToExclude = BulkConfig.PropertiesToExclude?.Count() > 0; if (AreSpecifiedPropertiesToInclude) { if (areSpecifiedUpdateByProperties) // Adds UpdateByProperties to PropertyToInclude if they are not already explicitly listed { foreach (var updateByProperty in BulkConfig.UpdateByProperties) { if (!BulkConfig.PropertiesToInclude.Contains(updateByProperty)) { BulkConfig.PropertiesToInclude.Add(updateByProperty); } } } else // Adds PrimaryKeys to PropertyToInclude if they are not already explicitly listed { foreach (var primaryKey in PrimaryKeys) { if (!BulkConfig.PropertiesToInclude.Contains(primaryKey)) { BulkConfig.PropertiesToInclude.Add(primaryKey); } } } } foreach (var property in allProperties) { if (property.PropertyInfo != null) // skip Shadow Property { FastPropertyDict.Add(property.Name, FastProperty.GetProperty(property.PropertyInfo)); } } UpdateByPropertiesAreNullable = properties.Any(a => PrimaryKeys != null && PrimaryKeys.Contains(a.Name) && a.IsNullable); if (AreSpecifiedPropertiesToInclude || AreSpecifiedPropertiesToExclude) { if (AreSpecifiedPropertiesToInclude && AreSpecifiedPropertiesToExclude) { throw new InvalidOperationException("Only one group of properties, either PropertiesToInclude or PropertiesToExclude can be specified, specifying both not allowed."); } if (AreSpecifiedPropertiesToInclude) { properties = properties.Where(a => BulkConfig.PropertiesToInclude.Contains(a.Name)); } if (AreSpecifiedPropertiesToExclude) { properties = properties.Where(a => !BulkConfig.PropertiesToExclude.Contains(a.Name)); } } if (loadOnlyPKColumn) { PropertyColumnNamesDict = properties.Where(a => PrimaryKeys.Contains(a.Name)).ToDictionary(a => a.Name, b => b.GetColumnName(storeObjectIdent).Replace("]", "]]")); } else { PropertyColumnNamesDict = properties.ToDictionary(a => a.Name, b => b.GetColumnName(storeObjectIdent).Replace("]", "]]")); ShadowProperties = new HashSet <string>(properties.Where(p => p.IsShadowProperty() && !p.IsForeignKey()).Select(p => p.GetColumnName(storeObjectIdent))); foreach (var property in properties.Where(p => p.GetValueConverter() != null)) { string columnName = property.GetColumnName(storeObjectIdent); ValueConverter converter = property.GetValueConverter(); ConvertibleProperties.Add(columnName, converter); } foreach (var navigation in entityType.GetNavigations().Where(x => !x.IsCollection && !x.TargetEntityType.IsOwned())) { FastPropertyDict.Add(navigation.Name, FastProperty.GetProperty(navigation.PropertyInfo)); } if (HasOwnedTypes) // Support owned entity property update. TODO: Optimize { foreach (var navgationProperty in ownedTypes) { var property = navgationProperty.PropertyInfo; FastPropertyDict.Add(property.Name, FastProperty.GetProperty(property)); Type navOwnedType = type.Assembly.GetType(property.PropertyType.FullName); var ownedEntityType = context.Model.FindEntityType(property.PropertyType); if (ownedEntityType == null) // when entity has more then one ownedType (e.g. Address HomeAddress, Address WorkAddress) or one ownedType is in multiple Entities like Audit is usually. { ownedEntityType = context.Model.GetEntityTypes().SingleOrDefault(a => a.DefiningNavigationName == property.Name && a.DefiningEntityType.Name == entityType.Name); } var ownedEntityProperties = ownedEntityType.GetProperties().ToList(); var ownedEntityPropertyNameColumnNameDict = new Dictionary <string, string>(); var ownedStoreObjectIdent = StoreObjectIdentifier.Create(ownedEntityType, StoreObjectType.Table).Value; foreach (var ownedEntityProperty in ownedEntityProperties) { if (!ownedEntityProperty.IsPrimaryKey()) { string columnName = ownedEntityProperty.GetColumnName(ownedStoreObjectIdent); ownedEntityPropertyNameColumnNameDict.Add(ownedEntityProperty.Name, columnName); var ownedEntityPropertyFullName = property.Name + "_" + ownedEntityProperty.Name; if (!FastPropertyDict.ContainsKey(ownedEntityPropertyFullName)) { FastPropertyDict.Add(ownedEntityPropertyFullName, FastProperty.GetProperty(ownedEntityProperty.PropertyInfo)); } } var converter = ownedEntityProperty.GetValueConverter(); if (converter != null) { ConvertibleProperties.Add($"{navgationProperty.Name}_{ownedEntityProperty.Name}", converter); } } var ownedProperties = property.PropertyType.GetProperties(); foreach (var ownedProperty in ownedProperties) { if (ownedEntityPropertyNameColumnNameDict.ContainsKey(ownedProperty.Name)) { string columnName = ownedEntityPropertyNameColumnNameDict[ownedProperty.Name]; var ownedPropertyType = Nullable.GetUnderlyingType(ownedProperty.PropertyType) ?? ownedProperty.PropertyType; bool doAddProperty = true; if (AreSpecifiedPropertiesToInclude && !BulkConfig.PropertiesToInclude.Contains(columnName)) { doAddProperty = false; } if (AreSpecifiedPropertiesToExclude && BulkConfig.PropertiesToExclude.Contains(columnName)) { doAddProperty = false; } if (doAddProperty) { PropertyColumnNamesDict.Add(property.Name + "." + ownedProperty.Name, columnName); OutputPropertyColumnNamesDict.Add(property.Name + "." + ownedProperty.Name, columnName); } } } } } } }
/// <summary> /// Returns the foreign key constraint name. /// </summary> /// <param name="foreignKey"> The foreign key. </param> /// <param name="storeObject"> The identifier of the containing store object. </param> /// <param name="principalStoreObject"> The identifier of the principal store object. </param> /// <returns> The foreign key constraint name. </returns> public static string GetConstraintName( [NotNull] this IForeignKey foreignKey, StoreObjectIdentifier storeObject, StoreObjectIdentifier principalStoreObject) { var annotation = foreignKey.FindAnnotation(RelationalAnnotationNames.Name); return(annotation != null ? (string)annotation.Value : foreignKey.GetDefaultName(storeObject, principalStoreObject)); }
/// <summary> /// Returns the name of the index in the database. /// </summary> /// <param name="index"> The index. </param> /// <param name="storeObject"> The identifier of the store object. </param> /// <returns> The name of the index in the database. </returns> public static string GetDatabaseName([NotNull] this IIndex index, StoreObjectIdentifier storeObject) => (string)index[RelationalAnnotationNames.Name] ?? index.Name ?? index.GetDefaultDatabaseName(storeObject);
public virtual void Owned_types_use_table_splitting_by_default() { var modelBuilder = CreateModelBuilder(); var model = modelBuilder.Model; modelBuilder.Entity <Book>().OwnsOne(b => b.AlternateLabel) .Ignore(l => l.Book) .OwnsOne(l => l.AnotherBookLabel) .Ignore(l => l.Book) .OwnsOne(s => s.SpecialBookLabel) .Ignore(l => l.Book) .Ignore(l => l.BookLabel); modelBuilder.Entity <Book>().OwnsOne(b => b.Label) .Ignore(l => l.Book) .OwnsOne(l => l.SpecialBookLabel) .Ignore(l => l.Book) .OwnsOne(a => a.AnotherBookLabel) .Ignore(l => l.Book); modelBuilder.Entity <Book>().OwnsOne(b => b.Label) .OwnsOne(l => l.AnotherBookLabel) .Ignore(l => l.Book) .OwnsOne(a => a.SpecialBookLabel) .Ignore(l => l.Book) .Ignore(l => l.BookLabel); modelBuilder.Entity <Book>().OwnsOne(b => b.AlternateLabel) .OwnsOne(l => l.SpecialBookLabel) .Ignore(l => l.Book) .OwnsOne(s => s.AnotherBookLabel) .Ignore(l => l.Book); var book = model.FindEntityType(typeof(Book)); var bookOwnership1 = book.FindNavigation(nameof(Book.Label)).ForeignKey; var bookOwnership2 = book.FindNavigation(nameof(Book.AlternateLabel)).ForeignKey; var bookLabel1Ownership1 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel)).ForeignKey; var bookLabel1Ownership2 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel)).ForeignKey; var bookLabel2Ownership1 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel)).ForeignKey; var bookLabel2Ownership2 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel)).ForeignKey; Assert.Equal(book.GetTableName(), bookOwnership1.DeclaringEntityType.GetTableName()); Assert.Equal(book.GetTableName(), bookOwnership2.DeclaringEntityType.GetTableName()); Assert.Equal(book.GetTableName(), bookLabel1Ownership1.DeclaringEntityType.GetTableName()); Assert.Equal(book.GetTableName(), bookLabel1Ownership2.DeclaringEntityType.GetTableName()); Assert.Equal(book.GetTableName(), bookLabel2Ownership1.DeclaringEntityType.GetTableName()); Assert.Equal(book.GetTableName(), bookLabel2Ownership2.DeclaringEntityType.GetTableName()); Assert.NotSame(bookOwnership1.DeclaringEntityType, bookOwnership2.DeclaringEntityType); Assert.Single(bookOwnership1.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookOwnership1.DeclaringEntityType.GetForeignKeys()); Assert.NotSame(bookLabel1Ownership1.DeclaringEntityType, bookLabel2Ownership1.DeclaringEntityType); Assert.NotSame(bookLabel1Ownership2.DeclaringEntityType, bookLabel2Ownership2.DeclaringEntityType); Assert.Single(bookLabel1Ownership1.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookLabel1Ownership2.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookLabel2Ownership1.DeclaringEntityType.GetForeignKeys()); Assert.Single(bookLabel2Ownership2.DeclaringEntityType.GetForeignKeys()); Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel))); Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(AnotherBookLabel))); Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(SpecialBookLabel))); Assert.Null( bookOwnership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id)) .GetColumnName(StoreObjectIdentifier.Table("Label", null))); Assert.Null( bookLabel2Ownership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id)) .GetColumnName(StoreObjectIdentifier.Table("AlternateLabel", null))); modelBuilder.Entity <Book>().OwnsOne(b => b.Label).ToTable("Label"); modelBuilder.Entity <Book>().OwnsOne(b => b.AlternateLabel).ToTable("AlternateLabel"); modelBuilder.FinalizeModel(); Assert.Equal( nameof(BookLabel.Id), bookOwnership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id)) .GetColumnName(StoreObjectIdentifier.Table("Label", null))); Assert.Equal( nameof(BookLabel.AnotherBookLabel) + "_" + nameof(BookLabel.Id), bookLabel2Ownership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id)) .GetColumnName(StoreObjectIdentifier.Table("AlternateLabel", null))); }
private IReadOnlyList <ColumnModification> GenerateColumnModifications() { var state = EntityState; var adding = state == EntityState.Added; var updating = state == EntityState.Modified; var columnModifications = new List <ColumnModification>(); Dictionary <string, ColumnValuePropagator> sharedColumnMap = null; if (_entries.Count > 1 || (_entries.Count == 1 && _entries[0].SharedIdentityEntry != null)) { sharedColumnMap = new Dictionary <string, ColumnValuePropagator>(); if (_comparer != null) { _entries.Sort(_comparer); } foreach (var entry in _entries) { if (entry.SharedIdentityEntry != null) { InitializeSharedColumns(entry.SharedIdentityEntry, updating, sharedColumnMap); } InitializeSharedColumns(entry, updating, sharedColumnMap); } } foreach (var entry in _entries) { var nonMainEntry = updating && (entry.EntityState == EntityState.Deleted || entry.EntityState == EntityState.Added); foreach (var property in entry.EntityType.GetProperties()) { var column = (IColumn)property.FindColumn(StoreObjectIdentifier.Table(TableName, Schema)); if (column == null) { continue; } var isKey = property.IsPrimaryKey(); var isCondition = !adding && (isKey || property.IsConcurrencyToken); var readValue = entry.IsStoreGenerated(property); ColumnValuePropagator columnPropagator = null; if (sharedColumnMap != null) { columnPropagator = sharedColumnMap[column.Name]; } var writeValue = false; if (!readValue) { if (adding) { writeValue = property.GetBeforeSaveBehavior() == PropertySaveBehavior.Save; } else if ((updating && property.GetAfterSaveBehavior() == PropertySaveBehavior.Save) || (!isKey && nonMainEntry)) { writeValue = columnPropagator?.TryPropagate(property, entry) ?? entry.IsModified(property); } } if (readValue || writeValue || isCondition) { if (readValue) { _requiresResultPropagation = true; } var columnModification = new ColumnModification( entry, property, column, _generateParameterName, readValue, writeValue, isKey, isCondition, _sensitiveLoggingEnabled); if (columnPropagator != null) { if (columnPropagator.ColumnModification != null) { columnPropagator.ColumnModification.AddSharedColumnModification(columnModification); continue; } columnPropagator.ColumnModification = columnModification; } columnModifications.Add(columnModification); } } } return(columnModifications); }
/// <summary> /// <para> /// Returns the <see cref="MySqlValueGenerationStrategy" /> to use for the property. /// </para> /// <para> /// If no strategy is set for the property, then the strategy to use will be taken from the <see cref="IModel" />. /// </para> /// </summary> /// <returns> The strategy, or <see cref="MySqlValueGenerationStrategy.None"/> if none was set. </returns> public static MySqlValueGenerationStrategy?GetValueGenerationStrategy([NotNull] this IReadOnlyProperty property, StoreObjectIdentifier storeObject = default) { var annotation = property[MySqlAnnotationNames.ValueGenerationStrategy]; if (annotation != null) { // Allow users to use the underlying type value instead of the enum itself. // Workaround for: https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues/1205 //return ObjectToEnumConverter.GetEnumValue<MySqlValueGenerationStrategy>(annotation); return(ObjectToEnumConverter.GetEnumValue <MySqlValueGenerationStrategy>(annotation)); } if (property.GetContainingForeignKeys().Any(fk => !fk.IsBaseLinking()) || property.TryGetDefaultValue(storeObject, out _) || property.GetDefaultValueSql() != null || property.GetComputedColumnSql() != null) { return(null); } if (storeObject != default && property.ValueGenerated == ValueGenerated.Never) { return(property.FindSharedStoreObjectRootProperty(storeObject) ?.GetValueGenerationStrategy(storeObject)); } if (property.ValueGenerated == ValueGenerated.OnAdd && IsCompatibleIdentityColumn(property)) { return(MySqlValueGenerationStrategy.IdentityColumn); } if (property.ValueGenerated == ValueGenerated.OnAddOrUpdate && IsCompatibleComputedColumn(property)) { return(MySqlValueGenerationStrategy.ComputedColumn); } return(null); }
/// <inheritdoc /> protected override void ValidateCompatible( IProperty property, IProperty duplicateProperty, string columnName, StoreObjectIdentifier storeObject, IDiagnosticsLogger <DbLoggerCategory.Model.Validation> logger) { base.ValidateCompatible(property, duplicateProperty, columnName, storeObject, logger); var propertyStrategy = property.GetValueGenerationStrategy(storeObject); var duplicatePropertyStrategy = duplicateProperty.GetValueGenerationStrategy(storeObject); if (propertyStrategy != duplicatePropertyStrategy) { throw new InvalidOperationException( SqlServerStrings.DuplicateColumnNameValueGenerationStrategyMismatch( duplicateProperty.DeclaringEntityType.DisplayName(), duplicateProperty.Name, property.DeclaringEntityType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); } switch (propertyStrategy) { case SqlServerValueGenerationStrategy.IdentityColumn: var increment = property.GetIdentityIncrement(); var duplicateIncrement = duplicateProperty.GetIdentityIncrement(); if (increment != duplicateIncrement) { throw new InvalidOperationException( SqlServerStrings.DuplicateColumnIdentityIncrementMismatch( duplicateProperty.DeclaringEntityType.DisplayName(), duplicateProperty.Name, property.DeclaringEntityType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); } var seed = property.GetIdentitySeed(); var duplicateSeed = duplicateProperty.GetIdentitySeed(); if (seed != duplicateSeed) { throw new InvalidOperationException( SqlServerStrings.DuplicateColumnIdentitySeedMismatch( duplicateProperty.DeclaringEntityType.DisplayName(), duplicateProperty.Name, property.DeclaringEntityType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); } break; case SqlServerValueGenerationStrategy.SequenceHiLo: if (property.GetHiLoSequenceName() != duplicateProperty.GetHiLoSequenceName() || property.GetHiLoSequenceSchema() != duplicateProperty.GetHiLoSequenceSchema()) { throw new InvalidOperationException( SqlServerStrings.DuplicateColumnSequenceMismatch( duplicateProperty.DeclaringEntityType.DisplayName(), duplicateProperty.Name, property.DeclaringEntityType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); } break; } }
/// <inheritdoc /> public virtual void ProcessModelFinalizing( IConventionModelBuilder modelBuilder, IConventionContext <IConventionModelBuilder> context) { foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) { foreach (var property in entityType.GetDeclaredProperties()) { SqlServerValueGenerationStrategy?strategy = null; var table = entityType.GetTableName(); if (table != null) { var storeObject = StoreObjectIdentifier.Table(table, entityType.GetSchema()); strategy = property.GetValueGenerationStrategy(storeObject, Dependencies.TypeMappingSource); if (strategy == SqlServerValueGenerationStrategy.None && !IsStrategyNoneNeeded(property, storeObject)) { strategy = null; } } else { var view = entityType.GetViewName(); if (view != null) { var storeObject = StoreObjectIdentifier.View(view, entityType.GetViewSchema()); strategy = property.GetValueGenerationStrategy(storeObject, Dependencies.TypeMappingSource); if (strategy == SqlServerValueGenerationStrategy.None && !IsStrategyNoneNeeded(property, storeObject)) { strategy = null; } } } // Needed for the annotation to show up in the model snapshot if (strategy != null) { property.Builder.HasValueGenerationStrategy(strategy); } } } bool IsStrategyNoneNeeded(IReadOnlyProperty property, StoreObjectIdentifier storeObject) { if (property.ValueGenerated == ValueGenerated.OnAdd && !property.TryGetDefaultValue(storeObject, out _) && property.GetDefaultValueSql(storeObject) == null && property.GetComputedColumnSql(storeObject) == null && property.DeclaringEntityType.Model.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.IdentityColumn) { var providerClrType = (property.GetValueConverter() ?? (property.FindRelationalTypeMapping(storeObject) ?? Dependencies.TypeMappingSource.FindMapping((IProperty)property))?.Converter) ?.ProviderClrType.UnwrapNullableType(); return(providerClrType != null && (providerClrType.IsInteger() || providerClrType == typeof(decimal))); } return(false); } }
public SqlServerUpdateContextQuery(DbContext context, string schema, string table, List <IProperty> propertiesToUpdate, List <IProperty> propertiesForPivot, List <IProperty> propertiesToBulkLoad, IEntityType baseType, IDictionary <string, MemberInfo> propertiesGetter, StoreObjectIdentifier storeObject) : base(context, schema ?? "dbo", table, propertiesToUpdate, propertiesForPivot, propertiesToBulkLoad, baseType, propertiesGetter, storeObject) { }
/// <summary> /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// </summary> public override IEnumerable <IAnnotation> For(IColumn column, bool designTime) { if (!designTime) { yield break; } var table = StoreObjectIdentifier.Table(column.Table.Name, column.Table.Schema); var identityProperty = column.PropertyMappings.Where( m => m.TableMapping.IsSharedTablePrincipal && m.TableMapping.EntityType == m.Property.DeclaringEntityType) .Select(m => m.Property) .FirstOrDefault( p => p.GetValueGenerationStrategy(table) == SqlServerValueGenerationStrategy.IdentityColumn); if (identityProperty != null) { var seed = identityProperty.GetIdentitySeed(table); var increment = identityProperty.GetIdentityIncrement(table); yield return(new Annotation( SqlServerAnnotationNames.Identity, string.Format(CultureInfo.InvariantCulture, "{0}, {1}", seed ?? 1, increment ?? 1))); } // Model validation ensures that these facets are the same on all mapped properties var property = column.PropertyMappings.First().Property; if (property.IsSparse() is bool isSparse) { yield return(new Annotation(SqlServerAnnotationNames.Sparse, isSparse)); } var entityType = column.Table.EntityTypeMappings.First().EntityType; if (entityType.IsTemporal() && designTime) { var periodStartPropertyName = entityType.GetPeriodStartPropertyName(); var periodEndPropertyName = entityType.GetPeriodEndPropertyName(); var storeObjectIdentifier = StoreObjectIdentifier.Table(table.Name, table.Schema); // for the RevEng path, we avoid adding period properties to the entity // because we don't want code for them to be generated - they need to be in shadow state // so if we don't find property on the entity, we know it's this scenario // and in that case period column name is actually the same as the period property name annotation // since in RevEng scenario there can't be custom column mapping // see #26007 var periodStartProperty = entityType.FindProperty(periodStartPropertyName !); var periodStartColumnName = periodStartProperty != null ? periodStartProperty.GetColumnName(storeObjectIdentifier) : periodStartPropertyName; var periodEndProperty = entityType.FindProperty(periodEndPropertyName !); var periodEndColumnName = periodEndProperty != null ? periodEndProperty.GetColumnName(storeObjectIdentifier) : periodEndPropertyName; if (column.Name == periodStartColumnName || column.Name == periodEndColumnName) { yield return(new Annotation(SqlServerAnnotationNames.IsTemporal, true)); yield return(new Annotation(SqlServerAnnotationNames.TemporalPeriodStartColumnName, periodStartColumnName)); yield return(new Annotation(SqlServerAnnotationNames.TemporalPeriodEndColumnName, periodEndColumnName)); } } }
/// <summary> /// <para> /// Finds the first <see cref="IConventionForeignKey" /> that is mapped to the same constraint in a shared table-like object. /// </para> /// <para> /// This method is typically used by database providers (and other extensions). It is generally /// not used in application code. /// </para> /// </summary> /// <param name="foreignKey"> The foreign key. </param> /// <param name="storeObject"> The identifier of the containing store object. </param> /// <returns> The foreign key if found, or <see langword="null" /> if none was found.</returns> public static IConventionForeignKey FindSharedObjectRootForeignKey( [NotNull] this IConventionForeignKey foreignKey, StoreObjectIdentifier storeObject) => (IConventionForeignKey)((IForeignKey)foreignKey).FindSharedObjectRootForeignKey(storeObject);
/// <inheritdoc /> public virtual void ProcessModelFinalizing( IConventionModelBuilder modelBuilder, IConventionContext <IConventionModelBuilder> context) { var tableToEntityTypes = new Dictionary <(string Name, string Schema), List <IConventionEntityType> >(); foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) { var tableName = entityType.GetTableName(); if (tableName == null) { continue; } var table = (tableName, entityType.GetSchema()); if (!tableToEntityTypes.TryGetValue(table, out var mappedTypes)) { mappedTypes = new List <IConventionEntityType>(); tableToEntityTypes[table] = mappedTypes; } mappedTypes.Add(entityType); } foreach (var tableToEntityType in tableToEntityTypes) { var table = tableToEntityType.Key; var mappedTypes = tableToEntityType.Value; var concurrencyColumns = GetConcurrencyTokensMap(StoreObjectIdentifier.Table(table.Name, table.Schema), mappedTypes); if (concurrencyColumns == null) { continue; } foreach (var concurrencyColumn in concurrencyColumns) { var concurrencyColumnName = concurrencyColumn.Key; var propertiesMappedToConcurrencyColumn = concurrencyColumn.Value; Dictionary <IConventionEntityType, IProperty> entityTypesMissingConcurrencyColumn = null; foreach (var entityType in mappedTypes) { var foundMappedProperty = !IsConcurrencyTokenMissing(propertiesMappedToConcurrencyColumn, entityType, mappedTypes) || entityType.GetProperties() .Any(p => p.GetColumnName(StoreObjectIdentifier.Table(table.Name, table.Schema)) == concurrencyColumnName); if (!foundMappedProperty) { if (entityTypesMissingConcurrencyColumn == null) { entityTypesMissingConcurrencyColumn = new Dictionary <IConventionEntityType, IProperty>(); } // store the entity type which is missing the // concurrency token property, mapped to an example // property which _is_ mapped to this concurrency token // column and which will be used later as a template entityTypesMissingConcurrencyColumn.Add( entityType, propertiesMappedToConcurrencyColumn.First()); } } if (entityTypesMissingConcurrencyColumn == null) { continue; } RemoveDerivedEntityTypes(entityTypesMissingConcurrencyColumn); foreach (var entityTypeToExampleProperty in entityTypesMissingConcurrencyColumn) { var exampleProperty = entityTypeToExampleProperty.Value; entityTypeToExampleProperty.Key.Builder.CreateUniqueProperty( exampleProperty.ClrType, ConcurrencyPropertyPrefix + exampleProperty.Name, !exampleProperty.IsNullable) .HasColumnName(concurrencyColumnName) .HasColumnType(exampleProperty.GetColumnType()) .IsConcurrencyToken(true) .ValueGenerated(exampleProperty.ValueGenerated); } } } }
/// <summary> /// Returns the foreign key constraint name. /// </summary> /// <param name="foreignKey"> The foreign key. </param> /// <returns> The foreign key constraint name. </returns> public static string GetConstraintName([NotNull] this IForeignKey foreignKey) => foreignKey.GetConstraintName( StoreObjectIdentifier.Table(foreignKey.DeclaringEntityType.GetTableName(), foreignKey.DeclaringEntityType.GetSchema()), StoreObjectIdentifier.Table(foreignKey.PrincipalEntityType.GetTableName(), foreignKey.PrincipalEntityType.GetSchema()));
/// <summary> /// <para> /// Returns the <see cref="MySQLValueGenerationStrategy" /> to use for the property. /// </para> /// <para> /// If no strategy is set for the property, then the strategy to use will be taken from the <see cref="IModel" />. /// </para> /// </summary> /// <returns> The strategy, or <see cref="SqlServerValueGenerationStrategy.None" /> if none was set. </returns> public static MySQLValueGenerationStrategy?GetValueGenerationStrategy([NotNull] this IProperty property, StoreObjectIdentifier storeObject = default) { var annotation = property[MySQLAnnotationNames.ValueGenerationStrategy]; if (annotation != null) { return((MySQLValueGenerationStrategy)annotation); } if (property.GetDefaultValue() != null || property.GetDefaultValueSql() != null || property.GetComputedColumnSql() != null) { return(null); } if (storeObject != default && property.ValueGenerated == ValueGenerated.Never) { return(property.FindSharedStoreObjectRootProperty(storeObject)?.GetValueGenerationStrategy(storeObject)); } if (IsCompatibleIdentityColumn(property) && property.ValueGenerated == ValueGenerated.OnAdd) { return(MySQLValueGenerationStrategy.IdentityColumn); } if (IsCompatibleComputedColumn(property) && property.ValueGenerated == ValueGenerated.OnAddOrUpdate) { return(MySQLValueGenerationStrategy.ComputedColumn); } return(null); }
/// <summary> /// <para> /// Finds the first <see cref="IConventionIndex" /> that is mapped to the same index in a shared table-like object. /// </para> /// <para> /// This method is typically used by database providers (and other extensions). It is generally /// not used in application code. /// </para> /// </summary> /// <param name="index"> The index. </param> /// <param name="storeObject"> The identifier of the containing store object. </param> /// <returns> The index found, or <see langword="null" /> if none was found.</returns> public static IConventionIndex FindSharedObjectRootIndex( [NotNull] this IConventionIndex index, StoreObjectIdentifier storeObject) => (IConventionIndex)((IIndex)index).FindSharedObjectRootIndex(storeObject);
/// <summary> /// Returns the key constraint name for this key. /// </summary> /// <param name="key"> The key. </param> /// <returns> The key constraint name for this key. </returns> public static string GetName([NotNull] this IKey key) => key.GetName(StoreObjectIdentifier.Table(key.DeclaringEntityType.GetTableName(), key.DeclaringEntityType.GetSchema()));
private static void ValidateTemporalPeriodProperty(IEntityType temporalEntityType, bool periodStart) { var annotationPropertyName = periodStart ? temporalEntityType.GetPeriodStartPropertyName() : temporalEntityType.GetPeriodEndPropertyName(); if (annotationPropertyName == null) { throw new InvalidOperationException( SqlServerStrings.TemporalMustDefinePeriodProperties( temporalEntityType.DisplayName())); } var periodProperty = temporalEntityType.FindProperty(annotationPropertyName); if (periodProperty == null) { throw new InvalidOperationException( SqlServerStrings.TemporalExpectedPeriodPropertyNotFound( temporalEntityType.DisplayName(), annotationPropertyName)); } if (!periodProperty.IsShadowProperty() && !temporalEntityType.IsPropertyBag) { throw new InvalidOperationException( SqlServerStrings.TemporalPeriodPropertyMustBeInShadowState( temporalEntityType.DisplayName(), periodProperty.Name)); } if (periodProperty.IsNullable || periodProperty.ClrType != typeof(DateTime)) { throw new InvalidOperationException( SqlServerStrings.TemporalPeriodPropertyMustBeNonNullableDateTime( temporalEntityType.DisplayName(), periodProperty.Name, nameof(DateTime))); } const string expectedPeriodColumnName = "datetime2"; if (periodProperty.GetColumnType() != expectedPeriodColumnName) { throw new InvalidOperationException( SqlServerStrings.TemporalPeriodPropertyMustBeMappedToDatetime2( temporalEntityType.DisplayName(), periodProperty.Name, expectedPeriodColumnName)); } if (periodProperty.TryGetDefaultValue(out var _)) { throw new InvalidOperationException( SqlServerStrings.TemporalPeriodPropertyCantHaveDefaultValue( temporalEntityType.DisplayName(), periodProperty.Name)); } if (temporalEntityType.GetTableName() is string tableName) { var storeObjectIdentifier = StoreObjectIdentifier.Table(tableName, temporalEntityType.GetSchema()); var periodColumnName = periodProperty.GetColumnName(storeObjectIdentifier); var propertiesMappedToPeriodColumn = temporalEntityType.GetProperties().Where( p => p.Name != periodProperty.Name && p.GetColumnName(storeObjectIdentifier) == periodColumnName).ToList(); foreach (var propertyMappedToPeriodColumn in propertiesMappedToPeriodColumn) { if (propertyMappedToPeriodColumn.ValueGenerated != ValueGenerated.OnAddOrUpdate) { throw new InvalidOperationException( SqlServerStrings.TemporalPropertyMappedToPeriodColumnMustBeValueGeneratedOnAddOrUpdate( temporalEntityType.DisplayName(), propertyMappedToPeriodColumn.Name, nameof(ValueGenerated.OnAddOrUpdate))); } if (propertyMappedToPeriodColumn.TryGetDefaultValue(out var _)) { throw new InvalidOperationException( SqlServerStrings.TemporalPropertyMappedToPeriodColumnCantHaveDefaultValue( temporalEntityType.DisplayName(), propertyMappedToPeriodColumn.Name)); } } } // TODO: check that period property is excluded from query (once the annotation is added) }
/// <summary> /// <para> /// Finds the first <see cref="IConventionKey" /> that is mapped to the same constraint in a shared table-like object. /// </para> /// <para> /// This method is typically used by database providers (and other extensions). It is generally /// not used in application code. /// </para> /// </summary> /// <param name="key"> The key. </param> /// <param name="storeObject"> The identifier of the containing store object. </param> /// <returns> The key found, or <see langword="null" /> if none was found.</returns> public static IConventionKey FindSharedObjectRootKey( [NotNull] this IConventionKey key, StoreObjectIdentifier storeObject) => (IConventionKey)((IKey)key).FindSharedObjectRootKey(storeObject);
/// <summary> /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// </summary> public virtual ResultSetMapping AppendBulkInsertOperation( StringBuilder commandStringBuilder, IReadOnlyList <IReadOnlyModificationCommand> modificationCommands, int commandPosition) { var table = StoreObjectIdentifier.Table(modificationCommands[0].TableName, modificationCommands[0].Schema); if (modificationCommands.Count == 1) { return(modificationCommands[0].ColumnModifications.All( o => !o.IsKey || !o.IsRead || o.Property?.GetValueGenerationStrategy(table) == SqlServerValueGenerationStrategy.IdentityColumn) ? AppendInsertOperation(commandStringBuilder, modificationCommands[0], commandPosition) : AppendInsertOperationWithServerKeys( commandStringBuilder, modificationCommands[0], modificationCommands[0].ColumnModifications.Where(o => o.IsKey).ToList(), modificationCommands[0].ColumnModifications.Where(o => o.IsRead).ToList(), commandPosition)); } var readOperations = modificationCommands[0].ColumnModifications.Where(o => o.IsRead).ToList(); var writeOperations = modificationCommands[0].ColumnModifications.Where(o => o.IsWrite).ToList(); var keyOperations = modificationCommands[0].ColumnModifications.Where(o => o.IsKey).ToList(); var defaultValuesOnly = writeOperations.Count == 0; var nonIdentityOperations = modificationCommands[0].ColumnModifications .Where(o => o.Property?.GetValueGenerationStrategy(table) != SqlServerValueGenerationStrategy.IdentityColumn) .ToList(); if (defaultValuesOnly) { if (nonIdentityOperations.Count == 0 || readOperations.Count == 0) { foreach (var modification in modificationCommands) { AppendInsertOperation(commandStringBuilder, modification, commandPosition); } return(readOperations.Count == 0 ? ResultSetMapping.NoResultSet : ResultSetMapping.LastInResultSet); } if (nonIdentityOperations.Count > 1) { nonIdentityOperations.RemoveRange(1, nonIdentityOperations.Count - 1); } } if (readOperations.Count == 0) { return(AppendBulkInsertWithoutServerValues(commandStringBuilder, modificationCommands, writeOperations)); } if (defaultValuesOnly) { return(AppendBulkInsertWithServerValuesOnly( commandStringBuilder, modificationCommands, commandPosition, nonIdentityOperations, keyOperations, readOperations)); } if (modificationCommands[0].Entries.SelectMany(e => e.EntityType.GetAllBaseTypesInclusive()) .Any(e => e.IsMemoryOptimized())) { if (!nonIdentityOperations.Any(o => o.IsRead && o.IsKey)) { foreach (var modification in modificationCommands) { AppendInsertOperation(commandStringBuilder, modification, commandPosition++); } } else { foreach (var modification in modificationCommands) { AppendInsertOperationWithServerKeys( commandStringBuilder, modification, keyOperations, readOperations, commandPosition++); } } return(ResultSetMapping.LastInResultSet); } return(AppendBulkInsertWithServerValues( commandStringBuilder, modificationCommands, commandPosition, writeOperations, keyOperations, readOperations)); }
/// <summary> /// Returns the key constraint name for this key for a particular table. /// </summary> /// <param name="key"> The key. </param> /// <param name="storeObject"> The identifier of the containing store object. </param> /// <returns> The key constraint name for this key. </returns> public static string GetName([NotNull] this IKey key, StoreObjectIdentifier storeObject) => (string)key[RelationalAnnotationNames.Name] ?? key.GetDefaultName(storeObject);
public static string FormatColumns( [NotNull] this IEnumerable <IProperty> properties, StoreObjectIdentifier storeObject) => "{" + string.Join(", ", properties.Select(p => "'" + p.GetColumnName(storeObject) + "'")) + "}";
/// <summary> /// Returns the default key constraint name that would be used for this key for a particular table. /// </summary> /// <param name="key"> The key. </param> /// <param name="storeObject"> The identifier of the containing store object. </param> /// <returns> The default key constraint name that would be used for this key. </returns> public static string GetDefaultName([NotNull] this IKey key, StoreObjectIdentifier storeObject) { string name = null; if (key.IsPrimaryKey()) { var rootKey = key; // Limit traversal to avoid getting stuck in a cycle (validation will throw for these later) // Using a hashset is detrimental to the perf when there are no cycles for (var i = 0; i < Metadata.Internal.RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++) { var linkingFk = rootKey.DeclaringEntityType.FindRowInternalForeignKeys(storeObject) .FirstOrDefault(); if (linkingFk == null) { break; } rootKey = linkingFk.PrincipalEntityType.FindPrimaryKey(); } if (rootKey != null && rootKey != key) { return(rootKey.GetName(storeObject)); } name = "PK_" + storeObject.Name; } else { var propertyNames = key.Properties.Select(p => p.GetColumnName(storeObject)).ToList(); var rootKey = key; // Limit traversal to avoid getting stuck in a cycle (validation will throw for these later) // Using a hashset is detrimental to the perf when there are no cycles for (var i = 0; i < Metadata.Internal.RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++) { var linkedKey = rootKey.DeclaringEntityType .FindRowInternalForeignKeys(storeObject) .SelectMany(fk => fk.PrincipalEntityType.GetKeys()) .FirstOrDefault(k => k.Properties.Select(p => p.GetColumnName(storeObject)).SequenceEqual(propertyNames)); if (linkedKey == null) { break; } rootKey = linkedKey; } if (rootKey != key) { return(rootKey.GetName(storeObject)); } name = new StringBuilder() .Append("AK_") .Append(storeObject.Name) .Append("_") .AppendJoin(key.Properties.Select(p => p.GetColumnName(storeObject)), "_") .ToString(); } return(Uniquifier.Truncate(name, key.DeclaringEntityType.Model.GetMaxIdentifierLength())); }
private static bool?GetDefaultIsClustered([NotNull] IIndex index, StoreObjectIdentifier storeObject) { var sharedTableRootIndex = index.FindSharedObjectRootIndex(storeObject); return(sharedTableRootIndex?.IsClustered(storeObject)); }
/// <summary> /// <para> /// Finds the first <see cref="IForeignKey" /> that is mapped to the same constraint in a shared table-like object. /// </para> /// <para> /// This method is typically used by database providers (and other extensions). It is generally /// not used in application code. /// </para> /// </summary> /// <param name="foreignKey"> The foreign key. </param> /// <param name="storeObject"> The identifier of the containing store object. </param> /// <returns> The foreign key if found, or <see langword="null" /> if none was found.</returns> public static IForeignKey FindSharedObjectRootForeignKey([NotNull] this IForeignKey foreignKey, StoreObjectIdentifier storeObject) { Check.NotNull(foreignKey, nameof(foreignKey)); var foreignKeyName = foreignKey.GetConstraintName(storeObject, StoreObjectIdentifier.Table(foreignKey.PrincipalEntityType.GetTableName(), foreignKey.PrincipalEntityType.GetSchema())); var rootForeignKey = foreignKey; // Limit traversal to avoid getting stuck in a cycle (validation will throw for these later) // Using a hashset is detrimental to the perf when there are no cycles for (var i = 0; i < Metadata.Internal.RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++) { var linkedKey = rootForeignKey.DeclaringEntityType .FindRowInternalForeignKeys(storeObject) .SelectMany(fk => fk.PrincipalEntityType.GetForeignKeys()) .FirstOrDefault(k => k.GetConstraintName(storeObject, StoreObjectIdentifier.Table(k.PrincipalEntityType.GetTableName(), k.PrincipalEntityType.GetSchema())) == foreignKeyName); if (linkedKey == null) { break; } rootForeignKey = linkedKey; } return(rootForeignKey == foreignKey ? null : rootForeignKey); }
public virtual void TPT_identifying_FK_are_created_only_on_declaring_type() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity <BigMak>() .Ignore(b => b.Bun) .Ignore(b => b.Pickles); modelBuilder.Entity <Ingredient>(b => { b.ToTable("Ingredients"); b.Ignore(i => i.BigMak); }); modelBuilder.Entity <Bun>(b => { b.ToTable("Buns"); b.HasOne(i => i.BigMak).WithOne().HasForeignKey <Bun>(i => i.Id); }); modelBuilder.Entity <SesameBun>(b => { b.ToTable("SesameBuns"); }); var model = modelBuilder.FinalizeModel(); var principalType = model.FindEntityType(typeof(BigMak)); Assert.Empty(principalType.GetForeignKeys()); Assert.Empty(principalType.GetIndexes()); var ingredientType = model.FindEntityType(typeof(Ingredient)); var bunType = model.FindEntityType(typeof(Bun)); Assert.Empty(bunType.GetIndexes()); var bunFk = bunType.GetDeclaredForeignKeys().Single(fk => !fk.IsBaseLinking()); Assert.Equal("FK_Buns_BigMak_Id", bunFk.GetConstraintName()); Assert.Equal("FK_Buns_BigMak_Id", bunFk.GetConstraintName( StoreObjectIdentifier.Create(bunType, StoreObjectType.Table).Value, StoreObjectIdentifier.Create(principalType, StoreObjectType.Table).Value)); Assert.Single(bunFk.GetMappedConstraints()); var bunLinkingFk = bunType.GetDeclaredForeignKeys().Single(fk => fk.IsBaseLinking()); Assert.Equal("FK_Buns_Ingredients_Id", bunLinkingFk.GetConstraintName()); Assert.Equal("FK_Buns_Ingredients_Id", bunLinkingFk.GetConstraintName( StoreObjectIdentifier.Create(bunType, StoreObjectType.Table).Value, StoreObjectIdentifier.Create(ingredientType, StoreObjectType.Table).Value)); Assert.Single(bunLinkingFk.GetMappedConstraints()); var sesameBunType = model.FindEntityType(typeof(SesameBun)); Assert.Empty(sesameBunType.GetIndexes()); var sesameBunFk = sesameBunType.GetDeclaredForeignKeys().Single(); Assert.True(sesameBunFk.IsBaseLinking()); Assert.Equal("FK_SesameBuns_Buns_Id", sesameBunFk.GetConstraintName()); Assert.Equal("FK_SesameBuns_Buns_Id", sesameBunFk.GetConstraintName( StoreObjectIdentifier.Create(sesameBunType, StoreObjectType.Table).Value, StoreObjectIdentifier.Create(bunType, StoreObjectType.Table).Value)); Assert.Single(sesameBunFk.GetMappedConstraints()); }
public static void PostgresModelCreating(this ModelBuilder builder) { var mapper = new NpgsqlSnakeCaseNameTranslator(); foreach (var entity in builder.Model.GetEntityTypes()) { // modify column names foreach (var property in entity.GetProperties()) { property.SetColumnName(mapper.TranslateMemberName(property.GetColumnName(StoreObjectIdentifier.Table(entity.GetTableName(), null)))); } // modify table name entity.SetTableName(mapper.TranslateMemberName(entity.GetTableName())); // modify keys names foreach (var key in entity.GetKeys()) { key.SetName(mapper.TranslateMemberName(key.GetName())); } // modify foreign keys names foreach (var key in entity.GetForeignKeys()) { key.SetConstraintName(mapper.TranslateMemberName(key.GetConstraintName())); } // modify indexes names foreach (var index in entity.GetIndexes()) { index.SetDatabaseName(mapper.TranslateMemberName(index.GetDatabaseName())); } // move asp_net tables into schema 'identity' if (entity.GetTableName().StartsWith("asp_net_")) { entity.SetTableName(entity.GetTableName().Replace("asp_net_", string.Empty)); entity.SetSchema("identity"); } } }