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

Commit 77e13c9

Browse files
Fix #40 EF Core value converter selector support (#42)
* Use semantic Id<>.ToString() * Add helper methods for Id<> * Add IN() case to selector for Id<> array to ConvertorTests * Invert if in DefineConvertors * Extract method WithNullCheck from DefineConvertors * Extract method WithConvertToObject from DefineConvertors * Extract method WithToDataParameter from DefineConvertors * Add SqlConverters from EFCore value convertors - fix ConvertorTests * Add assert for id<> convertions check * Use tricky converter * Use internal Id<> constructor and Value property * Use direct EF conveters instead linq2db automation * Add nullable equality case * Add equality Id<> operators * Drop nullable Id<SubDivision, long> field * Use modelType variable in DefineConvertors method * Use providerType variable in DefineConvertors method * Move sqlConverter variable from cycle in DefineConvertors method * Drop useless code * Extract MapEFCoreType local function from DefineConvertors method * Also define converters for Nullable<EFCoreType>
1 parent acc96cc commit 77e13c9

File tree

6 files changed

+105
-75
lines changed

6 files changed

+105
-75
lines changed

Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsImplDefault.cs

Lines changed: 59 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -396,69 +396,73 @@ public virtual void DefineConvertors(
396396
.Distinct()
397397
.ToArray();
398398

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

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-
}
407+
MapEFCoreType(modelType);
408+
if (modelType.IsValueType && !typeof(Nullable<>).IsSameOrParentOf(modelType))
409+
MapEFCoreType(typeof(Nullable<>).MakeGenericType(modelType));
458410
}
459411

412+
void MapEFCoreType(Type modelType)
413+
{
414+
var currentType = mappingSchema.GetDataType(modelType);
415+
if (currentType != SqlDataType.Undefined)
416+
return;
417+
418+
var infos = convertorSelector.Select(modelType).ToArray();
419+
if (infos.Length <= 0)
420+
return;
421+
422+
var info = infos[0];
423+
var providerType = info.ProviderClrType;
424+
var dataType = mappingSchema.GetDataType(providerType);
425+
var fromParam = Expression.Parameter(modelType, "t");
426+
var toParam = Expression.Parameter(providerType, "t");
427+
var converter = info.Create();
428+
429+
var valueExpression =
430+
Expression.Invoke(Expression.Constant(converter.ConvertToProvider), WithConvertToObject(fromParam));
431+
var convertLambda = WithToDataParameter(valueExpression, dataType, fromParam);
432+
433+
mappingSchema.SetConvertExpression(modelType, typeof(DataParameter), convertLambda, false);
434+
mappingSchema.SetConvertExpression(modelType, providerType,
435+
Expression.Lambda(Expression.Convert(valueExpression, providerType), fromParam));
436+
mappingSchema.SetConvertExpression(providerType, modelType,
437+
Expression.Lambda(
438+
Expression.Convert(
439+
Expression.Invoke(Expression.Constant(converter.ConvertFromProvider), WithConvertToObject(toParam)),
440+
modelType), toParam));
441+
442+
mappingSchema.SetValueToSqlConverter(modelType, (sb, dt, v)
443+
=> sqlConverter.Convert(sb, dt, converter.ConvertToProvider(v)));
444+
}
460445
}
461446

447+
private static LambdaExpression WithToDataParameter(Expression valueExpression, SqlDataType dataType, ParameterExpression fromParam)
448+
=> Expression.Lambda
449+
(
450+
Expression.New
451+
(
452+
DataParameterConstructor,
453+
Expression.Constant("Conv", typeof(string)),
454+
valueExpression,
455+
Expression.Constant(dataType.Type.DataType, typeof(DataType)),
456+
Expression.Constant(dataType.Type.DbType, typeof(string))
457+
),
458+
fromParam
459+
);
460+
461+
private static Expression WithConvertToObject(Expression valueExpression)
462+
=> valueExpression.Type != typeof(object)
463+
? Expression.Convert(valueExpression, typeof(object))
464+
: valueExpression;
465+
462466
/// <summary>
463467
/// Returns mapping schema using provided EF.Core data model and metadata provider.
464468
/// </summary>

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
}

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,27 @@
33

44
namespace LinqToDB.EntityFrameworkCore.SqlServer.Tests.ValueConversion
55
{
6+
public static class Id
7+
{
8+
public static Id<T, long> AsId<T>(this long id) where T : IEntity<long> => id.AsId<T, long>();
9+
10+
public static Id<T, Guid> AsId<T>(this Guid guid) where T : IEntity<Guid> => guid.AsId<T, Guid>();
11+
12+
public static Id<TEntity, TKey> AsId<TEntity, TKey>(this TKey @object) where TEntity : IEntity<TKey>
13+
=> new Id<TEntity, TKey>(@object);
14+
}
15+
616
public readonly struct Id<TEntity, TKey> : IEquatable<Id<TEntity, TKey>> where TEntity : IEntity<TKey>
717
{
8-
public Id(TKey id) => Value = id;
9-
public TKey Value { get; }
18+
internal Id(TKey id) => Value = id;
19+
internal TKey Value { get; }
1020
public static implicit operator TKey(Id<TEntity, TKey> id) => id.Value;
11-
public override string ToString() => Value?.ToString();
21+
public override string ToString() => $"{typeof(TEntity).Name}({Value})";
1222
public bool Equals(Id<TEntity, TKey> other) => EqualityComparer<TKey>.Default.Equals(Value, other.Value);
1323
public override bool Equals(object obj) => obj is Id<TEntity, TKey> other && Equals(other);
1424
public override int GetHashCode() => EqualityComparer<TKey>.Default.GetHashCode(Value);
25+
public static bool operator == (Id<TEntity, TKey> left, Id<TEntity, TKey> right)
26+
=> EqualityComparer<TKey>.Default.Equals(left.Value, right.Value);
27+
public static bool operator != (Id<TEntity, TKey> left, Id<TEntity, TKey> right) => !(left == right);
1528
}
1629
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ public class SubDivision : IEntity<long>
77
long IEntity<long>.Id => Id;
88
public Id<SubDivision, long> Id { get; set; }
99
public Guid PermanentId { get; set; }
10-
public Id<SubDivision, long>? ParentId { get; set; }
11-
// public Id<LegalEntity, long> LegalEntityId { get; set; }
1210
public string Code { get; set; }
1311
public string Name { get; set; }
1412
public bool? IsDeleted { get; set; }

0 commit comments

Comments
 (0)