Skip to content
This repository was archived by the owner on Feb 1, 2025. It is now read-only.

Commit 3782257

Browse files
committed
Merge branch 'master' into release
2 parents 821914a + b8af7d7 commit 3782257

27 files changed

+366
-35
lines changed

Source/LinqToDB.EntityFrameworkCore/EFCoreMetadataReader.cs

Lines changed: 175 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
using System;
2+
using System.Collections.Concurrent;
23
using System.Collections.Generic;
34
using System.Linq;
5+
using System.Linq.Expressions;
46
using System.Reflection;
5-
7+
using LinqToDB.Common;
8+
using LinqToDB.EntityFrameworkCore.Internal;
9+
using LinqToDB.SqlQuery;
610
using Microsoft.EntityFrameworkCore;
11+
using Microsoft.EntityFrameworkCore.Internal;
712
using Microsoft.EntityFrameworkCore.Metadata;
13+
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
814
using Microsoft.EntityFrameworkCore.Metadata.Internal;
15+
using Microsoft.EntityFrameworkCore.Query.Expressions;
16+
using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors;
917

1018
namespace LinqToDB.EntityFrameworkCore
1119
{
@@ -19,10 +27,13 @@ namespace LinqToDB.EntityFrameworkCore
1927
internal class EFCoreMetadataReader : IMetadataReader
2028
{
2129
readonly IModel _model;
30+
private readonly SqlTranslatingExpressionVisitorDependencies _dependencies;
31+
private readonly ConcurrentDictionary<MemberInfo, EFCoreExpressionAttribute> _calculatedExtensions = new ConcurrentDictionary<MemberInfo, EFCoreExpressionAttribute>();
2232

23-
public EFCoreMetadataReader(IModel model)
33+
public EFCoreMetadataReader(IModel model, SqlTranslatingExpressionVisitorDependencies dependencies)
2434
{
2535
_model = model;
36+
_dependencies = dependencies;
2637
}
2738

2839
public T[] GetAttributes<T>(Type type, bool inherit = true) where T : Attribute
@@ -133,6 +144,25 @@ public T[] GetAttributes<T>(Type type, MemberInfo memberInfo, bool inherit = tru
133144

134145
if (typeof(T) == typeof(Sql.ExpressionAttribute))
135146
{
147+
// Search for translator first
148+
if (_dependencies != null)
149+
{
150+
if (memberInfo.IsMethodEx())
151+
{
152+
var methodInfo = (MethodInfo) memberInfo;
153+
var func = GetDbFunctionFromMethodCall(type, methodInfo);
154+
if (func != null)
155+
return new T[] { (T) (Attribute) func };
156+
}
157+
else if (memberInfo.IsPropertyEx())
158+
{
159+
var propertyInfo = (PropertyInfo) memberInfo;
160+
var func = GetDbFunctionFromProperty(type, propertyInfo);
161+
if (func != null)
162+
return new T[] { (T) (Attribute) func };
163+
}
164+
}
165+
136166
if (memberInfo.IsMethodEx())
137167
{
138168
var method = (MethodInfo) memberInfo;
@@ -160,6 +190,149 @@ public T[] GetAttributes<T>(Type type, MemberInfo memberInfo, bool inherit = tru
160190
return Array.Empty<T>();
161191
}
162192

193+
private Sql.ExpressionAttribute GetDbFunctionFromMethodCall(Type type, MethodInfo methodInfo)
194+
{
195+
if (_dependencies == null || _model == null)
196+
return null;
197+
198+
methodInfo = (MethodInfo) type.GetMemberEx(methodInfo) ?? methodInfo;
199+
200+
var found = _calculatedExtensions.GetOrAdd(methodInfo, mi =>
201+
{
202+
EFCoreExpressionAttribute result = null;
203+
204+
if (!methodInfo.IsGenericMethodDefinition && !mi.GetCustomAttributes<Sql.ExpressionAttribute>().Any())
205+
{
206+
var objExpr = Expression.Constant(DefaultValue.GetValue(type), type);
207+
var parameterInfos = methodInfo.GetParameters();
208+
var parametersArray = parameterInfos
209+
.Select(p =>
210+
(Expression) Expression.Constant(DefaultValue.GetValue(p.ParameterType), p.ParameterType)).ToArray();
211+
212+
var mcExpr = Expression.Call(methodInfo.IsStatic ? null : objExpr, methodInfo, parametersArray);
213+
214+
var newExpression = _dependencies.MethodCallTranslator.Translate(mcExpr, _model);
215+
if (newExpression != null && newExpression != mcExpr)
216+
{
217+
if (!methodInfo.IsStatic)
218+
parametersArray = new Expression[] { objExpr }.Concat(parametersArray).ToArray();
219+
220+
result = ConvertToExpressionAttribute(methodInfo, newExpression, parametersArray);
221+
}
222+
}
223+
224+
return result;
225+
});
226+
227+
return found;
228+
}
229+
230+
private Sql.ExpressionAttribute GetDbFunctionFromProperty(Type type, PropertyInfo propInfo)
231+
{
232+
if (_dependencies == null || _model == null)
233+
return null;
234+
235+
propInfo = (PropertyInfo) type.GetMemberEx(propInfo) ?? propInfo;
236+
237+
var found = _calculatedExtensions.GetOrAdd(propInfo, mi =>
238+
{
239+
EFCoreExpressionAttribute result = null;
240+
241+
if ((propInfo.GetMethod?.IsStatic != true) && !mi.GetCustomAttributes<Sql.ExpressionAttribute>().Any())
242+
{
243+
var objExpr = Expression.Constant(DefaultValue.GetValue(type), type);
244+
var mcExpr = Expression.MakeMemberAccess(objExpr, propInfo);
245+
246+
var newExpression = _dependencies.MemberTranslator.Translate(mcExpr);
247+
if (newExpression != null && newExpression != mcExpr)
248+
{
249+
var parametersArray = new Expression[] { objExpr };
250+
result = ConvertToExpressionAttribute(propInfo, newExpression, parametersArray);
251+
}
252+
}
253+
254+
return result;
255+
});
256+
257+
return found;
258+
}
259+
260+
private static EFCoreExpressionAttribute ConvertToExpressionAttribute(MemberInfo memberInfo, Expression newExpression, Expression[] parameters)
261+
{
262+
string PrepareExpressionText(Expression expr)
263+
{
264+
var idx = parameters.IndexOf(expr);
265+
if (idx >= 0)
266+
return $"{{{idx}}}";
267+
268+
if (expr is SqlFragmentExpression fragment)
269+
return fragment.Sql;
270+
271+
if (expr is SqlFunctionExpression sqlFunction)
272+
{
273+
var text = sqlFunction.FunctionName;
274+
if (!sqlFunction.Schema.IsNullOrEmpty())
275+
text = sqlFunction.Schema + "." + sqlFunction.FunctionName;
276+
277+
if (!sqlFunction.IsNiladic)
278+
{
279+
text = text + "(";
280+
for (int i = 0; i < sqlFunction.Arguments.Count; i++)
281+
{
282+
var paramText = PrepareExpressionText(sqlFunction.Arguments[i]);
283+
if (i > 0)
284+
text = text + ", ";
285+
text = text + paramText;
286+
}
287+
288+
text = text + ")";
289+
}
290+
291+
return text;
292+
}
293+
294+
if (newExpression.GetType().GetProperty("Left") != null &&
295+
newExpression.GetType().GetProperty("Right") != null &&
296+
newExpression.GetType().GetProperty("Operator") != null)
297+
{
298+
// Handling NpgSql's CustomBinaryExpression
299+
300+
var left = newExpression.GetType().GetProperty("Left")?.GetValue(newExpression) as Expression;
301+
var right = newExpression.GetType().GetProperty("Right")?.GetValue(newExpression) as Expression;
302+
303+
var operand = newExpression.GetType().GetProperty("Operator")?.GetValue(newExpression) as string;
304+
305+
var text = $"{PrepareExpressionText(left)} {operand} {PrepareExpressionText(right)}";
306+
307+
return text;
308+
}
309+
310+
return "NULL";
311+
}
312+
313+
var converted = UnwrapConverted(newExpression);
314+
var expressionText = PrepareExpressionText(converted);
315+
var result = new EFCoreExpressionAttribute(expressionText)
316+
{ ServerSideOnly = true, IsPredicate = memberInfo.GetMemberType() == typeof(bool) };
317+
318+
if (converted is SqlFunctionExpression || converted is SqlFragmentExpression)
319+
result.Precedence = Precedence.Primary;
320+
321+
return result;
322+
}
323+
324+
private static Expression UnwrapConverted(Expression expr)
325+
{
326+
if (expr is SqlFunctionExpression func)
327+
{
328+
if (string.Equals(func.FunctionName, "COALESCE", StringComparison.InvariantCultureIgnoreCase) &&
329+
func.Arguments.Count == 2 && func.Arguments[1].NodeType == ExpressionType.Default)
330+
return UnwrapConverted(func.Arguments[0]);
331+
}
332+
333+
return expr;
334+
}
335+
163336
public MemberInfo[] GetDynamicColumns(Type type)
164337
{
165338
return Array.Empty<MemberInfo>();

Source/LinqToDB.EntityFrameworkCore/ILinqToDBForEFTools.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.EntityFrameworkCore;
55
using Microsoft.EntityFrameworkCore.Infrastructure;
66
using Microsoft.EntityFrameworkCore.Metadata;
7+
using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors;
78
using Microsoft.Extensions.Logging;
89

910
namespace LinqToDB.EntityFrameworkCore
@@ -35,8 +36,9 @@ public interface ILinqToDBForEFTools
3536
/// Creates metadata provider for specified EF.Core data model.
3637
/// </summary>
3738
/// <param name="model">EF.Core data model.</param>
39+
/// <param name="dependencies"></param>
3840
/// <returns>LINQ To DB metadata provider for specified EF.Core model. Can return <c>null</c>.</returns>
39-
IMetadataReader CreateMetadataReader(IModel model);
41+
IMetadataReader CreateMetadataReader(IModel model, SqlTranslatingExpressionVisitorDependencies dependencies);
4042

4143
/// <summary>
4244
/// Creates mapping schema using provided EF.Core data model and metadata provider.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using LinqToDB.Mapping;
6+
using LinqToDB.SqlQuery;
7+
8+
namespace LinqToDB.EntityFrameworkCore.Internal
9+
{
10+
public class EFCoreExpressionAttribute : Sql.ExpressionAttribute
11+
{
12+
public EFCoreExpressionAttribute(string expression) : base(expression)
13+
{
14+
}
15+
16+
public override ISqlExpression GetExpression(MappingSchema mapping, SelectQuery query, Expression expression, Func<Expression, ISqlExpression> converter)
17+
{
18+
var knownExpressions = new List<Expression>();
19+
if (expression.NodeType == ExpressionType.Call)
20+
{
21+
var mc = (MethodCallExpression) expression;
22+
if (!mc.Method.IsStatic)
23+
knownExpressions.Add(mc.Object);
24+
knownExpressions.AddRange(mc.Arguments);
25+
}
26+
else
27+
{
28+
var me = (MemberExpression) expression;
29+
knownExpressions.Add(me.Expression);
30+
}
31+
32+
var pams = new List<ISqlExpression>(knownExpressions.Select(_ => (ISqlExpression) null));
33+
34+
_ = Sql.ExtensionAttribute.ResolveExpressionValues(Expression,
35+
(v, d) =>
36+
{
37+
var idx = int.Parse(v);
38+
39+
if (pams[idx] == null)
40+
pams[idx] = converter(knownExpressions[idx]);
41+
42+
return v;
43+
});
44+
45+
var parameters = pams.Select(p => p ?? new SqlExpression("!!!")).ToArray();
46+
return new SqlExpression(expression.Type, Expression, Precedence, parameters);
47+
}
48+
}
49+
}

Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextExtensions.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static partial class LinqToDBForEFTools
2222
/// <param name="options">Operation options.</param>
2323
/// <param name="source">Records to insert.</param>
2424
/// <returns>Bulk insert operation status.</returns>
25-
public static BulkCopyRowsCopied BulkCopy<T>([NotNull] this DbContext context, BulkCopyOptions options, IEnumerable<T> source)
25+
public static BulkCopyRowsCopied BulkCopy<T>([NotNull] this DbContext context, BulkCopyOptions options, IEnumerable<T> source) where T : class
2626
{
2727
if (context == null) throw new ArgumentNullException(nameof(context));
2828

@@ -40,14 +40,13 @@ public static BulkCopyRowsCopied BulkCopy<T>([NotNull] this DbContext context, B
4040
/// <param name="maxBatchSize">Number of rows in each batch. At the end of each batch, the rows in the batch are sent to the server. </param>
4141
/// <param name="source">Records to insert.</param>
4242
/// <returns>Bulk insert operation status.</returns>
43-
public static BulkCopyRowsCopied BulkCopy<T>([NotNull] this DbContext context, int maxBatchSize, IEnumerable<T> source)
43+
public static BulkCopyRowsCopied BulkCopy<T>([NotNull] this DbContext context, int maxBatchSize, IEnumerable<T> source) where T : class
4444
{
4545
if (context == null) throw new ArgumentNullException(nameof(context));
4646

4747
using (var dc = context.CreateLinqToDbConnection())
4848
{
49-
return dc.DataProvider.BulkCopy(
50-
dc,
49+
return dc.BulkCopy(
5150
new BulkCopyOptions { MaxBatchSize = maxBatchSize },
5251
source);
5352
}
@@ -60,14 +59,13 @@ public static BulkCopyRowsCopied BulkCopy<T>([NotNull] this DbContext context, i
6059
/// <param name="context">Database context.</param>
6160
/// <param name="source">Records to insert.</param>
6261
/// <returns>Bulk insert operation status.</returns>
63-
public static BulkCopyRowsCopied BulkCopy<T>([NotNull] this DbContext context, IEnumerable<T> source)
62+
public static BulkCopyRowsCopied BulkCopy<T>([NotNull] this DbContext context, IEnumerable<T> source) where T : class
6463
{
6564
if (context == null) throw new ArgumentNullException(nameof(context));
6665

6766
using (var dc = context.CreateLinqToDbConnection())
6867
{
69-
return dc.DataProvider.BulkCopy(
70-
dc,
68+
return dc.BulkCopy(
7169
new BulkCopyOptions(),
7270
source);
7371
}

Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.Mapping.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ static void InitializeMapping()
1818
Linq.Expressions.MapMember(
1919
(DbFunctions f, string m, string p) => f.Like(m, p), (f, m, p) => Sql.Like(m, p));
2020

21-
InitializeSqlServerMapping();
21+
// InitializeSqlServerMapping();
2222
}
2323

2424

0 commit comments

Comments
 (0)