Skip to content

Commit

Permalink
EFCore 3 Release
Browse files Browse the repository at this point in the history
  • Loading branch information
borisdj committed Oct 4, 2019
1 parent d610273 commit 49920ca
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 48 deletions.
18 changes: 12 additions & 6 deletions EFCore.BulkExtensions.Tests/EFCoreBatchTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ public void BatchTest(DbServer databaseType)
RunInsert();
RunBatchUpdate();
RunBatchDelete();
RunContainsBatchDelete();
//RunContainsBatchDelete(); // currently not supported for EFCore 3.0

using (var context = new TestContext(ContextUtil.GetOptions()))
{
var lastItem = context.Items.LastOrDefault();
var lastItem = context.Items.ToList().LastOrDefault();
Assert.Equal(500, lastItem.ItemId);
Assert.Equal("Updated", lastItem.Description);
Assert.Equal(1.5m, lastItem.Price);
Expand All @@ -41,7 +41,7 @@ internal void RunDeleteAll(DbServer databaseType)
context.Items.Add(new Item { }); // used for initial add so that after RESEED it starts from 1, not 0
context.SaveChanges();

//context.Items.BatchDelete(); // TODO: Use after BatchDelete gets implemented for v3.0
context.Items.BatchDelete();
context.BulkDelete(context.Items.ToList());

if (databaseType == DbServer.SqlServer)
Expand All @@ -63,11 +63,17 @@ private void RunBatchUpdate()

decimal price = 0;
var query = context.Items.Where(a => a.ItemId <= 500 && a.Price >= price);
query.BatchUpdate(new Item { Description = "Updated", Price = 1.5m }/*, updateColumns*/);

var parametersDict = new Dictionary<string, object> // is used to fix issue of getting Query Parameters in .NetCore 3.0
{
{ nameof(price), price }
};

query.BatchUpdate(new Item { Description = "Updated", Price = 1.5m }/*, updateColumns*/, parametersDict: parametersDict);

var incrementStep = 100;
var suffix = " Concatenated";
query.BatchUpdate(a => new Item { Name = a.Name + suffix, Quantity = a.Quantity + incrementStep }); // example of BatchUpdate Increment/Decrement value in variable
query.BatchUpdate(a => new Item { Name = a.Name + suffix, Quantity = a.Quantity + incrementStep }, parametersDict); // example of BatchUpdate Increment/Decrement value in variable
//query.BatchUpdate(a => new Item { Quantity = a.Quantity + 100 }); // example direct value without variable
}
}
Expand Down Expand Up @@ -104,7 +110,7 @@ private void RunBatchDelete()
}
}

private void RunContainsBatchDelete()
private void RunContainsBatchDelete() // currently not supported for EFCore 3.0
{
var descriptionsToDelete = new List<string> { "info" };
using (var context = new TestContext(ContextUtil.GetOptions()))
Expand Down
12 changes: 9 additions & 3 deletions EFCore.BulkExtensions.Tests/EFCoreBatchTestAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public async Task BatchTestAsync(DbServer databaseType)

using (var context = new TestContext(ContextUtil.GetOptions()))
{
var lastItem = context.Items.LastOrDefaultAsync().Result;
var lastItem = (await context.Items.ToListAsync()).Last();
Assert.Equal(500, lastItem.ItemId);
Assert.Equal("Updated", lastItem.Description);
Assert.Null(lastItem.Price);
Expand Down Expand Up @@ -87,9 +87,15 @@ private async Task RunBatchUpdateAsync()

decimal price = 0;
var query = context.Items.Where(a => a.ItemId <= 500 && a.Price >= price);
await query.BatchUpdateAsync(new Item { Description = "Updated" }/*, updateColumns*/);

await query.BatchUpdateAsync(a => new Item { Name = a.Name + " Concatenated", Quantity = a.Quantity + 100, Price = null }); // example of BatchUpdate value Increment/Decrement
var parametersDict = new Dictionary<string, object> // is used to fix issue of getting Query Parameters in .NetCore 3.0
{
{ nameof(price), price }
};

await query.BatchUpdateAsync(new Item { Description = "Updated" }/*, updateColumns*/, parametersDict: parametersDict);

await query.BatchUpdateAsync(a => new Item { Name = a.Name + " Concatenated", Quantity = a.Quantity + 100, Price = null }, parametersDict); // example of BatchUpdate value Increment/Decrement
}
}

