Skip to content

Commit 49920ca

Browse files
committed
EFCore 3 Release
1 parent d610273 commit 49920ca

File tree

6 files changed

+138
-48
lines changed

6 files changed

+138
-48
lines changed

EFCore.BulkExtensions.Tests/EFCoreBatchTest.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ public void BatchTest(DbServer databaseType)
2121
RunInsert();
2222
RunBatchUpdate();
2323
RunBatchDelete();
24-
RunContainsBatchDelete();
24+
//RunContainsBatchDelete(); // currently not supported for EFCore 3.0
2525

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

44-
//context.Items.BatchDelete(); // TODO: Use after BatchDelete gets implemented for v3.0
44+
context.Items.BatchDelete();
4545
context.BulkDelete(context.Items.ToList());
4646

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

6464
decimal price = 0;
6565
var query = context.Items.Where(a => a.ItemId <= 500 && a.Price >= price);
66-
query.BatchUpdate(new Item { Description = "Updated", Price = 1.5m }/*, updateColumns*/);
66+
67+
var parametersDict = new Dictionary<string, object> // is used to fix issue of getting Query Parameters in .NetCore 3.0
68+
{
69+
{ nameof(price), price }
70+
};
71+
72+
query.BatchUpdate(new Item { Description = "Updated", Price = 1.5m }/*, updateColumns*/, parametersDict: parametersDict);
6773

6874
var incrementStep = 100;
6975
var suffix = " Concatenated";
70-
query.BatchUpdate(a => new Item { Name = a.Name + suffix, Quantity = a.Quantity + incrementStep }); // example of BatchUpdate Increment/Decrement value in variable
76+
query.BatchUpdate(a => new Item { Name = a.Name + suffix, Quantity = a.Quantity + incrementStep }, parametersDict); // example of BatchUpdate Increment/Decrement value in variable
7177
//query.BatchUpdate(a => new Item { Quantity = a.Quantity + 100 }); // example direct value without variable
7278
}
7379
}
@@ -104,7 +110,7 @@ private void RunBatchDelete()
104110
}
105111
}
106112

107-
private void RunContainsBatchDelete()
113+
private void RunContainsBatchDelete() // currently not supported for EFCore 3.0
108114
{
109115
var descriptionsToDelete = new List<string> { "info" };
110116
using (var context = new TestContext(ContextUtil.GetOptions()))

EFCore.BulkExtensions.Tests/EFCoreBatchTestAsync.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public async Task BatchTestAsync(DbServer databaseType)
2525

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

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

92-
await query.BatchUpdateAsync(a => new Item { Name = a.Name + " Concatenated", Quantity = a.Quantity + 100, Price = null }); // example of BatchUpdate value Increment/Decrement
91+
var parametersDict = new Dictionary<string, object> // is used to fix issue of getting Query Parameters in .NetCore 3.0
92+
{
93+
{ nameof(price), price }
94+
};
95+
96+
await query.BatchUpdateAsync(new Item { Description = "Updated" }/*, updateColumns*/, parametersDict: parametersDict);
97+
98+
await query.BatchUpdateAsync(a => new Item { Name = a.Name + " Concatenated", Quantity = a.Quantity + 100, Price = null }, parametersDict); // example of BatchUpdate value Increment/Decrement
9399
}
94100
}
95101

EFCore.BulkExtensions/BatchUtil.cs

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
using System.Linq.Expressions;
1111
using System.Reflection;
1212
using System.Text;
13+
using System.Threading;
14+
using System.Threading.Tasks;
1315

