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

Commit 42ad2f6

Browse files
authored
Added additional mapping for IValueConverterSelector (#26)
* Added tests for value conversion. * Added handling of IValueConverterSelector extension.
1 parent e4d558a commit 42ad2f6

File tree

11 files changed

+288
-16
lines changed

11 files changed

+288
-16
lines changed

Source/LinqToDB.EntityFrameworkCore/ILinqToDBForEFTools.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.EntityFrameworkCore.Metadata;
77
using Microsoft.EntityFrameworkCore.Query;
88
using Microsoft.EntityFrameworkCore.Storage;
9+
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
910
using Microsoft.Extensions.Logging;
1011

1112
namespace LinqToDB.EntityFrameworkCore
@@ -47,16 +48,18 @@ public interface ILinqToDBForEFTools
4748
/// </summary>
4849
/// <param name="model">EF.Core data model.</param>
4950
/// <param name="metadataReader">Additional optional LINQ To DB database metadata provider.</param>
51+
/// <param name="convertorSelector">EF Core registry for type conversion.</param>
5052
/// <returns>Mapping schema for provided EF.Core model.</returns>
51-
MappingSchema CreateMappingSchema(IModel model, IMetadataReader metadataReader);
53+
MappingSchema CreateMappingSchema(IModel model, IMetadataReader metadataReader, IValueConverterSelector convertorSelector);
5254

5355
/// <summary>
5456
/// Returns mapping schema using provided EF.Core data model and metadata provider.
5557
/// </summary>
5658
/// <param name="model">EF.Core data model.</param>
5759
/// <param name="metadataReader">Additional optional LINQ To DB database metadata provider.</param>
60+
/// <param name="convertorSelector">EF Core registry for type conversion.</param>
5861
/// <returns>Mapping schema for provided EF.Core model.</returns>
59-
MappingSchema GetMappingSchema(IModel model, IMetadataReader metadataReader);
62+
MappingSchema GetMappingSchema(IModel model, IMetadataReader metadataReader, IValueConverterSelector convertorSelector);
6063

6164
/// <summary>
6265
/// Returns EF.Core <see cref="IDbContextOptions"/> for specific <see cref="DbContext"/> instance.

Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
using JetBrains.Annotations;
1414
using Microsoft.EntityFrameworkCore.Query;
15+
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
1516

1617
namespace LinqToDB.EntityFrameworkCore
1718
{
@@ -211,14 +212,16 @@ public static IDataProvider GetDataProvider(EFProviderInfo info, EFConnectionInf
211212
/// Creates mapping schema using provided EF.Core data model.
212213
/// </summary>
213214
/// <param name="model">EF.Core data model.</param>
215+
/// <param name="convertorSelector">EF Core registry for type conversion.</param>
214216
/// <param name="dependencies"></param>
215217
/// <param name="mappingSource"></param>
216218
/// <returns>Mapping schema for provided EF.Core model.</returns>
217219
public static MappingSchema GetMappingSchema(IModel model,
220+
IValueConverterSelector convertorSelector,
218221
RelationalSqlTranslatingExpressionVisitorDependencies dependencies,
219222
IRelationalTypeMappingSource mappingSource)
220223
{
221-
return Implementation.GetMappingSchema(model, GetMetadataReader(model, dependencies, mappingSource));
224+
return Implementation.GetMappingSchema(model, GetMetadataReader(model, dependencies, mappingSource), convertorSelector);
222225
}
223226

224227
/// <summary>
@@ -280,7 +283,8 @@ public static DataConnection CreateLinqToDbConnection(this DbContext context,
280283

281284
var dependencies = context.GetService<RelationalSqlTranslatingExpressionVisitorDependencies>();
282285
var mappingSource = context.GetService<IRelationalTypeMappingSource>();
283-
var mappingSchema = GetMappingSchema(context.Model, dependencies, mappingSource);
286+
var converters = context.GetService<IValueConverterSelector>();
287+
var mappingSchema = GetMappingSchema(context.Model, converters, dependencies, mappingSource);
284288
if (mappingSchema != null)
285289
dc.AddMappingSchema(mappingSchema);
286290

@@ -307,7 +311,8 @@ public static IDataContext CreateLinqToDbContext(this DbContext context,
307311
var provider = GetDataProvider(info, connectionInfo);
308312
var dependencies = context.GetService<RelationalSqlTranslatingExpressionVisitorDependencies>();
309313
var mappingSource = context.GetService<IRelationalTypeMappingSource>();
310-
var mappingSchema = GetMappingSchema(context.Model, dependencies, mappingSource);
314+
var converters = context.GetService<IValueConverterSelector>();
315+
var mappingSchema = GetMappingSchema(context.Model, converters, dependencies, mappingSource);
311316
var logger = CreateLogger(info.Options);
312317

313318
if (transaction != null)
@@ -367,7 +372,8 @@ public static DataConnection CreateLinq2DbConnectionDetached([JetBrains.Annotati
367372

368373
var dependencies = context.GetService<RelationalSqlTranslatingExpressionVisitorDependencies>();
369374
var mappingSource = context.GetService<IRelationalTypeMappingSource>();
370-
var mappingSchema = GetMappingSchema(context.Model, dependencies, mappingSource);
375+
var converters = context.GetService<IValueConverterSelector>();
376+
var mappingSchema = GetMappingSchema(context.Model, converters, dependencies, mappingSource);
371377
if (mappingSchema != null)
372378
dc.AddMappingSchema(mappingSchema);
373379

@@ -438,7 +444,7 @@ public static DataConnection CreateLinqToDbConnection(this DbContextOptions opti
438444

439445
if (model != null)
440446
{
441-
var mappingSchema = GetMappingSchema(model, null, null);
447+
var mappingSchema = GetMappingSchema(model, null, null, null);
442448
if (mappingSchema != null)
443449
dc.AddMappingSchema(mappingSchema);
444450
}

Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsImplDefault.cs

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Microsoft.EntityFrameworkCore.Infrastructure;
1414
using Microsoft.EntityFrameworkCore.Metadata;
1515
using Microsoft.EntityFrameworkCore.Storage;
16+
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
1617
using Microsoft.EntityFrameworkCore.Query;
1718
using Microsoft.EntityFrameworkCore.Query.Internal;
1819
using Microsoft.Extensions.Logging;
@@ -26,6 +27,7 @@ namespace LinqToDB.EntityFrameworkCore
2627
using Mapping;
2728
using Metadata;
2829
using Extensions;
30+
using SqlQuery;
2931
using Common.Internal.Cache;
3032

3133
using DataProvider;
@@ -356,27 +358,110 @@ public virtual IMetadataReader CreateMetadataReader(IModel model,
356358
/// </summary>
357359
/// <param name="model">EF.Core data model.</param>
358360
/// <param name="metadataReader">Additional optional LINQ To DB database metadata provider.</param>
361+
/// <param name="convertorSelector"></param>
359362
/// <returns>Mapping schema for provided EF.Core model.</returns>
360-
public virtual MappingSchema CreateMappingSchema(IModel model, IMetadataReader metadataReader)
363+
public virtual MappingSchema CreateMappingSchema(IModel model, IMetadataReader metadataReader,
364+
IValueConverterSelector convertorSelector)
361365
{
362366
var schema = new MappingSchema();
363367
if (metadataReader != null)
364368
schema.AddMetadataReader(metadataReader);
369+
370+
DefineConvertors(schema, model, convertorSelector);
371+
365372
return schema;
366373
}
367374

375+
public virtual void DefineConvertors(
376+
[JetBrains.Annotations.NotNull] MappingSchema mappingSchema,
377+
[JetBrains.Annotations.NotNull] IModel model,
378+
IValueConverterSelector convertorSelector)
379+
{
380+
if (mappingSchema == null) throw new ArgumentNullException(nameof(mappingSchema));
381+
if (model == null) throw new ArgumentNullException(nameof(model));
382+
383+
if (convertorSelector == null)
384+
return;
385+
386+
var entities = model.GetEntityTypes().ToArray();
387+
388+
var types = entities.SelectMany(e => e.GetProperties().Select(p => p.ClrType))
389+
.Distinct()
390+
.ToArray();
391+
392+
foreach (var clrType in types)
393+
{
394+
var currentType = mappingSchema.GetDataType(clrType);
395+
if (currentType != SqlDataType.Undefined)
396+
continue;
397+
398+
var infos = convertorSelector.Select(clrType).ToArray();
399+
if (infos.Length > 0)
400+
{
401+
foreach (var info in infos)
402+
{
403+
currentType = mappingSchema.GetDataType(info.ModelClrType);
404+
if (currentType != SqlDataType.Undefined)
405+
continue;
406+
407+
var dataType = mappingSchema.GetDataType(info.ProviderClrType);
408+
var fromParam = Expression.Parameter(clrType, "t");
409+
410+
var convertExpression = mappingSchema.GetConvertExpression(clrType, info.ProviderClrType, false);
411+
var converter = convertExpression.GetBody(fromParam);
412+
413+
var valueExpression = converter;
414+
415+
if (clrType.IsClass || clrType.IsInterface)
416+
{
417+
valueExpression = Expression.Condition(
418+
Expression.Equal(fromParam,
419+
Expression.Constant(null, clrType)),
420+
Expression.Constant(null, clrType),
421+
valueExpression
422+
);
423+
}
424+
else if (typeof(Nullable<>).IsSameOrParentOf(clrType))
425+
{
426+
valueExpression = Expression.Condition(
427+
Expression.Property(fromParam, "HasValue"),
428+
Expression.Convert(valueExpression, typeof(object)),
429+
Expression.Constant(null, typeof(object))
430+
);
431+
}
432+
433+
if (valueExpression.Type != typeof(object))
434+
valueExpression = Expression.Convert(valueExpression, typeof(object));
435+
436+
var convertLambda = Expression.Lambda(
437+
Expression.New(DataParameterConstructor,
438+
Expression.Constant("Conv", typeof(string)),
439+
valueExpression,
440+
Expression.Constant(dataType.DataType, typeof(DataType)),
441+
Expression.Constant(dataType.DbType, typeof(string))
442+
), fromParam);
443+
444+
mappingSchema.SetConvertExpression(clrType, typeof(DataParameter), convertLambda, false);
445+
}
446+
}
447+
}
448+
449+
}
450+
368451
/// <summary>
369452
/// Returns mapping schema using provided EF.Core data model and metadata provider.
370453
/// </summary>
371454
/// <param name="model">EF.Core data model.</param>
372455
/// <param name="metadataReader">Additional optional LINQ To DB database metadata provider.</param>
456+
/// <param name="convertorSelector"></param>
373457
/// <returns>Mapping schema for provided EF.Core model.</returns>
374-
public virtual MappingSchema GetMappingSchema(IModel model, IMetadataReader metadataReader)
458+
public virtual MappingSchema GetMappingSchema(IModel model, IMetadataReader metadataReader,
459+
IValueConverterSelector convertorSelector)
375460
{
376-
var result = _schemaCache.GetOrCreate(Tuple.Create(model, metadataReader), e =>
461+
var result = _schemaCache.GetOrCreate(Tuple.Create(model, metadataReader, convertorSelector), e =>
377462
{
378463
e.SlidingExpiration = TimeSpan.FromHours(1);
379-
return CreateMappingSchema(model, metadataReader);
464+
return CreateMappingSchema(model, metadataReader, convertorSelector);
380465
});
381466

382467
return result;
@@ -425,9 +510,11 @@ public virtual IDbContextOptions GetContextOptions(DbContext context)
425510
static readonly MethodInfo EFProperty =
426511
MemberHelper.MethodOf(() => EF.Property<object>(1, "")).GetGenericMethodDefinition();
427512

428-
private static readonly MethodInfo
513+
static readonly MethodInfo
429514
L2DBProperty = typeof(Sql).GetMethod(nameof(Sql.Property)).GetGenericMethodDefinition();
430515

516+
static readonly ConstructorInfo DataParameterConstructor = MemberHelper.ConstructorOf(() => new DataParameter("", "", DataType.Undefined, ""));
517+
431518
public static Expression Unwrap(Expression ex)
432519
{
433520
if (ex == null)

Tests/LinqToDB.EntityFrameworkCore.Tests/LinqToDB.EntityFrameworkCore.Tests.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
<ItemGroup>
1414
<PackageReference Include="JetBrains.DotMemoryUnit" Version="3.0.20171219.105559" />
15-
<PackageReference Include="linq2db" Version="2.9.6" />
1615
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.0.0" />
1716
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.0.0" />
1817
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.0.0" />

Tests/LinqToDB.EntityFrameworkCore.Tests/ToolsTests.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.EntityFrameworkCore.Infrastructure;
1313
using Microsoft.EntityFrameworkCore.Query;
1414
using Microsoft.EntityFrameworkCore.Storage;
15+
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
1516

1617
namespace LinqToDB.EntityFrameworkCore.Tests
1718
{
@@ -387,7 +388,8 @@ public void TestCompositeKey()
387388
{
388389
var dependencies = ctx.GetService<RelationalSqlTranslatingExpressionVisitorDependencies>();
389390
var mappingSource = ctx.GetService<IRelationalTypeMappingSource>();
390-
var ms = LinqToDBForEFTools.GetMappingSchema(ctx.Model, dependencies, mappingSource);
391+
var converters = ctx.GetService<IValueConverterSelector>();
392+
var ms = LinqToDBForEFTools.GetMappingSchema(ctx.Model, converters, dependencies, mappingSource);
391393

392394
var customerPk = ms.GetAttribute<ColumnAttribute>(typeof(CustomerAddress),
393395
MemberHelper.MemberOf<CustomerAddress>(c => c.CustomerID));
@@ -412,7 +414,8 @@ public void TestAssociations()
412414
{
413415
var dependencies = ctx.GetService<RelationalSqlTranslatingExpressionVisitorDependencies>();
414416
var mappingSource = ctx.GetService<IRelationalTypeMappingSource>();
415-
var ms = LinqToDBForEFTools.GetMappingSchema(ctx.Model, dependencies, null);
417+
var converters = ctx.GetService<IValueConverterSelector>();
418+
var ms = LinqToDBForEFTools.GetMappingSchema(ctx.Model, converters, dependencies, null);
416419

417420
var associationCustomer = ms.GetAttribute<AssociationAttribute>(typeof(CustomerAddress),
418421
MemberHelper.MemberOf<CustomerAddress>(c => c.Customer));
@@ -438,7 +441,8 @@ public void TestIdentityColumn()
438441
{
439442
var dependencies = ctx.GetService<RelationalSqlTranslatingExpressionVisitorDependencies>();
440443
var mappingSource = ctx.GetService<IRelationalTypeMappingSource>();
441-
var ms = LinqToDBForEFTools.GetMappingSchema(ctx.Model, dependencies, mappingSource);
444+
var converters = ctx.GetService<IValueConverterSelector>();
445+
var ms = LinqToDBForEFTools.GetMappingSchema(ctx.Model, converters, dependencies, mappingSource);
442446

443447
var identity = ms.GetAttribute<ColumnAttribute>(typeof(SalesOrderDetail),
444448
MemberHelper.MemberOf<SalesOrderDetail>(c => c.SalesOrderDetailID));
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System;
2+
using System.ComponentModel.DataAnnotations.Schema;
3+
using System.Linq;
4+
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
6+
using NUnit.Framework;
7+
8+
namespace LinqToDB.EntityFrameworkCore.Tests.ValueConversion
9+
{
10+
[TestFixture]
11+
public class ConvertorTests
12+
{
13+
private DbContextOptions<ConvertorContext> _options;
14+
15+
public class ConvertorContext : DbContext
16+
{
17+
public ConvertorContext(DbContextOptions options) : base(options)
18+
{
19+
}
20+
21+
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
22+
public virtual DbSet<SubDivision> Subdivisions { get; set; }
23+
}
24+
25+
public ConvertorTests()
26+
{
27+
var optionsBuilder = new DbContextOptionsBuilder<ConvertorContext>();
28+
29+
optionsBuilder
30+
.ReplaceService<IValueConverterSelector, IdValueConverterSelector>()
31+
.UseSqlServer("Server=.;Database=ConverterTests;Integrated Security=SSPI")
32+
.UseLoggerFactory(TestUtils.LoggerFactory);;
33+
34+
_options = optionsBuilder.Options;
35+
}
36+
37+
38+
[Test]
39+
public void TestToList()
40+
{
41+
using (var ctx = new ConvertorContext(_options))
42+
using (var db = ctx.CreateLinqToDbConnection())
43+
{
44+
ctx.Database.EnsureDeleted();
45+
ctx.Database.EnsureCreated();
46+
47+
48+
var resut = db.InsertWithInt64Identity(new SubDivision()
49+
{ Code = "C1", Id = new Id<SubDivision, long>(0), Name = "N1", PermanentId = Guid.NewGuid() });
50+
51+
resut = db.InsertWithInt64Identity(new SubDivision()
52+
{ Code = "C2", Id = new Id<SubDivision, long>(1), Name = "N2", PermanentId = Guid.NewGuid() });
53+
54+
resut = db.InsertWithInt64Identity(new SubDivision()
55+
{ Code = "C3", Id = new Id<SubDivision, long>(2), Name = "N3", PermanentId = Guid.NewGuid() });
56+
57+
58+
db.GetTable<SubDivision>()
59+
.Where(s => s.Id == 1)
60+
.Set(s => s.ParentId, new Id<SubDivision, long>(11))
61+
.Update();
62+
63+
db.GetTable<SubDivision>()
64+
.Where(s => s.Id == 2)
65+
.Set(s => s.ParentId, () => new Id<SubDivision, long>(33))
66+
.Update();
67+
68+
var ef = ctx.Subdivisions.Where(s => s.Id == 1L).ToArray();
69+
var ltdb = ctx.Subdivisions.ToLinqToDB().Where(s => s.Id == 1L).ToArray();
70+
var all = ctx.Subdivisions.ToLinqToDB().ToArray();
71+
}
72+
}
73+
74+
}
75+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace LinqToDB.EntityFrameworkCore.Tests.ValueConversion
5+
{
6+
public interface IEntity<TKey>
7+
{
8+
public TKey Id { get; }
9+
}
10+
11+
}

0 commit comments

Comments
 (0)