public static (string, List <object>) GetSqlUpdate <T>(IQueryable <T> query, HookingDbContext context, Expression <Func <T, T> > expression) where T : class { return(GetSqlUpdate <T>(query, context, typeof(T), expression)); }
/// <summary> /// Loads an entity referenced by a navigation property from database, unless data is already loaded. /// </summary> /// <param name="entity">Entity instance to load data for.</param> /// <param name="navigationProperty">The navigation property expression.</param> /// <param name="force"><c>false:</c> do nothing if data is already loaded. <c>true:</c> Reload data event if loaded already.</param> /// <param name="queryModifier">Modifier for the query that is about to be executed against the database.</param> /// <param name="cancelToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param> public static async Task <ReferenceEntry <TEntity, TProperty> > LoadReferenceAsync <TEntity, TProperty>( this HookingDbContext ctx, TEntity entity, Expression <Func <TEntity, TProperty> > navigationProperty, bool force = false, Func <IQueryable <TProperty>, IQueryable <TProperty> > queryModifier = null, CancellationToken cancelToken = default) where TEntity : BaseEntity where TProperty : BaseEntity { Guard.NotNull(entity, nameof(entity)); Guard.NotNull(navigationProperty, nameof(navigationProperty)); if (entity.Id == 0) { return(null); } var entry = ctx.Entry(entity); if (entry.State == EfState.Deleted) { return(null); } var reference = entry.Reference(navigationProperty); var isLoaded = reference.CurrentValue != null || reference.IsLoaded; if (!isLoaded && entry.State == EfState.Detached) { try { // Attaching an entity while another instance with same primary key is attached will throw. // First we gonna try to locate an already attached entity. var other = ctx.FindTracked <TEntity>(entity.Id); if (other != null) { // An entity with same key is attached already. So we gonna load the reference property of attached entity // and copy the result to this detached entity. This way we don't need to attach the source entity. var otherReference = await ctx.LoadReferenceAsync(other, navigationProperty, force, queryModifier, cancelToken : cancelToken); // Copy reference over to detached entity. reference.CurrentValue = otherReference.CurrentValue; reference.IsLoaded = true; isLoaded = true; force = false; } else { ctx.Attach(entity); } } catch { // Attach may throw! } } if (force) { reference.IsLoaded = false; isLoaded = false; } if (!isLoaded) { if (queryModifier != null) { var query = queryModifier(reference.Query()); reference.CurrentValue = await query.FirstOrDefaultAsync(cancellationToken : cancelToken); } else { await reference.LoadAsync(cancelToken); } reference.IsLoaded = true; } return(reference); }
public static (string, string, string, string, string, IEnumerable <object>) GetBatchSql(IQueryable query, HookingDbContext context, bool isUpdate) { var(fullSqlQuery, parameters) = ToParametrizedSql(query, context); var databaseType = context.DataProvider.ProviderType; var(leadingComments, sqlQuery) = SplitLeadingCommentsAndMainSqlQuery(fullSqlQuery); string tableAlias = string.Empty; string tableAliasSufixAs = string.Empty; string topStatement = string.Empty; if (databaseType != DbSystemType.Sqlite) // when Sqlite and Deleted metod tableAlias is Empty: "" { string escapeSymbolEnd = (databaseType == DbSystemType.SqlServer) ? "]" : "."; // SqlServer : PostgreSql; string escapeSymbolStart = (databaseType == DbSystemType.SqlServer) ? "[" : " "; // SqlServer : PostgreSql; string tableAliasEnd = sqlQuery[SelectStatementLength..sqlQuery.IndexOf(escapeSymbolEnd)]; // " TOP(10) [table_alias" / " [table_alias" : " table_alias"
public static int DetachEntities <TEntity>(this HookingDbContext ctx, bool unchangedEntitiesOnly = true, bool deep = false) where TEntity : BaseEntity { return(ctx.DetachEntities(o => o is TEntity, unchangedEntitiesOnly, deep)); }
/// <summary> /// Loads entities referenced by a collection navigation property from database, unless data is already loaded. /// </summary> /// <param name="entity">Entity instance to load data for.</param> /// <param name="navigationProperty">The navigation property expression.</param> /// <param name="force"><c>false:</c> do nothing if data is already loaded. <c>true:</c> reload data even if already loaded.</param> /// <param name="queryModifier">Modifier for the query that is about to be executed against the database.</param> /// <param name="cancelToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param> public static async Task <CollectionEntry <TEntity, TCollection> > LoadCollectionAsync <TEntity, TCollection>( this HookingDbContext ctx, TEntity entity, Expression <Func <TEntity, IEnumerable <TCollection> > > navigationProperty, bool force = false, Func <IQueryable <TCollection>, IQueryable <TCollection> > queryModifier = null, CancellationToken cancelToken = default) where TEntity : BaseEntity where TCollection : BaseEntity { Guard.NotNull(entity, nameof(entity)); Guard.NotNull(navigationProperty, nameof(navigationProperty)); if (entity.Id == 0) { return(null); } var entry = ctx.Entry(entity); if (entry.State == EfState.Deleted) { return(null); } var collection = entry.Collection(navigationProperty); // TODO: (Core) Entities with hashSets as collections always return true here, as they are never null (for example: Product.ProductVariantAttributes). var isLoaded = collection.CurrentValue != null || collection.IsLoaded; if (!isLoaded && entry.State == EfState.Detached) { try { // Attaching an entity while another instance with same primary key is attached will throw. // First we gonna try to locate an already attached entity. var other = ctx.FindTracked <TEntity>(entity.Id); if (other != null) { // An entity with same key is attached already. So we gonna load the navigation property of attached entity // and copy the result to this detached entity. This way we don't need to attach the source entity. var otherCollection = await ctx.LoadCollectionAsync(other, navigationProperty, force, queryModifier, cancelToken : cancelToken); // Copy collection over to detached entity. collection.CurrentValue = otherCollection.CurrentValue; collection.IsLoaded = true; isLoaded = true; force = false; } else { ctx.Attach(entity); } } catch { // Attach may throw! } } if (force) { collection.IsLoaded = false; isLoaded = false; } if (!isLoaded) { if (queryModifier != null) { var query = queryModifier(collection.Query()); collection.CurrentValue = await query.ToListAsync(cancellationToken : cancelToken); } else { await collection.LoadAsync(cancelToken); } collection.IsLoaded = true; } return(collection); }
/// <summary> /// Reloads the entity from the database overwriting any property values with values from the database. /// The entity will be in the Unchanged state after calling this method. /// </summary> /// <typeparam name="TEntity">Type of entity</typeparam> /// <param name="entity">The entity instance</param> public static Task ReloadEntityAsync <TEntity>(this HookingDbContext ctx, TEntity entity, CancellationToken cancelToken = default) where TEntity : BaseEntity { return(ctx.Entry((object)entity).ReloadEntityAsync(cancelToken)); }
public static int DetachEntity <TEntity>(this HookingDbContext ctx, TEntity entity, bool deep = false) where TEntity : BaseEntity { return(ctx.DetachInternal(entity, deep ? new HashSet <BaseEntity>() : null, deep)); }
/// <summary> /// Gets a list of modified properties for the specified entity /// </summary> /// <param name="entity">The entity instance for which to get modified properties for</param> /// <returns> /// A dictionary, where the key is the name of the modified property /// and the value is its ORIGINAL value (which was tracked when the entity /// was attached to the context the first time) /// Returns an empty dictionary if no modification could be detected. /// </returns> public static IDictionary <string, object> GetModifiedProperties(this HookingDbContext ctx, BaseEntity entity) { return(ctx.Entry((object)entity).GetModifiedProperties()); }
/// <summary> /// Reloads the entity from the database overwriting any property values with values from the database. /// The entity will be in the Unchanged state after calling this method. /// </summary> /// <typeparam name="TEntity">Type of entity</typeparam> /// <param name="entity">The entity instance</param> public static void ReloadEntity <TEntity>(this HookingDbContext ctx, TEntity entity) where TEntity : BaseEntity { ctx.Entry((object)entity).ReloadEntity(); }
/// <summary> /// Checks whether at least one entity in the change tracker is in <see cref="EfState.Added"/>, /// <see cref="EfState.Deleted"/> or <see cref="EfState.Modified"/> state. /// </summary> /// <param name="ctx"></param> /// <returns></returns> public static bool HasChanges(this HookingDbContext ctx) { return(ctx.ChangeTracker.Entries().Where(x => x.State > EfState.Unchanged).Any()); }
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 == DbSystemType.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); } } } } } } }
public static TableInfo CreateInstance(HookingDbContext context, Type type, IList <object> entities, OperationType operationType, BulkConfig bulkConfig) { return(CreateInstance <object>(context, type, entities, operationType, bulkConfig)); }
public static TableInfo CreateInstance <T>(HookingDbContext context, IList <T> entities, OperationType operationType, BulkConfig bulkConfig) { return(CreateInstance <T>(context, typeof(T), entities, operationType, bulkConfig)); }
public static (string, List <object>) GetSqlUpdate(IQueryable query, HookingDbContext context, Type type, Expression <Func <object, object> > expression) { return(GetSqlUpdate <object>(query, context, type, expression)); }