10
10
using System . Linq . Expressions ;
11
11
using System . Reflection ;
12
12
using System . Text ;
13
+ using System . Threading ;
14
+ using System . Threading . Tasks ;
13
15
14
16
namespace EFCore . BulkExtensions
15
17
{
16
- static class BatchUtil
18
+ public static class BatchUtil
17
19
{
18
20
// In comment are Examples of how SqlQuery is changed for Sql Batch
19
21
@@ -24,9 +26,12 @@ static class BatchUtil
24
26
// DELETE [a]
25
27
// FROM [Table] AS [a]
26
28
// 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
28
30
{
29
31
( 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 ) ) ;
30
35
31
36
innerParameters = ReloadSqlParameters ( context , innerParameters . ToList ( ) ) ; // Sqlite requires SqliteParameters
32
37
tableAlias = ( GetDatabaseType ( context ) == DbServer . SqlServer ) ? $ "[{ tableAlias } ]" : tableAlias ;
@@ -42,9 +47,12 @@ public static (string, List<object>) GetSqlDelete<T>(IQueryable<T> query, DbCont
42
47
// UPDATE [a] SET [UpdateColumns] = N'updateValues'
43
48
// FROM [Table] AS [a]
44
49
// 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 ( )
46
51
{
47
52
( 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 ) ) ;
48
56
var sqlParameters = new List < object > ( innerParameters ) ;
49
57
50
58
string sqlSET = GetSqlSetSegment ( context , updateValues , updateColumns , sqlParameters ) ;
@@ -62,13 +70,15 @@ public static (string, List<object>) GetSqlDelete<T>(IQueryable<T> query, DbCont
62
70
/// <param name="query"></param>
63
71
/// <param name="expression"></param>
64
72
/// <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
66
74
{
67
- DbContext context = BatchUtil . GetDbContext ( query ) ;
68
75
( string sql , string tableAlias , string tableAliasSufixAs , IEnumerable < object > innerParameters ) = GetBatchSql ( query , context , isUpdate : true ) ;
69
76
var sqlColumns = new StringBuilder ( ) ;
77
+ int paramsIndex = 0 ;
78
+ if ( parametersDict != null )
79
+ innerParameters = parametersDict . Select ( a => new SqlParameter ( $ "@__{ a . Key } _{ paramsIndex ++ } ", a . Value ) ) ;
70
80
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 ;
72
82
var dbType = GetDatabaseType ( context ) ;
73
83
CreateUpdateBody ( columnNameValueDict , tableAlias , expression . Body , dbType , ref sqlColumns , ref sqlParameters ) ;
74
84
@@ -85,7 +95,7 @@ public static List<object> ReloadSqlParameters(DbContext context, List<object> s
85
95
if ( databaseType == DbServer . Sqlite )
86
96
{
87
97
var sqlParametersReloaded = new List < object > ( ) ;
88
- foreach ( var parameter in sqlParameters )
98
+ foreach ( var parameter in sqlParameters )
89
99
{
90
100
var sqlParameter = ( SqlParameter ) parameter ;
91
101
sqlParametersReloaded . Add ( new SqliteParameter ( sqlParameter . ParameterName , sqlParameter . Value ) ) ;
@@ -100,13 +110,27 @@ public static List<object> ReloadSqlParameters(DbContext context, List<object> s
100
110
101
111
public static ( string , string , string , IEnumerable < object > ) GetBatchSql < T > ( IQueryable < T > query , DbContext context , bool isUpdate ) where T : class
102
112
{
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 )
106
119
{
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'.
108
126
}
109
127
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
+
110
134
DbServer databaseType = GetDatabaseType ( context ) ;
111
135
string tableAlias = "" ;
112
136
string tableAliasSufixAs = "" ;
@@ -126,7 +150,7 @@ public static (string, string, string, IEnumerable<object>) GetBatchSql<T>(IQuer
126
150
int indexPrefixFROM = sql . IndexOf ( Environment . NewLine , 1 ) ; // skip NewLine from start of string
127
151
tableAlias = sql . Substring ( 7 , indexPrefixFROM - 14 ) ; // get name of table: "TableName"
128
152
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 ) + " " ;
130
154
}
131
155
132
156
return ( sql , tableAlias , tableAliasSufixAs , innerParameters ) ;
@@ -290,7 +314,7 @@ public static DbContext GetDbContext(IQueryable query)
290
314
var queryCompiler = typeof ( EntityQueryProvider ) . GetField ( "_queryCompiler" , bindingFlags ) . GetValue ( query . Provider ) ;
291
315
var queryContextFactory = queryCompiler . GetType ( ) . GetField ( "_queryContextFactory" , bindingFlags ) . GetValue ( queryCompiler ) ;
292
316
293
- var dependencies = typeof ( RelationalQueryContextFactory ) . GetProperty ( "Dependencies ", bindingFlags ) . GetValue ( queryContextFactory ) ;
317
+ var dependencies = typeof ( RelationalQueryContextFactory ) . GetField ( "_dependencies ", bindingFlags ) . GetValue ( queryContextFactory ) ;
294
318
var queryContextDependencies = typeof ( DbContext ) . Assembly . GetType ( typeof ( QueryContextDependencies ) . FullName ) ;
295
319
var stateManagerProperty = queryContextDependencies . GetProperty ( "StateManager" , bindingFlags | BindingFlags . Public ) . GetValue ( dependencies ) ;
296
320
var stateManager = ( IStateManager ) stateManagerProperty ;
@@ -315,5 +339,37 @@ internal static bool IsStringConcat(BinaryExpression binaryExpression) {
315
339
return method . DeclaringType == typeof ( string ) && method . Name == nameof ( string . Concat ) ;
316
340
317
341
}
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." ;
318
374
}
319
375
}
0 commit comments