Skip to content

Commit 1cba370

Browse files
authored
Support Guid[] as TVP parameters (#405)
* Support `Guid[]` as TVP parameters * Guid has no length * reformat * Move TypesWithTvpSupport * Bump version * Change version
1 parent fbf7484 commit 1cba370

File tree

10 files changed

+92
-53
lines changed

10 files changed

+92
-53
lines changed

Extensions/Xtensive.Orm.BulkOperations.Tests/ContainsTest.cs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// This code is distributed under MIT license terms.
33
// See the License.txt file in the project root for more information.
44

5-
using System;
6-
using System.Linq;
75
using NUnit.Framework;
86
using Xtensive.Orm.BulkOperations.ContainsTestModel;
97
using Xtensive.Orm.Configuration;
@@ -17,12 +15,16 @@ public class TagType : Entity
1715
[Field, Key]
1816
public long Id { get; private set; }
1917

18+
[Field]
19+
public Guid Guid { get; private set; }
20+
2021
[Field]
2122
public int ProjectedValueAdjustment { get; set; }
2223

23-
public TagType(Session session, long id)
24+
public TagType(Session session, long id, Guid guid)
2425
:base(session, id)
2526
{
27+
Guid = guid;
2628
}
2729
}
2830
}
@@ -32,6 +34,7 @@ namespace Xtensive.Orm.BulkOperations.Tests
3234
public class ContainsTest : BulkOperationBaseTest
3335
{
3436
private long[] tagIds;
37+
private Guid[] guids;
3538

3639
protected override DomainConfiguration BuildConfiguration()
3740
{
@@ -40,13 +43,17 @@ protected override DomainConfiguration BuildConfiguration()
4043
return configuration;
4144
}
4245

46+
private static Guid IntToGuid(int v) =>
47+
new((uint)v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
48+
4349
protected override void PopulateData()
4450
{
4551
tagIds = Enumerable.Range(0, 100).Select(i => (long) i).ToArray();
52+
guids = tagIds.Select(v => IntToGuid((int)v)).ToArray();
4653
using (var session = Domain.OpenSession())
4754
using (var transaction = session.OpenTransaction()) {
4855
foreach (var id in tagIds.Concat(Enumerable.Repeat(1000, 1).Select(i => (long) i))) {
49-
_ = new TagType(session, id) { ProjectedValueAdjustment = -1 };
56+
_ = new TagType(session, id, IntToGuid((int)id)) { ProjectedValueAdjustment = -1 };
5057
}
5158

5259
transaction.Complete();
@@ -139,6 +146,23 @@ public void TestManyIds()
139146
Assert.That(session.Query.All<TagType>().Count(t => t.ProjectedValueAdjustment == 2 && t.Id <= 200), Is.EqualTo(100));
140147
Assert.That(session.Query.All<TagType>().Count(t => t.ProjectedValueAdjustment == -1 && t.Id > 700), Is.EqualTo(1));
141148
}
149+
150+
}
151+
152+
[Test]
153+
public void TestManyGuids()
154+
{
155+
using (var session = Domain.OpenSession())
156+
using (var tx = session.OpenTransaction()) {
157+
var ids = guids.Concat(Enumerable.Range(4000, 5000).Select(IntToGuid));
158+
var updatedRows = session.Query.All<TagType>()
159+
.Where(t => t.Guid.In(ids))
160+
.Set(t => t.ProjectedValueAdjustment, 2)
161+
.Update();
162+
Assert.That(updatedRows, Is.EqualTo(100));
163+
Assert.That(session.Query.All<TagType>().Count(t => t.ProjectedValueAdjustment == 2 && t.Id <= 200), Is.EqualTo(100));
164+
Assert.That(session.Query.All<TagType>().Count(t => t.ProjectedValueAdjustment == -1 && t.Id > 700), Is.EqualTo(1));
165+
}
142166
}
143167
}
144168
}

