11using System ;
2+ using System . Collections . Concurrent ;
23using System . Collections . Generic ;
34using System . Linq ;
5+ using System . Linq . Expressions ;
46using System . Reflection ;
5-
7+ using LinqToDB . Common ;
8+ using LinqToDB . EntityFrameworkCore . Internal ;
9+ using LinqToDB . SqlQuery ;
610using Microsoft . EntityFrameworkCore ;
11+ using Microsoft . EntityFrameworkCore . Internal ;
712using Microsoft . EntityFrameworkCore . Metadata ;
13+ using Microsoft . EntityFrameworkCore . Metadata . Conventions . Internal ;
814using Microsoft . EntityFrameworkCore . Metadata . Internal ;
15+ using Microsoft . EntityFrameworkCore . Query . Expressions ;
16+ using Microsoft . EntityFrameworkCore . Query . ExpressionVisitors ;
917
1018namespace 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 > ( ) ;
0 commit comments