|
19 | 19 | using Microsoft.Extensions.Logging; |
20 | 20 |
|
21 | 21 | using JetBrains.Annotations; |
| 22 | +using LinqToDB.Common; |
22 | 23 | using LinqToDB.Tools; |
23 | 24 |
|
24 | 25 | namespace LinqToDB.EntityFrameworkCore |
@@ -396,69 +397,73 @@ public virtual void DefineConvertors( |
396 | 397 | .Distinct() |
397 | 398 | .ToArray(); |
398 | 399 |
|
399 | | - foreach (var clrType in types) |
| 400 | + var sqlConverter = mappingSchema.ValueToSqlConverter; |
| 401 | + |
| 402 | + foreach (var modelType in types) |
400 | 403 | { |
401 | 404 | // skipping enums |
402 | | - if (clrType.IsEnum) |
403 | | - continue; |
404 | | - |
405 | | - var currentType = mappingSchema.GetDataType(clrType); |
406 | | - if (currentType != SqlDataType.Undefined) |
| 405 | + if (modelType.IsEnum) |
407 | 406 | continue; |
408 | 407 |
|
409 | | - var infos = convertorSelector.Select(clrType).ToArray(); |
410 | | - if (infos.Length > 0) |
411 | | - { |
412 | | - foreach (var info in infos) |
413 | | - { |
414 | | - currentType = mappingSchema.GetDataType(info.ModelClrType); |
415 | | - if (currentType != SqlDataType.Undefined) |
416 | | - continue; |
417 | | - |
418 | | - var dataType = mappingSchema.GetDataType(info.ProviderClrType); |
419 | | - var fromParam = Expression.Parameter(clrType, "t"); |
420 | | - |
421 | | - var convertExpression = mappingSchema.GetConvertExpression(clrType, info.ProviderClrType, false); |
422 | | - var converter = convertExpression.GetBody(fromParam); |
423 | | - |
424 | | - var valueExpression = converter; |
425 | | - |
426 | | - if (clrType.IsClass || clrType.IsInterface) |
427 | | - { |
428 | | - valueExpression = Expression.Condition( |
429 | | - Expression.Equal(fromParam, |
430 | | - Expression.Constant(null, clrType)), |
431 | | - Expression.Constant(null, clrType), |
432 | | - valueExpression |
433 | | - ); |
434 | | - } |
435 | | - else if (typeof(Nullable<>).IsSameOrParentOf(clrType)) |
436 | | - { |
437 | | - valueExpression = Expression.Condition( |
438 | | - Expression.Property(fromParam, "HasValue"), |
439 | | - Expression.Convert(valueExpression, typeof(object)), |
440 | | - Expression.Constant(null, typeof(object)) |
441 | | - ); |
442 | | - } |
443 | | - |
444 | | - if (valueExpression.Type != typeof(object)) |
445 | | - valueExpression = Expression.Convert(valueExpression, typeof(object)); |
446 | | - |
447 | | - var convertLambda = Expression.Lambda( |
448 | | - Expression.New(DataParameterConstructor, |
449 | | - Expression.Constant("Conv", typeof(string)), |
450 | | - valueExpression, |
451 | | - Expression.Constant(dataType.Type.DataType, typeof(DataType)), |
452 | | - Expression.Constant(dataType.Type.DbType, typeof(string)) |
453 | | - ), fromParam); |
454 | | - |
455 | | - mappingSchema.SetConvertExpression(clrType, typeof(DataParameter), convertLambda, false); |
456 | | - } |
457 | | - } |
| 408 | + MapEFCoreType(modelType); |
| 409 | + if (modelType.IsValueType && !typeof(Nullable<>).IsSameOrParentOf(modelType)) |
| 410 | + MapEFCoreType(typeof(Nullable<>).MakeGenericType(modelType)); |
458 | 411 | } |
459 | 412 |
|
| 413 | + void MapEFCoreType(Type modelType) |
| 414 | + { |
| 415 | + var currentType = mappingSchema.GetDataType(modelType); |
| 416 | + if (currentType != SqlDataType.Undefined) |
| 417 | + return; |
| 418 | + |
| 419 | + var infos = convertorSelector.Select(modelType).ToArray(); |
| 420 | + if (infos.Length <= 0) |
| 421 | + return; |
| 422 | + |
| 423 | + var info = infos[0]; |
| 424 | + var providerType = info.ProviderClrType; |
| 425 | + var dataType = mappingSchema.GetDataType(providerType); |
| 426 | + var fromParam = Expression.Parameter(modelType, "t"); |
| 427 | + var toParam = Expression.Parameter(providerType, "t"); |
| 428 | + var converter = info.Create(); |
| 429 | + |
| 430 | + var valueExpression = |
| 431 | + Expression.Invoke(Expression.Constant(converter.ConvertToProvider), WithConvertToObject(fromParam)); |
| 432 | + var convertLambda = WithToDataParameter(valueExpression, dataType, fromParam); |
| 433 | + |
| 434 | + mappingSchema.SetConvertExpression(modelType, typeof(DataParameter), convertLambda, false); |
| 435 | + mappingSchema.SetConvertExpression(modelType, providerType, |
| 436 | + Expression.Lambda(Expression.Convert(valueExpression, providerType), fromParam)); |
| 437 | + mappingSchema.SetConvertExpression(providerType, modelType, |
| 438 | + Expression.Lambda( |
| 439 | + Expression.Convert( |
| 440 | + Expression.Invoke(Expression.Constant(converter.ConvertFromProvider), WithConvertToObject(toParam)), |
| 441 | + modelType), toParam)); |
| 442 | + |
| 443 | + mappingSchema.SetValueToSqlConverter(modelType, (sb, dt, v) |
| 444 | + => sqlConverter.Convert(sb, dt, converter.ConvertToProvider(v))); |
| 445 | + } |
460 | 446 | } |
461 | 447 |
|
| 448 | + private static LambdaExpression WithToDataParameter(Expression valueExpression, SqlDataType dataType, ParameterExpression fromParam) |
| 449 | + => Expression.Lambda |
| 450 | + ( |
| 451 | + Expression.New |
| 452 | + ( |
| 453 | + DataParameterConstructor, |
| 454 | + Expression.Constant("Conv", typeof(string)), |
| 455 | + valueExpression, |
| 456 | + Expression.Constant(dataType.Type.DataType, typeof(DataType)), |
| 457 | + Expression.Constant(dataType.Type.DbType, typeof(string)) |
| 458 | + ), |
| 459 | + fromParam |
| 460 | + ); |
| 461 | + |
| 462 | + private static Expression WithConvertToObject(Expression valueExpression) |
| 463 | + => valueExpression.Type != typeof(object) |
| 464 | + ? Expression.Convert(valueExpression, typeof(object)) |
| 465 | + : valueExpression; |
| 466 | + |
462 | 467 | /// <summary> |
463 | 468 | /// Returns mapping schema using provided EF.Core data model and metadata provider. |
464 | 469 | /// </summary> |
@@ -488,43 +493,42 @@ public virtual IDbContextOptions GetContextOptions(DbContext context) |
488 | 493 | return context?.GetService<IDbContextOptions>(); |
489 | 494 | } |
490 | 495 |
|
491 | | - static readonly MethodInfo GetTableMethodInfo = |
492 | | - MemberHelper.MethodOf<IDataContext>(dc => dc.GetTable<object>()).GetGenericMethodDefinition(); |
| 496 | + static readonly MethodInfo GetTableMethodInfo = MemberHelper.MethodOfGeneric<IDataContext>(dc => dc.GetTable<object>()); |
| 497 | + |
| 498 | + static readonly MethodInfo EnumerableWhereMethodInfo = MemberHelper.MethodOfGeneric<IEnumerable<object>>(q => q.Where(p => true)); |
493 | 499 |
|
494 | | - static readonly MethodInfo EnumerableWhereMethodInfo = |
495 | | - MemberHelper.MethodOf<IEnumerable<object>>(q => q.Where(p => true)).GetGenericMethodDefinition(); |
| 500 | + static readonly MethodInfo FromSqlOnQueryableMethodInfo = |
| 501 | + typeof(RelationalQueryableExtensions).GetMethods(BindingFlags.Static|BindingFlags.NonPublic).Single(x => x.Name == "FromSqlOnQueryable").GetGenericMethodDefinition(); |
496 | 502 |
|
497 | | - static readonly MethodInfo EnumerableToListMethodInfo = |
498 | | - MemberHelper.MethodOf<IEnumerable<object>>(q => q.ToList()).GetGenericMethodDefinition(); |
| 503 | + static readonly MethodInfo EnumerableToListMethodInfo = MemberHelper.MethodOfGeneric<IEnumerable<object>>(q => q.ToList()); |
499 | 504 |
|
500 | | - static readonly MethodInfo IgnoreQueryFiltersMethodInfo = |
501 | | - MemberHelper.MethodOf<IQueryable<object>>(q => q.IgnoreQueryFilters()).GetGenericMethodDefinition(); |
| 505 | + static readonly MethodInfo IgnoreQueryFiltersMethodInfo = MemberHelper.MethodOfGeneric<IQueryable<object>>(q => q.IgnoreQueryFilters()); |
502 | 506 |
|
503 | | - static readonly MethodInfo IncludeMethodInfo = |
504 | | - MemberHelper.MethodOf<IQueryable<object>>(q => q.Include(o => o.ToString())).GetGenericMethodDefinition(); |
| 507 | + static readonly MethodInfo IncludeMethodInfo = MemberHelper.MethodOfGeneric<IQueryable<object>>(q => q.Include(o => o.ToString())); |
505 | 508 |
|
506 | | - static readonly MethodInfo IncludeMethodInfoString = |
507 | | - MemberHelper.MethodOf<IQueryable<object>>(q => q.Include(string.Empty)).GetGenericMethodDefinition(); |
| 509 | + static readonly MethodInfo IncludeMethodInfoString = MemberHelper.MethodOfGeneric<IQueryable<object>>(q => q.Include(string.Empty)); |
508 | 510 |
|
509 | 511 | static readonly MethodInfo ThenIncludeMethodInfo = |
510 | | - MemberHelper.MethodOf<IIncludableQueryable<object, object>>(q => q.ThenInclude<object, object, object>(null)).GetGenericMethodDefinition(); |
| 512 | + MemberHelper.MethodOfGeneric<IIncludableQueryable<object, object>>(q => q.ThenInclude<object, object, object>(null)); |
511 | 513 |
|
512 | 514 | static readonly MethodInfo ThenIncludeEnumerableMethodInfo = |
513 | | - MemberHelper.MethodOf<IIncludableQueryable<object, IEnumerable<object>>>(q => q.ThenInclude<object, object, object>(null)).GetGenericMethodDefinition(); |
| 515 | + MemberHelper.MethodOfGeneric<IIncludableQueryable<object, IEnumerable<object>>>(q => q.ThenInclude<object, object, object>(null)); |
514 | 516 |
|
515 | 517 |
|
516 | | - static readonly MethodInfo FirstMethodInfo = |
517 | | - MemberHelper.MethodOf<IEnumerable<object>>(q => q.First()).GetGenericMethodDefinition(); |
| 518 | + static readonly MethodInfo FirstMethodInfo = MemberHelper.MethodOfGeneric<IEnumerable<object>>(q => q.First()); |
518 | 519 |
|
519 | | - static readonly MethodInfo AsNoTrackingMethodInfo = |
520 | | - MemberHelper.MethodOf<IQueryable<object>>(q => q.AsNoTracking()).GetGenericMethodDefinition(); |
| 520 | + static readonly MethodInfo AsNoTrackingMethodInfo = MemberHelper.MethodOfGeneric<IQueryable<object>>(q => q.AsNoTracking()); |
521 | 521 |
|
522 | | - static readonly MethodInfo EFProperty = |
523 | | - MemberHelper.MethodOf(() => EF.Property<object>(1, "")).GetGenericMethodDefinition(); |
| 522 | + static readonly MethodInfo EFProperty = MemberHelper.MethodOfGeneric(() => EF.Property<object>(1, "")); |
524 | 523 |
|
525 | 524 | static readonly MethodInfo |
526 | 525 | L2DBProperty = typeof(Sql).GetMethod(nameof(Sql.Property)).GetGenericMethodDefinition(); |
527 | 526 |
|
| 527 | + static readonly MethodInfo L2DBFromSqlMethodInfo = |
| 528 | + MemberHelper.MethodOfGeneric<IDataContext>(dc => dc.FromSql<object>(new Common.RawSqlString())); |
| 529 | + |
| 530 | + static readonly ConstructorInfo RawSqlStringConstructor = MemberHelper.ConstructorOf(() => new Common.RawSqlString("")); |
| 531 | + |
528 | 532 | static readonly ConstructorInfo DataParameterConstructor = MemberHelper.ConstructorOf(() => new DataParameter("", "", DataType.Undefined, "")); |
529 | 533 |
|
530 | 534 | public static Expression Unwrap(Expression ex) |
@@ -746,7 +750,7 @@ Expression LocalTransform(Expression e) |
746 | 750 |
|
747 | 751 | var propName = (string)EvaluateExpression(methodCall.Arguments[1]); |
748 | 752 | var param = Expression.Parameter(methodCall.Method.GetGenericArguments()[0], "e"); |
749 | | - var propPath = propName.Split('.', StringSplitOptions.RemoveEmptyEntries); |
| 753 | + var propPath = propName.Split(new[] {'.'}, StringSplitOptions.RemoveEmptyEntries); |
750 | 754 | var prop = (Expression)param; |
751 | 755 | for (int i = 0; i < propPath.Length; i++) |
752 | 756 | { |
@@ -788,6 +792,15 @@ Expression LocalTransform(Expression e) |
788 | 792 | break; |
789 | 793 | } |
790 | 794 |
|
| 795 | + if (generic == FromSqlOnQueryableMethodInfo) |
| 796 | + { |
| 797 | + //convert the arguments from the FromSqlOnQueryable method from EF, to a L2DB FromSql call |
| 798 | + return Expression.Call(null, L2DBFromSqlMethodInfo.MakeGenericMethod(methodCall.Method.GetGenericArguments()[0]), |
| 799 | + Expression.Constant(dc), |
| 800 | + Expression.New(RawSqlStringConstructor, methodCall.Arguments[1]), |
| 801 | + methodCall.Arguments[2]); |
| 802 | + } |
| 803 | + |
791 | 804 | if (typeof(IQueryable<>).IsSameOrParentOf(methodCall.Type)) |
792 | 805 | { |
793 | 806 | // Invoking function to evaluate EF's Subquery located in function |
|
0 commit comments