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

Commit db7df7a

Browse files
authored
Merge pull request #44 from linq2db/master
Release 3.1.0
2 parents a0c0070 + 19a2518 commit db7df7a

File tree

12 files changed

+199
-103
lines changed

12 files changed

+199
-103
lines changed

Build/linq2db.Default.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>3.0.0</Version>
3+
<Version>3.1.0</Version>
44

55
<Description>Allows to execute Linq to DB (linq2db) queries in Entity Framework Core DbContext.</Description>
66
<Title>Linq to DB (linq2db) extensions for Entity Framework Core</Title>

NuGet/linq2db.EntityFrameworkCore.nuspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<projectUrl>https://github.com/linq2db/linq2db.EntityFrameworkCore</projectUrl>
1515
<license type="file">MIT-LICENSE.txt</license>
1616
<dependencies>
17-
<group targetFramework=".NETStandard2.1">
17+
<group targetFramework=".NETStandard2.0">
1818
<dependency id="Microsoft.EntityFrameworkCore.Relational" version="3.1.3" />
1919
<dependency id="linq2db" version="3.0.0-preview.2" />
2020
</group>

Source/LinqToDB.EntityFrameworkCore/EFCoreMetadataReader.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ static bool CompareProperty(MemberInfo property, MemberInfo memberInfo)
6464
if (property == memberInfo)
6565
return true;
6666

67-
if (memberInfo.DeclaringType.IsAssignableFrom(property.DeclaringType)
67+
if (memberInfo.DeclaringType?.IsAssignableFrom(property.DeclaringType) == true
6868
&& memberInfo.Name == property.Name
6969
&& memberInfo.MemberType == property.MemberType
7070
&& memberInfo.GetMemberType() == property.GetMemberType())
@@ -74,7 +74,8 @@ static bool CompareProperty(MemberInfo property, MemberInfo memberInfo)
7474

7575
return false;
7676
}
77-
77+
78+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "<Pending>")]
7879
static bool CompareProperty(IProperty property, MemberInfo memberInfo)
7980
{
8081
return CompareProperty(property.GetIdentifyingMemberInfo(), memberInfo);

Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsImplDefault.cs

Lines changed: 89 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using Microsoft.Extensions.Logging;
2020

2121
using JetBrains.Annotations;
22+
using LinqToDB.Common;
2223
using LinqToDB.Tools;
2324

2425
namespace LinqToDB.EntityFrameworkCore
@@ -396,69 +397,73 @@ public virtual void DefineConvertors(
396397
.Distinct()
397398
.ToArray();
398399

399-
foreach (var clrType in types)
400+
var sqlConverter = mappingSchema.ValueToSqlConverter;
401+
402+
foreach (var modelType in types)
400403
{
401404
// skipping enums
402-
if (clrType.IsEnum)
403-
continue;
404-
405-
var currentType = mappingSchema.GetDataType(clrType);
406-
if (currentType != SqlDataType.Undefined)
405+
if (modelType.IsEnum)
407406
continue;
408407

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));
458411
}
459412

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+
}
460446
}
461447

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+
462467
/// <summary>
463468
/// Returns mapping schema using provided EF.Core data model and metadata provider.
464469
/// </summary>
@@ -488,43 +493,42 @@ public virtual IDbContextOptions GetContextOptions(DbContext context)
488493
return context?.GetService<IDbContextOptions>();
489494
}
490495

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));
493499

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();
496502

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());
499504

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());
502506

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()));
505508

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));
508510

509511
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));
511513

512514
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));
514516

515517

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());
518519

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());
521521

522-
static readonly MethodInfo EFProperty =
523-
MemberHelper.MethodOf(() => EF.Property<object>(1, "")).GetGenericMethodDefinition();
522+
static readonly MethodInfo EFProperty = MemberHelper.MethodOfGeneric(() => EF.Property<object>(1, ""));
524523

525524
static readonly MethodInfo
526525
L2DBProperty = typeof(Sql).GetMethod(nameof(Sql.Property)).GetGenericMethodDefinition();
527526

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+
528532
static readonly ConstructorInfo DataParameterConstructor = MemberHelper.ConstructorOf(() => new DataParameter("", "", DataType.Undefined, ""));
529533

530534
public static Expression Unwrap(Expression ex)
@@ -746,7 +750,7 @@ Expression LocalTransform(Expression e)
746750

747751
var propName = (string)EvaluateExpression(methodCall.Arguments[1]);
748752
var param = Expression.Parameter(methodCall.Method.GetGenericArguments()[0], "e");
749-
var propPath = propName.Split('.', StringSplitOptions.RemoveEmptyEntries);
753+
var propPath = propName.Split(new[] {'.'}, StringSplitOptions.RemoveEmptyEntries);
750754
var prop = (Expression)param;
751755
for (int i = 0; i < propPath.Length; i++)
752756
{
@@ -788,6 +792,15 @@ Expression LocalTransform(Expression e)
788792
break;
789793
}
790794

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+
791804
if (typeof(IQueryable<>).IsSameOrParentOf(methodCall.Type))
792805
{
793806
// Invoking function to evaluate EF's Subquery located in function

Source/LinqToDB.EntityFrameworkCore/linq2db.EntityFrameworkCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<Import Project="..\..\Build\linq2db.Default.props" />
44

55
<PropertyGroup>
6-
<TargetFramework>netstandard2.1</TargetFramework>
6+
<TargetFramework>netstandard2.0</TargetFramework>
77
<RootNamespace>LinqToDB.EntityFrameworkCore</RootNamespace>
88
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\linq2db.EntityFrameworkCore.xml</DocumentationFile>
99
</PropertyGroup>

Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ToolsTests.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,62 @@ public async Task TestSetUpdate()
537537
}
538538
}
539539

