Beispiel #1
0
 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));
 }
Beispiel #2
0
        /// <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);
        }
Beispiel #3
0
        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"
Beispiel #4
0
 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));
 }
Beispiel #5
0
        /// <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);
        }
Beispiel #6
0
 /// <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));
 }
Beispiel #7
0
 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));
 }
Beispiel #8
0
 /// <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());
 }
Beispiel #9
0
 /// <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();
 }
Beispiel #10
0
 /// <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());
 }
Beispiel #11
0
        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);
                                }
                            }
                        }
                    }
                }
            }
        }
Beispiel #12
0
 public static TableInfo CreateInstance(HookingDbContext context, Type type, IList <object> entities, OperationType operationType, BulkConfig bulkConfig)
 {
     return(CreateInstance <object>(context, type, entities, operationType, bulkConfig));
 }
Beispiel #13
0
 public static TableInfo CreateInstance <T>(HookingDbContext context, IList <T> entities, OperationType operationType, BulkConfig bulkConfig)
 {
     return(CreateInstance <T>(context, typeof(T), entities, operationType, bulkConfig));
 }
Beispiel #14
0
 public static (string, List <object>) GetSqlUpdate(IQueryable query, HookingDbContext context, Type type, Expression <Func <object, object> > expression)
 {
     return(GetSqlUpdate <object>(query, context, type, expression));
 }