1416
namespace EFCore.BulkExtensions
1517
{
16-
static class BatchUtil
18+
public static class BatchUtil
1719
{
1820
// In comment are Examples of how SqlQuery is changed for Sql Batch
1921

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

3136
innerParameters = ReloadSqlParameters(context, innerParameters.ToList()); // Sqlite requires SqliteParameters
3237
tableAlias = (GetDatabaseType(context) == DbServer.SqlServer) ? $"[{tableAlias}]" : tableAlias;
@@ -42,9 +47,12 @@ public static (string, List<object>) GetSqlDelete<T>(IQueryable<T> query, DbCont
4247
// UPDATE [a] SET [UpdateColumns] = N'updateValues'
4348
// FROM [Table] AS [a]
4449
// WHERE [a].[Columns] = FilterValues
45-
public static (string, List<object>) GetSqlUpdate<T>(IQueryable<T> query, DbContext context, T updateValues, List<string> updateColumns) where T : class, new()
50+
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()
4651
{
4752
(string sql, string tableAlias, string tableAliasSufixAs, IEnumerable<object> innerParameters) = GetBatchSql(query, context, isUpdate: true);
53+
int paramsIndex = 0;
54+
if (parametersDict != null)
55+
innerParameters = parametersDict.Select(a => new SqlParameter($"@__{a.Key}_{paramsIndex++}", a.Value));
4856
var sqlParameters = new List<object>(innerParameters);
4957

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

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

101111
public static (string, string, string, IEnumerable<object>) GetBatchSql<T>(IQueryable<T> query, DbContext context, bool isUpdate) where T : class
102112
{
103-
string sqlQuery = query.ToSql();
104-
IEnumerable<object> innerParameters = new List<object>();
105-
if (!sqlQuery.Contains(" IN (")) // ToParametrizedSql does not work correctly with Contains that is translated to sql IN command
113+
string sqlQuery;
114+
try
115+
{
116+
sqlQuery = query.ToSql();
117+
}
118+
catch (Exception ex)
106119
{
107-
(sqlQuery, innerParameters) = query.ToParametrizedSql();
120+
if (ex.Message.StartsWith("Unable to cast object"))
121+
throw new NotSupportedException($"Query with 'Contains' not currently supported on .NetCore 3. ({ex.Message})");
122+
else
123+
throw ex;
124+
// Unable to cast object of type 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlParameterExpression'
125+
// to type 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlConstantExpression'.
108126
}
109127

128+
IEnumerable<object> innerParameters = new List<object>();
129+
/*if (!sqlQuery.Contains(" IN (")) // DEPRECATED (EFCore 2) ToParametrizedSql does not work correctly with Contains that is translated to sql IN command
130+
{
131+
//(sqlQuery, innerParameters) = query.ToParametrizedSql();
132+
}*/
133+
110134
DbServer databaseType = GetDatabaseType(context);
111135
string tableAlias = "";
112136
string tableAliasSufixAs = "";
@@ -126,7 +150,7 @@ public static (string, string, string, IEnumerable<object>) GetBatchSql<T>(IQuer
126150
int indexPrefixFROM = sql.IndexOf(Environment.NewLine, 1); // skip NewLine from start of string
127151
tableAlias = sql.Substring(7, indexPrefixFROM - 14); // get name of table: "TableName"
128152
sql = sql.Substring(indexPrefixFROM, sql.Length - indexPrefixFROM); // remove segment: FROM "TableName" AS "a"
129-
tableAliasSufixAs = " AS " + sql.Substring(8 , 3) + " ";
153+
tableAliasSufixAs = " AS " + sql.Substring(8, 3) + " ";
130154
}
131155

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

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

317341
}
342+
343+
internal static int ExecuteSql(DbContext dbContext, string sql, List<object> sqlParameters)
344+
{
345+
try
346+
{
347+
return dbContext.Database.ExecuteSqlRaw(sql, sqlParameters);
348+
}
349+
catch (Exception ex)
350+
{
351+
if (ex.Message.Contains("Must declare the scalar variable"))
352+
throw new InvalidOperationException($"{ParametersDictNotFoundMessage} SourceMessage: {ex.Message})");
353+
else
354+
throw ex;
355+
}
356+
}
357+
358+
internal static async Task<int> ExecuteSqlAsync(DbContext dbContext, string sql, List<object> sqlParameters, CancellationToken cancellationToken = default)
359+
{
360+
try
361+
{
362+
return await dbContext.Database.ExecuteSqlRawAsync(sql, sqlParameters, cancellationToken).ConfigureAwait(false);
363+
}
364+
catch (Exception ex)
365+
{
366+
if (ex.Message.Contains("Must declare the scalar variable"))
367+
throw new InvalidOperationException($"{ParametersDictNotFoundMessage} SourceMessage: {ex.Message})");
368+
else
369+
throw ex;
370+
}
371+
}
372+
373+
internal static string ParametersDictNotFoundMessage => "For Query with parameterized variable BatchOperation requires argument 'parametersDict' (Name and Value). Example in library ReadMe.";
318374
}
319375
}

EFCore.BulkExtensions/EFCore.BulkExtensions.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
<PropertyGroup>
44
<TargetFramework>netstandard2.1</TargetFramework>
55
<Title>EFCore.BulkExtensions</Title>
6-
<Version>3.0.0-rc</Version>
6+
<Version>3.0.0</Version>
77
<Authors>borisdj</Authors>
88
<Description>EntityFramework EF Core Bulk Batch Extensions for Insert Update Delete and Read (CRUD) operations on SQL Server and SQLite</Description>
99
<PackageProjectUrl>https://github.com/borisdj/EFCore.BulkExtensions</PackageProjectUrl>
1010
<PackageIconUrl>https://raw.githubusercontent.com/borisdj/EFCore.BulkExtensions/master/logo.png</PackageIconUrl>
1111
<Company />
1212
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1313
<PackageTags>EntityFrameworkCore Entity Framework Core EFCore EF Core Bulk Batch Extensions Insert Update Delete Read</PackageTags>
14-
<PackageReleaseNotes>Target switch to .NetStandard 2.1 using .NetCore 3.0</PackageReleaseNotes>
14+
<PackageReleaseNotes>.NetCore 3.0 RTM</PackageReleaseNotes>
1515
<AssemblyVersion>3.0.0.0</AssemblyVersion>
1616
<FileVersion>3.0.0.0</FileVersion>
1717
</PropertyGroup>

0 commit comments

Comments
 (0)