Extensions/Xtensive.Orm.BulkOperations/Internals/QueryOperation.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22
// This code is distributed under MIT license terms.
33
// See the License.txt file in the project root for more information.
44

5-
using System;
6-
using System.Collections.Generic;
7-
using System.Linq;
85
using System.Linq.Expressions;
9-
using Xtensive.Core;
106
using Xtensive.Orm.Linq;
117
using Xtensive.Orm.Model;
128
using Xtensive.Orm.Providers;
@@ -82,7 +78,7 @@ protected override int ExecuteInternal()
8278
#region Non-public methods
8379

8480
private bool CanUseTvp(Type fieldType) =>
85-
(fieldType == typeof(long) || fieldType == typeof(int) || fieldType == typeof(string))
81+
TypeHelper.TypesWithTvpSupport.Contains(fieldType)
8682
&& DomainHandler.Handlers.ProviderInfo.Supports(ProviderFeatures.TableValuedParameters);
8783

8884
protected abstract SqlTableRef GetStatementTable(SqlStatement statement);

Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Driver.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
// Created by: Denis Krjuchkov
55
// Created: 2009.07.07
66

7-
using System;
8-
using System.Collections.Generic;
9-
using System.Threading.Tasks;
107
using Xtensive.Sql.Compiler;
118
using Xtensive.Sql.Info;
129
using ISqlExecutor = Xtensive.Orm.Providers.ISqlExecutor;
@@ -28,12 +25,15 @@ IF NOT EXISTS(SELECT 1 FROM sys.types WHERE name = '{TypeMapper.LongListTypeName
2825
CREATE TYPE [{TypeMapper.LongListTypeName}] AS TABLE ([Value] BIGINT NOT NULL PRIMARY KEY);
2926
IF NOT EXISTS(SELECT 1 FROM sys.types WHERE name = '{TypeMapper.StringListTypeName}')
3027
CREATE TYPE [{TypeMapper.StringListTypeName}] AS TABLE ([Value] NVARCHAR(256) NOT NULL PRIMARY KEY);
28+
IF NOT EXISTS(SELECT 1 FROM sys.types WHERE name = '{TypeMapper.GuidListTypeName}')
29+
CREATE TYPE [{TypeMapper.GuidListTypeName}] AS TABLE ([Value] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY);
3130
""");
3231

3332
protected override void RegisterCustomMappings(TypeMappingRegistryBuilder builder)
3433
{
3534
base.RegisterCustomMappings(builder);
3635
builder.Add(typeof(List<long>), null, builder.Mapper.BindLongList, null);
36+
builder.Add(typeof(List<Guid>), null, builder.Mapper.BindGuidList, null);
3737
builder.Add(typeof(List<string>), null, builder.Mapper.BindStringList, null);
3838

3939
// As far as SqlGeometry and SqlGeography have no support in .Net Standard

Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/SqlDataRecordList.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ public IEnumerator<SqlDataRecord> GetEnumerator()
4545
}
4646
}
4747
} break;
48+
case SqlDbType.UniqueIdentifier: {
49+
SqlMetaData[] metaData = [new("Value", sqlDbType)];
50+
SqlDataRecord record = new(metaData);
51+
HashSet<Guid> added = [];
52+
foreach (var valueObj in tuples.Select(t => t.GetValueOrDefault(0)).Where(o => o != null)) {
53+
Guid castValue = (Guid) valueObj;
54+
if (added.Add(castValue)) {
55+
record.SetSqlGuid(0, castValue);
56+
yield return record;
57+
}
58+
}
59+
} break;
4860
case SqlDbType.NVarChar: {
4961
SqlMetaData[] metaData = [new("Value", sqlDbType, tuples.Max(t => (t.GetValueOrDefault(0) as string)?.Length ?? 20))];
5062
SqlDataRecord record = new(metaData);

Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/TypeMapper.cs

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,50 @@
44
// Created by: Denis Krjuchkov
55
// Created: 2009.07.02
66

7-
using System;
8-
using System.Collections.Generic;
97
using System.Data;
108
using System.Data.Common;
119
using Microsoft.Data.SqlClient;
1210
using Tuple = Xtensive.Tuples.Tuple;
1311

14-
namespace Xtensive.Sql.Drivers.SqlServer.v10
15-
{
16-
internal class TypeMapper(SqlDriver driver) : v09.TypeMapper(driver)
17-
{
18-
public const string
19-
LongListTypeName = "_DO_LongList",
20-
StringListTypeName = "_DO_StringList";
12+
namespace Xtensive.Sql.Drivers.SqlServer.v10;
2113

22-
public override void BindDateTime(DbParameter parameter, object value)
23-
{
24-
parameter.DbType = DbType.DateTime2;
25-
parameter.Value = value ?? DBNull.Value;
26-
}
14+
internal class TypeMapper(SqlDriver driver) : v09.TypeMapper(driver)
15+
{
16+
public const string
17+
LongListTypeName = "_DO_LongList",
18+
GuidListTypeName = "_DO_GuidList",
19+
StringListTypeName = "_DO_StringList";
2720

28-
public override DateTime ReadDateTime(DbDataReader reader, int index)
29-
{
30-
string type = reader.GetDataTypeName(index);
31-
if (type=="time") {
32-
var time = (TimeSpan) reader.GetValue(index);
33-
return new DateTime(time.Ticks / 100);
34-
}
35-
return base.ReadDateTime(reader, index);
36-
}
21+
public override void BindDateTime(DbParameter parameter, object value)
22+
{
23+
parameter.DbType = DbType.DateTime2;
24+
parameter.Value = value ?? DBNull.Value;
25+
}
3726

38-
private static void BindList(DbParameter parameter, object value, SqlDbType sqlDbType)
39-
{
40-
var sqlParameter = (SqlParameter) parameter;
41-
sqlParameter.SqlDbType = SqlDbType.Structured;
42-
sqlParameter.Value = new SqlDataRecordList((List<Tuple>) value, sqlDbType) switch { var o => o.IsEmpty ? null : o };
43-
sqlParameter.TypeName = sqlDbType == SqlDbType.BigInt ? LongListTypeName : StringListTypeName;
27+
public override DateTime ReadDateTime(DbDataReader reader, int index)
28+
{
29+
string type = reader.GetDataTypeName(index);
30+
if (type=="time") {
31+
var time = (TimeSpan) reader.GetValue(index);
32+
return new DateTime(time.Ticks / 100);
4433
}
34+
return base.ReadDateTime(reader, index);
35+
}
4536

46-
public override void BindLongList(DbParameter parameter, object value) => BindList(parameter, value, SqlDbType.BigInt);
47-
public override void BindStringList(DbParameter parameter, object value) => BindList(parameter, value, SqlDbType.NVarChar);
37+
private static void BindList(DbParameter parameter, object value, SqlDbType sqlDbType)
38+
{
39+
var sqlParameter = (SqlParameter) parameter;
40+
sqlParameter.SqlDbType = SqlDbType.Structured;
41+
sqlParameter.Value = new SqlDataRecordList((List<Tuple>) value, sqlDbType) switch { var o => o.IsEmpty ? null : o };
42+
sqlParameter.TypeName =
43+
sqlDbType switch {
44+
SqlDbType.BigInt => LongListTypeName,
45+
SqlDbType.UniqueIdentifier => GuidListTypeName,
46+
_ => StringListTypeName
47+
};
4848
}
49+
50+
public override void BindLongList(DbParameter parameter, object value) => BindList(parameter, value, SqlDbType.BigInt);
51+
public override void BindGuidList(DbParameter parameter, object value) => BindList(parameter, value, SqlDbType.UniqueIdentifier);
52+
public override void BindStringList(DbParameter parameter, object value) => BindList(parameter, value, SqlDbType.NVarChar);
4953
}

Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.Include.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ internal protected override SqlProvider VisitInclude(IncludeProvider provider)
3939
&& tableValuedParametersSupported
4040
&& provider.FilteredColumnsExtractionTransform.Descriptor.Count == 1) {
4141
var fieldType = provider.FilteredColumnsExtractionTransform.Descriptor[0];
42-
if (fieldType == WellKnownTypes.Int64 || fieldType == WellKnownTypes.Int32 || fieldType == WellKnownTypes.String) {
42+
if (TypeHelper.TypesWithTvpSupport.Contains(fieldType)) {
4343
tvpType = fieldType;
4444
}
4545
}
@@ -104,9 +104,10 @@ internal protected override SqlProvider VisitInclude(IncludeProvider provider)
104104
Type tableValuedParameterType,
105105
bool enforceTvp)
106106
{
107-
var tvpMapping = Driver.GetTypeMapping(tableValuedParameterType == WellKnownTypes.String
108-
? typeof(List<string>)
109-
: typeof(List<long>));
107+
var tvpMapping = Driver.GetTypeMapping(
108+
tableValuedParameterType == WellKnownTypes.String ? typeof(List<string>)
109+
: tableValuedParameterType == WellKnownTypes.Guid ? typeof(List<Guid>)
110+
: typeof(List<long>));
110111
QueryRowFilterParameterBinding binding = new(mappings, valueAccessor, tvpMapping, enforceTvp);
111112
return (SqlDml.TvpDynamicFilter(binding, provider.FilteredColumns.Select(index => sourceColumns[index]).ToArray()), binding);
112113
}

Orm/Xtensive.Orm/Reflection/TypeHelper.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
using System.Collections;
88
using System.Collections.Concurrent;
9+
using System.Collections.Frozen;
910
using System.Diagnostics;
1011
using System.Reflection;
1112
using System.Reflection.Emit;
@@ -1280,5 +1281,8 @@ private static string CorrectGenericSuffix(string typeName, int argumentCount)
12801281
}
12811282

12821283
#endregion
1284+
1285+
1286+
public static readonly FrozenSet<Type> TypesWithTvpSupport = [typeof(int), typeof(long), typeof(Guid), typeof(string)];
12831287
}
12841288
}

Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
// This code is distributed under MIT license terms.
33
// See the License.txt file in the project root for more information.
44

5-
using System;
6-
using System.Collections.Generic;
7-
using System.Linq;
85
using System.Linq.Expressions;
96
using System.Reflection;
107
using Xtensive.Orm.Linq;

Orm/Xtensive.Orm/Sql/ValueTypeMapping/TypeMapper.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,9 @@ public virtual void BindByteArray(DbParameter parameter, object value)
172172
parameter.Value = value ?? DBNull.Value;
173173
}
174174

175-
public virtual void BindLongList(DbParameter parameter, object value) => throw new NotSupportedException("Table-Valued Paramenters not supported");
176-
public virtual void BindStringList(DbParameter parameter, object value) => throw new NotSupportedException("Table-Valued Paramenters not supported");
175+
public virtual void BindLongList(DbParameter parameter, object value) => throw new NotSupportedException("Table-Valued Parameters not supported");
176+
public virtual void BindGuidList(DbParameter parameter, object value) => throw new NotSupportedException("Table-Valued Parameters not supported");
177+
public virtual void BindStringList(DbParameter parameter, object value) => throw new NotSupportedException("Table-Valued Parameters not supported");
177178

178179
#endregion
179180

Version.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33

44
<PropertyGroup>
5-
<DoVersion>7.2.189</DoVersion>
5+
<DoVersion>7.2.190</DoVersion>
66
<DoVersionSuffix>servicetitan</DoVersionSuffix>
77
</PropertyGroup>
88

0 commit comments

Comments
 (0)