Skip to content

Commit 5de34c1

Browse files
fix bug EFCore: SqlQuery with collection parameters (#558)
1 parent 765ee03 commit 5de34c1

File tree

13 files changed

+276
-85
lines changed

13 files changed

+276
-85
lines changed

src/EFCore.Ydb/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
- Fixed bug: SqlQuery throws exception when using list parameters ([#540](https://github.com/ydb-platform/ydb-dotnet-sdk/issues/540)).
12
- Added support for the YDB retry policy (ADO.NET) and new configuration methods in `YdbDbContextOptionsBuilder`:
23
- `EnableRetryIdempotence()`: enables retries for errors classified as idempotent. You must ensure the operation itself is idempotent.
34
- `UseRetryPolicy(YdbRetryPolicyConfig retryPolicyConfig)`: configures custom backoff parameters and the maximum number of retry attempts.

src/EFCore.Ydb/src/EntityFrameworkCore.Ydb.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
</ItemGroup>
1515

1616
<ItemGroup>
17-
<PackageReference Include="Ydb.Sdk" Version="0.24.0"/>
17+
<PackageReference Include="Ydb.Sdk" Version="0.25.1"/>
1818
</ItemGroup>
1919

2020
<ItemGroup>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Ydb.Sdk.Ado.YdbType;
2+
3+
namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
4+
5+
internal interface IYdbTypeMapping
6+
{
7+
/// <summary>
8+
/// The database type used by YDB.
9+
/// </summary>
10+
YdbDbType YdbDbType { get; }
11+
}

src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbBoolTypeMapping.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ private YdbBoolTypeMapping(RelationalTypeMappingParameters parameters)
1515
{
1616
}
1717

18-
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
19-
=> new YdbBoolTypeMapping(parameters);
18+
protected override YdbBoolTypeMapping Clone(RelationalTypeMappingParameters parameters) => new(parameters);
2019

2120
protected override string GenerateNonNullSqlLiteral(object value)
2221
=> (bool)value ? "TRUE" : "FALSE";

src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbBytesTypeMapping.cs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,16 @@ public class YdbBytesTypeMapping : RelationalTypeMapping
88
{
99
public static YdbBytesTypeMapping Default { get; } = new();
1010

11-
private YdbBytesTypeMapping() : base(
12-
new RelationalTypeMappingParameters(
13-
new CoreTypeMappingParameters(
14-
typeof(byte[]),
15-
jsonValueReaderWriter: JsonByteArrayReaderWriter.Instance
16-
),
17-
storeType: "Bytes",
18-
dbType: System.Data.DbType.Binary,
19-
unicode: false
20-
)
21-
)
11+
private YdbBytesTypeMapping() : base("Bytes", typeof(byte[]), System.Data.DbType.Binary,
12+
jsonValueReaderWriter: JsonByteArrayReaderWriter.Instance, unicode: false)
2213
{
2314
}
2415

2516
protected YdbBytesTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters)
2617
{
2718
}
2819

29-
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
30-
=> new YdbBytesTypeMapping(parameters);
20+
protected override YdbBytesTypeMapping Clone(RelationalTypeMappingParameters parameters) => new(parameters);
3121

3222
protected override string GenerateNonNullSqlLiteral(object value)
3323
{

src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbDateOnlyTypeMapping.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ protected YdbDateOnlyTypeMapping(RelationalTypeMappingParameters parameters) : b
2424
{
2525
}
2626

27-
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
28-
=> new YdbDateOnlyTypeMapping(parameters);
27+
protected override YdbDateOnlyTypeMapping Clone(RelationalTypeMappingParameters parameters) => new(parameters);
2928

3029
protected override string GenerateNonNullSqlLiteral(object value)
3130
{

src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbJsonTypeMapping.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
using System.Text;
88
using System.Text.Json;
99
using Microsoft.EntityFrameworkCore.Storage;
10+
using Ydb.Sdk.Ado;
11+
using Ydb.Sdk.Ado.YdbType;
1012

1113
namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
1214

13-
public class YdbJsonTypeMapping : JsonTypeMapping
15+
public class YdbJsonTypeMapping : JsonTypeMapping, IYdbTypeMapping
1416
{
1517
public YdbJsonTypeMapping(string storeType, Type clrType, DbType? dbType) : base(storeType, clrType, dbType)
1618
{
@@ -32,8 +34,7 @@ private static readonly MethodInfo? EncodingGetBytesMethod
3234
private static readonly ConstructorInfo? MemoryStreamConstructor
3335
= typeof(MemoryStream).GetConstructor([typeof(byte[])]);
3436

35-
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
36-
=> new YdbJsonTypeMapping(parameters);
37+
protected override YdbJsonTypeMapping Clone(RelationalTypeMappingParameters parameters) => new(parameters);
3738

3839
public override MethodInfo GetDataReaderMethod() => GetStringMethod;
3940

@@ -72,4 +73,9 @@ public override Expression CustomizeDataReaderExpression(Expression expression)
7273
EncodingGetBytesMethod ?? throw new Exception(),
7374
expression)
7475
);
76+
77+
public YdbDbType YdbDbType => YdbDbType.Json;
78+
79+
protected override void ConfigureParameter(DbParameter parameter) =>
80+
((YdbParameter)parameter).YdbDbType = YdbDbType;
7581
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Collections;
2+
using System.Data.Common;
3+
using Microsoft.EntityFrameworkCore.Storage;
4+
using Ydb.Sdk.Ado;
5+
using Ydb.Sdk.Ado.YdbType;
6+
7+
namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
8+
9+
internal class YdbListTypeMapping(
10+
YdbDbType ydbDbType,
11+
string storeTypeElement
12+
) : RelationalTypeMapping(storeType: $"List<{storeTypeElement}>", typeof(IList))
13+
{
14+
protected override YdbListTypeMapping Clone(RelationalTypeMappingParameters parameters) =>
15+
new(ydbDbType, storeTypeElement);
16+
17+
protected override void ConfigureParameter(DbParameter parameter) =>
18+
((YdbParameter)parameter).YdbDbType = YdbDbType.List | ydbDbType;
19+
}

src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbTextTypeMapping.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33

44
namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
55

6-
public class YdbTextTypeMapping : RelationalTypeMapping
6+
public sealed class YdbTextTypeMapping : RelationalTypeMapping
77
{
88
public static YdbTextTypeMapping Default { get; } = new("Text");
99

10-
public YdbTextTypeMapping(string storeType)
10+
private YdbTextTypeMapping(string storeType)
1111
: base(
1212
new RelationalTypeMappingParameters(
1313
new CoreTypeMappingParameters(
@@ -23,15 +23,15 @@ public YdbTextTypeMapping(string storeType)
2323
{
2424
}
2525

26-
protected YdbTextTypeMapping(RelationalTypeMappingParameters parameters)
26+
private YdbTextTypeMapping(RelationalTypeMappingParameters parameters)
2727
: base(parameters)
2828
{
2929
}
3030

3131
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
3232
=> new YdbTextTypeMapping(parameters);
3333

34-
protected virtual string EscapeSqlLiteral(string literal) => literal.Replace("'", "\\'");
34+
private static string EscapeSqlLiteral(string literal) => literal.Replace("'", "\\'");
3535

3636
protected override string GenerateNonNullSqlLiteral(object value) => $"'{EscapeSqlLiteral((string)value)}'u";
3737
}

src/EFCore.Ydb/src/Storage/Internal/YdbTypeMappingSource.cs

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
44
using System.Data;
5+
using System.Linq;
56
using System.Text.Json;
67
using EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
78
using Microsoft.EntityFrameworkCore.Storage;
9+
using Ydb.Sdk.Ado.YdbType;
810
using Type = System.Type;
911

1012
namespace EntityFrameworkCore.Ydb.Storage.Internal;
@@ -15,6 +17,7 @@ RelationalTypeMappingSourceDependencies relationalDependencies
1517
) : RelationalTypeMappingSource(dependencies, relationalDependencies)
1618
{
1719
private static readonly ConcurrentDictionary<RelationalTypeMappingInfo, RelationalTypeMapping> DecimalCache = new();
20+
private static readonly ConcurrentDictionary<YdbDbType, YdbListTypeMapping> ListMappings = new();
1821

1922
#region Mappings
2023

@@ -125,19 +128,60 @@ RelationalTypeMappingSourceDependencies relationalDependencies
125128
var clrType = mappingInfo.ClrType;
126129
var storeTypeName = mappingInfo.StoreTypeName;
127130

128-
if (storeTypeName is null || !StoreTypeMapping.TryGetValue(storeTypeName, out var mappings))
131+
if (storeTypeName is not null && StoreTypeMapping.TryGetValue(storeTypeName, out var mappings))
129132
{
130-
return clrType is null ? null : ClrTypeMapping.GetValueOrDefault(clrType);
131-
}
133+
// We found the user-specified store type. No CLR type was provided - we're probably
134+
// scaffolding from an existing database, take the first mapping as the default.
135+
if (clrType is null)
136+
{
137+
return mappings[0];
138+
}
132139

133-
foreach (var m in mappings)
134-
{
135-
if (m.ClrType == clrType)
140+
// A CLR type was provided - look for a mapping between the store and CLR types. If not found, fail
141+
// immediately.
142+
foreach (var m in mappings)
136143
{
137-
return m;
144+
if (m.ClrType == clrType)
145+
{
146+
return m;
147+
}
138148
}
139149
}
140150

141151
return clrType is null ? null : ClrTypeMapping.GetValueOrDefault(clrType);
142152
}
153+
154+
public override RelationalTypeMapping? FindMapping(Type type)
155+
{
156+
if (type == typeof(byte[]))
157+
return base.FindMapping(type);
158+
159+
var elementType = type.IsArray
160+
? type.GetElementType()
161+
: type.GetInterfaces()
162+
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>))?
163+
.GetGenericArguments()[0];
164+
165+
if (elementType == null)
166+
return base.FindMapping(type);
167+
168+
elementType = Nullable.GetUnderlyingType(elementType) ?? elementType;
169+
170+
var elementTypeMapping = FindMapping(elementType);
171+
172+
if (elementTypeMapping == null)
173+
return base.FindMapping(type);
174+
175+
var ydbDbType = elementTypeMapping is IYdbTypeMapping ydbTypeMapping
176+
? ydbTypeMapping.YdbDbType
177+
: (elementTypeMapping.DbType ?? DbType.Object).ToYdbDbType();
178+
179+
if (ListMappings.TryGetValue(ydbDbType, out var mapping))
180+
return mapping;
181+
182+
mapping = new YdbListTypeMapping(ydbDbType, elementTypeMapping.StoreType);
183+
ListMappings.TryAdd(ydbDbType, mapping);
184+
185+
return mapping;
186+
}
143187
}

0 commit comments

Comments
 (0)