Expand Down
82 changes: 69 additions & 13 deletions EFCore.BulkExtensions/BatchUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace EFCore.BulkExtensions
{
static class BatchUtil
public static class BatchUtil
{
// In comment are Examples of how SqlQuery is changed for Sql Batch

Expand All @@ -24,9 +26,12 @@ static class BatchUtil
// DELETE [a]
// FROM [Table] AS [a]
// WHERE [a].[Columns] = FilterValues
public static (string, List<object>) GetSqlDelete<T>(IQueryable<T> query, DbContext context) where T : class
public static (string, List<object>) GetSqlDelete<T>(IQueryable<T> query, DbContext context, Dictionary<string, object> parametersDict) where T : class
{
(string sql, string tableAlias, string tableAliasSufixAs, IEnumerable<object> innerParameters) = GetBatchSql(query, context, isUpdate: false);
int paramsIndex = 0;
if (parametersDict != null)
innerParameters = parametersDict.Select(a => new SqlParameter($"@__{a.Key}_{paramsIndex++}", a.Value));

innerParameters = ReloadSqlParameters(context, innerParameters.ToList()); // Sqlite requires SqliteParameters
tableAlias = (GetDatabaseType(context) == DbServer.SqlServer) ? $"[{tableAlias}]" : tableAlias;
Expand All @@ -42,9 +47,12 @@ public static (string, List<object>) GetSqlDelete<T>(IQueryable<T> query, DbCont
// UPDATE [a] SET [UpdateColumns] = N'updateValues'
// FROM [Table] AS [a]
// WHERE [a].[Columns] = FilterValues
public static (string, List<object>) GetSqlUpdate<T>(IQueryable<T> query, DbContext context, T updateValues, List<string> updateColumns) where T : class, new()
public static (string, List<object>) GetSqlUpdate<T>(IQueryable<T> query, DbContext context, T updateValues, List<string> updateColumns, Dictionary<string, object> parametersDict) where T : class, new()
{
(string sql, string tableAlias, string tableAliasSufixAs, IEnumerable<object> innerParameters) = GetBatchSql(query, context, isUpdate: true);
int paramsIndex = 0;
if (parametersDict != null)
innerParameters = parametersDict.Select(a => new SqlParameter($"@__{a.Key}_{paramsIndex++}", a.Value));
var sqlParameters = new List<object>(innerParameters);

string sqlSET = GetSqlSetSegment(context, updateValues, updateColumns, sqlParameters);
Expand All @@ -62,13 +70,15 @@ public static (string, List<object>) GetSqlDelete<T>(IQueryable<T> query, DbCont
/// <param name="query"></param>
/// <param name="expression"></param>
/// <returns></returns>
public static (string, List<object>) GetSqlUpdate<T>(IQueryable<T> query, Expression<Func<T, T>> expression) where T : class
public static (string, List<object>) GetSqlUpdate<T>(IQueryable<T> query, DbContext context, Expression<Func<T, T>> expression, Dictionary<string, object> parametersDict) where T : class
{
DbContext context = BatchUtil.GetDbContext(query);
(string sql, string tableAlias, string tableAliasSufixAs, IEnumerable<object> innerParameters) = GetBatchSql(query, context, isUpdate: true);
var sqlColumns = new StringBuilder();
int paramsIndex = 0;
if (parametersDict != null)
innerParameters = parametersDict.Select(a => new SqlParameter($"@__{a.Key}_{paramsIndex++}", a.Value));
var sqlParameters = new List<object>(innerParameters);
var columnNameValueDict = TableInfo.CreateInstance(GetDbContext(query), new List<T>(), OperationType.Read, new BulkConfig()).PropertyColumnNamesDict;
var columnNameValueDict = TableInfo.CreateInstance(context, new List<T>(), OperationType.Read, new BulkConfig()).PropertyColumnNamesDict;
var dbType = GetDatabaseType(context);
CreateUpdateBody(columnNameValueDict, tableAlias, expression.Body, dbType, ref sqlColumns, ref sqlParameters);

Expand All @@ -85,7 +95,7 @@ public static List<object> ReloadSqlParameters(DbContext context, List<object> s
if (databaseType == DbServer.Sqlite)
{
var sqlParametersReloaded = new List<object>();
foreach(var parameter in sqlParameters)
foreach (var parameter in sqlParameters)
{
var sqlParameter = (SqlParameter)parameter;
sqlParametersReloaded.Add(new SqliteParameter(sqlParameter.ParameterName, sqlParameter.Value));
Expand All @@ -100,13 +110,27 @@ public static List<object> ReloadSqlParameters(DbContext context, List<object> s

public static (string, string, string, IEnumerable<object>) GetBatchSql<T>(IQueryable<T> query, DbContext context, bool isUpdate) where T : class
{
string sqlQuery = query.ToSql();
IEnumerable<object> innerParameters = new List<object>();
if (!sqlQuery.Contains(" IN (")) // ToParametrizedSql does not work correctly with Contains that is translated to sql IN command
string sqlQuery;
try
{
sqlQuery = query.ToSql();
}
catch (Exception ex)
{
(sqlQuery, innerParameters) = query.ToParametrizedSql();
if (ex.Message.StartsWith("Unable to cast object"))
throw new NotSupportedException($"Query with 'Contains' not currently supported on .NetCore 3. ({ex.Message})");
else
throw ex;
// Unable to cast object of type 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlParameterExpression'
// to type 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlConstantExpression'.
}

IEnumerable<object> innerParameters = new List<object>();
/*if (!sqlQuery.Contains(" IN (")) // DEPRECATED (EFCore 2) ToParametrizedSql does not work correctly with Contains that is translated to sql IN command
{
//(sqlQuery, innerParameters) = query.ToParametrizedSql();
}*/

DbServer databaseType = GetDatabaseType(context);
string tableAlias = "";
string tableAliasSufixAs = "";
Expand All @@ -126,7 +150,7 @@ public static (string, string, string, IEnumerable<object>) GetBatchSql<T>(IQuer
int indexPrefixFROM = sql.IndexOf(Environment.NewLine, 1); // skip NewLine from start of string
tableAlias = sql.Substring(7, indexPrefixFROM - 14); // get name of table: "TableName"
sql = sql.Substring(indexPrefixFROM, sql.Length - indexPrefixFROM); // remove segment: FROM "TableName" AS "a"
tableAliasSufixAs = " AS " + sql.Substring(8 , 3) + " ";
tableAliasSufixAs = " AS " + sql.Substring(8, 3) + " ";
}

return (sql, tableAlias, tableAliasSufixAs, innerParameters);
Expand Down Expand Up @@ -290,7 +314,7 @@ public static DbContext GetDbContext(IQueryable query)
var queryCompiler = typeof(EntityQueryProvider).GetField("_queryCompiler", bindingFlags).GetValue(query.Provider);
var queryContextFactory = queryCompiler.GetType().GetField("_queryContextFactory", bindingFlags).GetValue(queryCompiler);

var dependencies = typeof(RelationalQueryContextFactory).GetProperty("Dependencies", bindingFlags).GetValue(queryContextFactory);
var dependencies = typeof(RelationalQueryContextFactory).GetField("_dependencies", bindingFlags).GetValue(queryContextFactory);
var queryContextDependencies = typeof(DbContext).Assembly.GetType(typeof(QueryContextDependencies).FullName);
var stateManagerProperty = queryContextDependencies.GetProperty("StateManager", bindingFlags | BindingFlags.Public).GetValue(dependencies);
var stateManager = (IStateManager)stateManagerProperty;
Expand All @@ -315,5 +339,37 @@ internal static bool IsStringConcat(BinaryExpression binaryExpression) {
return method.DeclaringType == typeof(string) && method.Name == nameof(string.Concat);

}

internal static int ExecuteSql(DbContext dbContext, string sql, List<object> sqlParameters)
{
try
{
return dbContext.Database.ExecuteSqlRaw(sql, sqlParameters);
}
catch (Exception ex)
{
if (ex.Message.Contains("Must declare the scalar variable"))
throw new InvalidOperationException($"{ParametersDictNotFoundMessage} SourceMessage: {ex.Message})");
else
throw ex;
}
}

internal static async Task<int> ExecuteSqlAsync(DbContext dbContext, string sql, List<object> sqlParameters, CancellationToken cancellationToken = default)
{
try
{
return await dbContext.Database.ExecuteSqlRawAsync(sql, sqlParameters, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
if (ex.Message.Contains("Must declare the scalar variable"))
throw new InvalidOperationException($"{ParametersDictNotFoundMessage} SourceMessage: {ex.Message})");
else
throw ex;
}
}

internal static string ParametersDictNotFoundMessage => "For Query with parameterized variable BatchOperation requires argument 'parametersDict' (Name and Value). Example in library ReadMe.";
}
}
4 changes: 2 additions & 2 deletions EFCore.BulkExtensions/EFCore.BulkExtensions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Title>EFCore.BulkExtensions</Title>
<Version>3.0.0-rc</Version>
<Version>3.0.0</Version>
<Authors>borisdj</Authors>
<Description>EntityFramework EF Core Bulk Batch Extensions for Insert Update Delete and Read (CRUD) operations on SQL Server and SQLite</Description>
<PackageProjectUrl>https://github.com/borisdj/EFCore.BulkExtensions</PackageProjectUrl>
<PackageIconUrl>https://raw.githubusercontent.com/borisdj/EFCore.BulkExtensions/master/logo.png</PackageIconUrl>
<Company />
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageTags>EntityFrameworkCore Entity Framework Core EFCore EF Core Bulk Batch Extensions Insert Update Delete Read</PackageTags>
<PackageReleaseNotes>Target switch to .NetStandard 2.1 using .NetCore 3.0</PackageReleaseNotes>
<PackageReleaseNotes>.NetCore 3.0 RTM</PackageReleaseNotes>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
</PropertyGroup>
Expand Down
Loading

0 comments on commit 49920ca

Please sign in to comment.