540+
[Test]
541+
public async Task FromSqlRaw()
542+
{
543+
using (var ctx = CreateContext())
544+
{
545+
var id = 1;
546+
var query = ctx.Categories.FromSqlRaw("SELECT * FROM [dbo].[Categories] WHERE CategoryId = {0}", id);
547+
548+
549+
var efResult = await query.ToArrayAsyncEF();
550+
var linq2dbResult = await query.ToArrayAsyncLinqToDB();
551+
}
552+
}
553+
554+
[Test]
555+
public async Task FromSqlRaw2()
556+
{
557+
using (var ctx = CreateContext())
558+
{
559+
var id = 1;
560+
var query = from c1 in ctx.Categories
561+
from c2 in ctx.Categories.FromSqlRaw("SELECT * FROM [dbo].[Categories] WHERE CategoryId = {0}", id)
562+
select c2;
563+
564+
var efResult = await query.ToArrayAsyncEF();
565+
var linq2dbResult = await query.ToArrayAsyncLinqToDB();
566+
}
567+
}
568+
569+
[Test]
570+
public async Task FromSqlInterpolated()
571+
{
572+
using (var ctx = CreateContext())
573+
{
574+
var id = 1;
575+
var query = ctx.Categories.FromSqlInterpolated($"SELECT * FROM [dbo].[Categories] WHERE CategoryId = {id}");
576+
577+
var efResult = await query.ToArrayAsyncEF();
578+
var linq2dbResult = await query.ToArrayAsyncLinqToDB();
579+
}
580+
}
581+
582+
[Test]
583+
public async Task FromSqlInterpolated2()
584+
{
585+
using (var ctx = CreateContext())
586+
{
587+
var id = 1;
588+
var query = from c1 in ctx.Categories
589+
from c2 in ctx.Categories.FromSqlInterpolated($"SELECT * FROM [dbo].[Categories] WHERE CategoryId = {id}")
590+
select c2;
591+
592+
var efResult = await query.ToArrayAsyncEF();
593+
var linq2dbResult = await query.ToArrayAsyncLinqToDB();
594+
}
595+
}
540596

541597
}
542598
}

Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ValueConversion/ConvertorTests.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,25 @@ public void TestToList()
5454

5555
resut = db.InsertWithInt64Identity(new SubDivision()
5656
{ Code = "C3", Id = new Id<SubDivision, long>(2), Name = "N3", PermanentId = Guid.NewGuid() });
57-
58-
59-
db.GetTable<SubDivision>()
60-
.Where(s => s.Id == 1)
61-
.Set(s => s.ParentId, new Id<SubDivision, long>(11))
62-
.Update();
63-
64-
db.GetTable<SubDivision>()
65-
.Where(s => s.Id == 2)
66-
.Set(s => s.ParentId, () => new Id<SubDivision, long>(33))
67-
.Update();
68-
57+
6958
var ef = ctx.Subdivisions.Where(s => s.Id == 1L).ToArray();
7059
var ltdb = ctx.Subdivisions.ToLinqToDB().Where(s => s.Id == 1L).ToArray();
71-
var all = ctx.Subdivisions.ToLinqToDB().ToArray();
60+
61+
var id = new Nullable<Id<SubDivision, long>>(0L.AsId<SubDivision>());
62+
var ltdb2 = ctx.Subdivisions.ToLinqToDB().Where(s => s.Id == id).ToArray();
63+
64+
var ids = new[] {1L.AsId<SubDivision>(), 2L.AsId<SubDivision>(),};
65+
var ltdbin = ctx.Subdivisions.ToLinqToDB()
66+
.Where(s => ids.Contains(s.Id)).ToArray();
67+
68+
var all = ctx.Subdivisions.ToLinqToDB().ToArray();
69+
70+
Assert.AreEqual(ef[0].Code, ltdb[0].Code);
71+
Assert.AreEqual(ef[0].Id, ltdb[0].Id);
72+
73+
Assert.AreEqual(ef[0].Code, ltdb2[0].Code);
74+
Assert.AreEqual(ef[0].Id, ltdb2[0].Id);
7275
}
7376
}
74-
7577
}
7678
}

Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ValueConversion/IdValueConverterSelector.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ public override IEnumerable<ValueConverterInfo> Select(Type modelClrType, Type p
3131
if (key != providerClrType)
3232
yield break;
3333

34-
var ct = typeof(IdValueConverter<,>).MakeGenericType(key, t[0]);
34+
var ct =
35+
36+
key == typeof(long)
37+
? typeof(IdValueConverter<>).MakeGenericType(t[0])
38+
:
39+
40+
typeof(IdValueConverter<,>).MakeGenericType(key, t[0]);
3541
yield return new ValueConverterInfo
3642
(
3743
modelClrType,

Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ValueConversion/IdValueConverter`2.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,11 @@ public sealed class IdValueConverter<TKey, TEntity> : ValueConverter<Id<TEntity,
88
public IdValueConverter(ConverterMappingHints mappingHints = null)
99
: base(id => id.Value, key => new Id<TEntity, TKey>(key)) { }
1010
}
11+
12+
public sealed class IdValueConverter<TEntity> : ValueConverter<Id<TEntity, long>, long>
13+
where TEntity : IEntity<long>
14+
{
15+
public IdValueConverter(ConverterMappingHints mappingHints = null)
16+
: base(id => id.Value + 1, key => new Id<TEntity, long>(key - 1)) { }
17+
}
1118
}

0 commit comments

Comments
 (0)