public void LoadData <T>(DbContext context, bool loadOnlyPKColumn) { var entityType = context.Model.FindEntityType(typeof(T)); if (entityType == null) { throw new InvalidOperationException("DbContext does not contain EntitySet for Type: " + typeof(T).Name); } var relationalData = entityType.Relational(); Schema = relationalData.Schema ?? "dbo"; TableName = relationalData.TableName; TempTableSufix = "Temp" + 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; PrimaryKeys = AreSpecifiedUpdateByProperties ? BulkConfig.UpdateByProperties : entityType.FindPrimaryKey().Properties.Select(a => a.Name).ToList(); HasSinglePrimaryKey = PrimaryKeys.Count == 1; var allProperties = entityType.GetProperties().AsEnumerable(); var allNavigationProperties = entityType.GetNavigations().Where(a => a.GetTargetType().IsOwned()); HasOwnedTypes = allNavigationProperties.Any(); // timestamp datatype can only be set by database, that's property having [Timestamp] Attribute but keep if one with [ConcurrencyCheck] var timeStampProperties = allProperties.Where(a => a.IsConcurrencyToken == true && a.ValueGenerated == ValueGenerated.OnAddOrUpdate && a.BeforeSaveBehavior == PropertySaveBehavior.Ignore); TimeStampColumn = timeStampProperties.FirstOrDefault()?.Relational().ColumnName; // expected to be only One var properties = allProperties.Except(timeStampProperties); OutputPropertyColumnNamesDict = properties.ToDictionary(a => a.Name, b => b.Relational().ColumnName); properties = properties.Where(a => a.Relational().ComputedColumnSql == null); 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); } } } } UpdateByPropertiesAreNullable = properties.Any(a => 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 specifed, 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.Relational().ColumnName); } else { PropertyColumnNamesDict = properties.ToDictionary(a => a.Name, b => b.Relational().ColumnName); ShadowProperties = new HashSet <string>(properties.Where(p => p.IsShadowProperty).Select(p => p.Relational().ColumnName)); foreach (var property in properties.Where(p => p.GetValueConverter() != null)) { ConvertibleProperties.Add(property.Relational().ColumnName, property.GetValueConverter()); } if (HasOwnedTypes) // Support owned entity property update. TODO: Optimize { var type = typeof(T); foreach (var navgationProperty in allNavigationProperties) { var property = navgationProperty.PropertyInfo; 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>(); foreach (var ownedEntityProperty in ownedEntityProperties) { if (!ownedEntityProperty.IsPrimaryKey()) { string columnName = ownedEntityProperty.Relational().ColumnName; ownedEntityPropertyNameColumnNameDict.Add(ownedEntityProperty.Name, columnName); } } 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; PropertyColumnNamesDict.Add(property.Name + "." + ownedProperty.Name, columnName); OutputPropertyColumnNamesDict.Add(property.Name + "." + ownedProperty.Name, columnName); } } } } } }
public void LoadData <T>(DbContext context, IList <T> entities, bool loadOnlyPKColumn) { LoadOnlyPKColumn = loadOnlyPKColumn; var type = typeof(T); 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(); Schema = relationalData.Schema ?? "dbo"; TableName = relationalData.TableName; TempTableSufix = "Temp" + 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(); var ownedTypes = entityType.GetNavigations().Where(a => a.GetTargetType().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)?.Name; // 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 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.Relational().ColumnType == timestampDbTypeName); TimeStampColumnName = timeStampProperties.FirstOrDefault()?.Relational().ColumnName; // can be only One var allPropertiesExceptTimeStamp = allProperties.Except(timeStampProperties); var properties = allPropertiesExceptTimeStamp.Where(a => a.Relational().ComputedColumnSql == 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.Relational().ColumnName.Replace("]", "]]")); // square brackets have to be escaped ColumnNameContainsSquareBracket = allPropertiesExceptTimeStamp.Concat(timeStampProperties).Any(a => a.Relational().ColumnName.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); } } } } UpdateByPropertiesAreNullable = properties.Any(a => 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 specifed, 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.Relational().ColumnName.Replace("]", "]]")); } else { PropertyColumnNamesDict = properties.ToDictionary(a => a.Name, b => b.Relational().ColumnName.Replace("]", "]]")); ShadowProperties = new HashSet <string>(properties.Where(p => p.IsShadowProperty).Select(p => p.Relational().ColumnName)); foreach (var property in properties.Where(p => p.GetValueConverter() != null)) { string columnName = property.Relational().ColumnName; ValueConverter converter = property.GetValueConverter(); ConvertibleProperties.Add(columnName, converter); } if (HasOwnedTypes) // Support owned entity property update. TODO: Optimize { foreach (var navgationProperty in ownedTypes) { var property = navgationProperty.PropertyInfo; 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>(); foreach (var ownedEntityProperty in ownedEntityProperties) { if (!ownedEntityProperty.IsPrimaryKey()) { string columnName = ownedEntityProperty.Relational().ColumnName; ownedEntityPropertyNameColumnNameDict.Add(ownedEntityProperty.Name, columnName); } } 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 void LoadData <T>(DbContext context, bool loadOnlyPKColumn) { var entityType = context.Model.FindEntityType(typeof(T)); if (entityType == null) { throw new InvalidOperationException("DbContext does not contain EntitySet for Type: " + typeof(T).Name); } var relationalData = entityType.Relational(); Schema = relationalData.Schema ?? "dbo"; TableName = relationalData.TableName; TempTableSufix = "Temp" + 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; PrimaryKeys = AreSpecifiedUpdateByProperties ? BulkConfig.UpdateByProperties : entityType.FindPrimaryKey().Properties.Select(a => a.Name).ToList(); HasSinglePrimaryKey = PrimaryKeys.Count == 1; var allProperties = entityType.GetProperties().AsEnumerable(); var allNavigationProperties = entityType.GetNavigations().Where(a => a.GetTargetType().IsOwned()); HasOwnedTypes = allNavigationProperties.Any(); // timestamp datatype can only be set by database, that's property having [Timestamp] Attribute but keep if one with [ConcurrencyCheck] var timeStampProperties = allProperties.Where(a => a.IsConcurrencyToken == true && a.ValueGenerated == ValueGenerated.OnAddOrUpdate && a.BeforeSaveBehavior == PropertySaveBehavior.Ignore); TimeStampColumn = timeStampProperties.FirstOrDefault()?.Relational().ColumnName; // expected to be only One var properties = allProperties.Except(timeStampProperties); 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); } } } } UpdateByPropertiesAreNullable = properties.Any(a => 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 specifed, 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.Relational().ColumnName); } else { PropertyColumnNamesDict = properties.ToDictionary(a => a.Name, b => b.Relational().ColumnName); ShadowProperties = new HashSet <string>(properties.Where(p => p.IsShadowProperty).Select(p => p.Relational().ColumnName)); foreach (var property in properties.Where(p => p.GetValueConverter() != null)) { ConvertibleProperties.Add(property.Relational().ColumnName, property.GetValueConverter()); } } }