Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 2.0 #45

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions EntityFramework.Utilities/EntityFramework.Utilities/App.config
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/>

<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --></configSections>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
<parameters>
<parameter value="Data Source=.\SQLEXPRESS; Integrated Security=True; MultipleActiveResultSets=True" />
<parameter value="Data Source=.\SQLEXPRESS; Integrated Security=True; MultipleActiveResultSets=True"/>
</parameters>
</defaultConnectionFactory>
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer"/>
</providers>
</entityFramework>
</configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/></startup></configuration>
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ public class ColumnMapping
public string DataType { get; set; }

public bool IsPrimaryKey { get; set; }

public bool IsStoreGenerated { get; set; }
}
}
189 changes: 141 additions & 48 deletions EntityFramework.Utilities/EntityFramework.Utilities/EFBatchOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,24 @@
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace EntityFramework.Utilities
{

public interface IEFBatchOperationBase<TContext, T> where T : class
{
IEFBatchOperationFiltered<TContext, T> Where(Expression<Func<T, bool>> predicate);

/// <summary>
/// Bulk insert all items if the Provider supports it. Otherwise it will use the default insert unless Configuration.DisableDefaultFallback is set to true in which case it would throw an exception.
/// </summary>
/// <param name="items">The items to insert</param>
/// <param name="connection">The DbConnection to use for the insert. Only needed when for example a profiler wraps the connection. Then you need to provide a connection of the type the provider use.</param>
/// <param name="batchSize">The size of each batch. Default depends on the provider. SqlProvider uses 15000 as default</param>
void InsertAll<TEntity>(IEnumerable<TEntity> items, DbConnection connection = null, int? batchSize = null) where TEntity : class, T;
IEFBatchOperationFiltered<TContext, T> Where(Expression<Func<T, bool>> predicate);
void InsertAll<TEntity>(IEnumerable<TEntity> items, DbConnection connection = null, int? batchSize = null) where TEntity : class, T;
void InsertAll<TEntity>(IEnumerable<TEntity> items, BulkSettings settings) where TEntity : class, T;
Task InsertAllAsync<TEntity>(IEnumerable<TEntity> items, BulkSettings settings = null) where TEntity : class, T;


/// <summary>
Expand All @@ -34,6 +38,19 @@ public interface IEFBatchOperationBase<TContext, T> where T : class
/// <param name="connection">The DbConnection to use for the insert. Only needed when for example a profiler wraps the connection. Then you need to provide a connection of the type the provider use.</param>
/// <param name="batchSize">The size of each batch. Default depends on the provider. SqlProvider uses 15000 as default</param>
void UpdateAll<TEntity>(IEnumerable<TEntity> items, Action<UpdateSpecification<TEntity>> updateSpecification, DbConnection connection = null, int? batchSize = null) where TEntity : class, T;

Task UpdateAllAsync<TEntity>(IEnumerable<TEntity> items, Action<UpdateSpecification<TEntity>> updateSpecification, BulkSettings settings = null) where TEntity : class, T;

}

public class BulkSettings
{
public int? BatchSize { get; set; }
public DbConnection Connection { get; set; }
/// <summary>
/// If set to true Database generated Id's will be returned and populate the entities. The insert takes about 2-3x longer time with this enabled.
/// </summary>
public bool ReturnIdsOnInsert { get; set; }
}

public class UpdateSpecification<T>
Expand Down Expand Up @@ -66,7 +83,7 @@ public static IEFBatchOperationBase<TContext, T> For<TContext, T>(TContext conte
return EFBatchOperation<TContext, T>.For(context, set);
}
}
public class EFBatchOperation<TContext, T> : IEFBatchOperationBase<TContext, T>, IEFBatchOperationFiltered<TContext, T>
public class EFBatchOperation<TContext, T> : IEFBatchOperationBase<TContext, T>, IEFBatchOperationFiltered<TContext, T>
where T : class
where TContext : DbContext
{
Expand Down Expand Up @@ -97,36 +114,33 @@ public static IEFBatchOperationBase<TContext, T> For<TContext, T>(TContext conte
/// <param name="batchSize">The size of each batch. Default depends on the provider. SqlProvider uses 15000 as default</param>
public void InsertAll<TEntity>(IEnumerable<TEntity> items, DbConnection connection = null, int? batchSize = null) where TEntity : class, T
{
InsertAll(items, new BulkSettings { Connection = connection, BatchSize = batchSize });
}

public void InsertAll<TEntity>(IEnumerable<TEntity> items, BulkSettings settings) where TEntity : class, T
{
settings = settings ?? new BulkSettings();
var con = context.Connection as EntityConnection;
if (con == null && connection == null)
if (con == null && settings.Connection == null)
{
Configuration.Log("No provider could be found because the Connection didn't implement System.Data.EntityClient.EntityConnection");
Fallbacks.DefaultInsertAll(context, items);
return;
}

var connectionToUse = connection ?? con.StoreConnection;
var connectionToUse = settings.Connection ?? con.StoreConnection;
var currentType = typeof(TEntity);
var provider = Configuration.Providers.FirstOrDefault(p => p.CanHandle(connectionToUse));
if (provider != null && provider.CanInsert)
{
var tableSpec = GetBulkTableSpec<TEntity>(currentType);

var mapping = EntityFramework.Utilities.EfMappingFactory.GetMappingsForContext(this.dbContext);
var typeMapping = mapping.TypeMappings[typeof(T)];
var tableMapping = typeMapping.TableMappings.First();

var properties = tableMapping.PropertyMappings
.Where(p => currentType.IsSubclassOf(p.ForEntityType) || p.ForEntityType == currentType)
.Select(p => new ColumnMapping { NameInDatabase = p.ColumnName, NameOnObject = p.PropertyName }).ToList();
if (tableMapping.TPHConfiguration != null)
provider.InsertItems(items, tableSpec.TableMapping.Schema, tableSpec.TableMapping.TableName, tableSpec.Properties, new BulkSettings
{
properties.Add(new ColumnMapping
{
NameInDatabase = tableMapping.TPHConfiguration.ColumnName,
StaticValue = tableMapping.TPHConfiguration.Mappings[typeof(TEntity)]
});
}

provider.InsertItems(items, tableMapping.Schema, tableMapping.TableName, properties, connectionToUse, batchSize);
Connection = connectionToUse,
BatchSize = settings.BatchSize,
ReturnIdsOnInsert = settings.ReturnIdsOnInsert
});
}
else
{
Expand All @@ -135,6 +149,32 @@ public void InsertAll<TEntity>(IEnumerable<TEntity> items, DbConnection connecti
}
}

public async Task InsertAllAsync<TEntity>(IEnumerable<TEntity> items, BulkSettings settings = null) where TEntity : class, T
{
settings = settings ?? new BulkSettings();
var con = context.Connection as EntityConnection;
if (con == null && settings.Connection == null)
{
throw new InvalidOperationException("No provider supporting the InsertAll operation for this datasource was found, InsertAllAsync does not support fallbacks.");
}

var connectionToUse = settings.Connection ?? con.StoreConnection;
var currentType = typeof(TEntity);
var provider = Configuration.Providers.FirstOrDefault(p => p.CanHandle(connectionToUse));
if (provider == null || !provider.CanInsert)
{
throw new InvalidOperationException("No provider supporting the InsertAll operation for this datasource was found, InsertAllAsync does not support fallbacks.");
}

var tableSpec = GetBulkTableSpec<TEntity>(currentType);

await provider.InsertItemsAsync(items, tableSpec.TableMapping.Schema, tableSpec.TableMapping.TableName, tableSpec.Properties, new BulkSettings
{
Connection = connectionToUse,
BatchSize = settings.BatchSize,
ReturnIdsOnInsert = settings.ReturnIdsOnInsert
});
}

public void UpdateAll<TEntity>(IEnumerable<TEntity> items, Action<UpdateSpecification<TEntity>> updateSpecification, DbConnection connection = null, int? batchSize = null) where TEntity : class, T
{
Expand All @@ -143,46 +183,101 @@ public void UpdateAll<TEntity>(IEnumerable<TEntity> items, Action<UpdateSpecific
{
Configuration.Log("No provider could be found because the Connection didn't implement System.Data.EntityClient.EntityConnection");
Fallbacks.DefaultInsertAll(context, items);
return;
}

var connectionToUse = connection ?? con.StoreConnection;
var currentType = typeof(TEntity);
var provider = Configuration.Providers.FirstOrDefault(p => p.CanHandle(connectionToUse));
if (provider != null && provider.CanBulkUpdate)
{

var mapping = EntityFramework.Utilities.EfMappingFactory.GetMappingsForContext(this.dbContext);
var typeMapping = mapping.TypeMappings[typeof(T)];
var tableMapping = typeMapping.TableMappings.First();

var properties = tableMapping.PropertyMappings
.Where(p => currentType.IsSubclassOf(p.ForEntityType) || p.ForEntityType == currentType)
.Select(p => new ColumnMapping {
NameInDatabase = p.ColumnName,
NameOnObject = p.PropertyName,
DataType = p.DataType,
IsPrimaryKey = p.IsPrimaryKey
}).ToList();

if (tableMapping.TPHConfiguration != null)
{
properties.Add(new ColumnMapping
{
NameInDatabase = tableMapping.TPHConfiguration.ColumnName,
StaticValue = tableMapping.TPHConfiguration.Mappings[typeof(TEntity)]
});
}
var tableSpec = GetBulkTableSpec<TEntity>(currentType);

var spec = new UpdateSpecification<TEntity>();
updateSpecification(spec);
provider.UpdateItems(items, tableMapping.Schema, tableMapping.TableName, properties, connectionToUse, batchSize, spec);
provider.UpdateItems(items, tableSpec.TableMapping.Schema, tableSpec.TableMapping.TableName, tableSpec.Properties, connectionToUse, batchSize, spec);
}
else
{
Configuration.Log("Found provider: " + (provider == null ? "[]" : provider.GetType().Name) + " for " + connectionToUse.GetType().Name);
Fallbacks.DefaultInsertAll(context, items);
}
}

public async Task UpdateAllAsync<TEntity>(IEnumerable<TEntity> items, Action<UpdateSpecification<TEntity>> updateSpecification, BulkSettings settings = null) where TEntity : class, T
{

settings = settings ?? new BulkSettings();
var con = context.Connection as EntityConnection;
if (con == null && settings.Connection == null)
{
throw new InvalidOperationException("No provider supporting the UpdateAll operation for this datasource was found, UpdateAllAsync does not support fallbacks.");
}

var connectionToUse = settings.Connection ?? con.StoreConnection;
var currentType = typeof(TEntity);
var provider = Configuration.Providers.FirstOrDefault(p => p.CanHandle(connectionToUse));
if (provider == null || !provider.CanBulkUpdate)
{
throw new InvalidOperationException("No provider supporting the UpdateAll operation for this datasource was found, UpdateAllAsync does not support fallbacks.");
}

var tableSpec = GetBulkTableSpec<TEntity>(currentType);

var spec = new UpdateSpecification<TEntity>();
updateSpecification(spec);
settings.Connection = connectionToUse;
await provider.UpdateItemsAsync(items, tableSpec.TableMapping.Schema, tableSpec.TableMapping.TableName, tableSpec.Properties, settings, spec);

}

private class BulkTableSpec
{
public List<ColumnMapping> Properties { get; set; }
public TableMapping TableMapping { get; set; }
public TypeMapping TypeMapping { get; set; }
}


private BulkTableSpec GetBulkTableSpec<TEntity>(Type currentType) where TEntity : class, T
{
var mapping = EntityFramework.Utilities.EfMappingFactory.GetMappingsForContext(this.dbContext);
var typeMapping = mapping.TypeMappings[typeof(T)];
var tableMapping = typeMapping.TableMappings.First();

var properties = GetProperties(currentType, tableMapping);

if (tableMapping.TPHConfiguration != null)
{
properties.Add(new ColumnMapping
{
NameInDatabase = tableMapping.TPHConfiguration.ColumnName,
StaticValue = tableMapping.TPHConfiguration.Mappings[typeof(TEntity)]
});
}

return new BulkTableSpec
{
Properties = properties,
TableMapping = tableMapping,
TypeMapping = typeMapping,
};
}

private static List<ColumnMapping> GetProperties(Type currentType, TableMapping tableMapping)
{
var properties = tableMapping.PropertyMappings
.Where(p => currentType.IsSubclassOf(p.ForEntityType) || p.ForEntityType == currentType)
.Select(p => new ColumnMapping
{
NameInDatabase = p.ColumnName,
NameOnObject = p.PropertyName,
DataType = p.DataType,
IsPrimaryKey = p.IsPrimaryKey,
IsStoreGenerated = p.IsStoreGenerated,
}).ToList();
return properties;
}

public IEFBatchOperationFiltered<TContext, T> Where(Expression<Func<T, bool>> predicate)
{
Expand Down Expand Up @@ -212,7 +307,7 @@ public int Delete()
}
else
{
Configuration.Log("Found provider: " + (provider == null ? "[]" : provider.GetType().Name ) + " for " + con.StoreConnection.GetType().Name);
Configuration.Log("Found provider: " + (provider == null ? "[]" : provider.GetType().Name) + " for " + con.StoreConnection.GetType().Name);
return Fallbacks.DefaultDelete(context, this.predicate);
}
}
Expand Down Expand Up @@ -240,7 +335,7 @@ public int Update<TP>(Expression<Func<T, TP>> prop, Expression<Func<T, TP>> modi
var mqueryInfo = provider.GetQueryInformation<T>(mquery);

var update = provider.GetUpdateQuery(queryInformation, mqueryInfo);

var parameters = query.Parameters
.Concat(mquery.Parameters)
.Select(p => new SqlParameter { Value = p.Value, ParameterName = p.Name })
Expand All @@ -255,7 +350,5 @@ public int Update<TP>(Expression<Func<T, TP>> prop, Expression<Func<T, TP>> modi
}
}



}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
Expand All @@ -9,8 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>EntityFramework.Utilities</RootNamespace>
<AssemblyName>EntityFramework.Utilities</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand All @@ -20,6 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
Expand All @@ -28,6 +30,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
Expand Down Expand Up @@ -65,6 +68,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="QueryInformation.cs" />
<Compile Include="SqlQueryProvider.cs" />
<Compile Include="SqlServerTSQLGenerator.cs" />
<Compile Include="SqlStringHelper.cs" />
<Compile Include="UpdateSpec.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,24 @@ internal static Action<T, TP> PropertyExpressionToSetter<T, TP>(Expression<Func<
// compile it
return set.Compile();
}

internal static Action<T, object> PropertyNameToSetter<T>(string propertyName)
{
var method = typeof(T).GetProperty(propertyName).GetSetMethod();

var obj = Expression.Parameter(typeof(T), "o");
var value = Expression.Parameter(typeof(object));

Expression<Action<T, object>> expr =
Expression.Lambda<Action<T, object>>(
Expression.Call(
Expression.Convert(obj, method.DeclaringType),
method,
Expression.Convert(value, method.GetParameters()[0].ParameterType)),
obj,
value);

return expr.Compile();
}
}
}
Loading