Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions Extensions/Xtensive.Orm.BulkOperations.Tests/ContainsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.

using System;
using System.Linq;
using NUnit.Framework;
using Xtensive.Orm.BulkOperations.ContainsTestModel;
using Xtensive.Orm.Configuration;
Expand All @@ -17,12 +15,16 @@ public class TagType : Entity
[Field, Key]
public long Id { get; private set; }

[Field]
public Guid Guid { get; private set; }

[Field]
public int ProjectedValueAdjustment { get; set; }

public TagType(Session session, long id)
public TagType(Session session, long id, Guid guid)
:base(session, id)
{
Guid = guid;
}
}
}
Expand All @@ -32,6 +34,7 @@ namespace Xtensive.Orm.BulkOperations.Tests
public class ContainsTest : BulkOperationBaseTest
{
private long[] tagIds;
private Guid[] guids;

protected override DomainConfiguration BuildConfiguration()
{
Expand All @@ -40,13 +43,17 @@ protected override DomainConfiguration BuildConfiguration()
return configuration;
}

private static Guid IntToGuid(int v) =>
new((uint)v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);

protected override void PopulateData()
{
tagIds = Enumerable.Range(0, 100).Select(i => (long) i).ToArray();
guids = tagIds.Select(v => IntToGuid((int)v)).ToArray();
using (var session = Domain.OpenSession())
using (var transaction = session.OpenTransaction()) {
foreach (var id in tagIds.Concat(Enumerable.Repeat(1000, 1).Select(i => (long) i))) {
_ = new TagType(session, id) { ProjectedValueAdjustment = -1 };
_ = new TagType(session, id, IntToGuid((int)id)) { ProjectedValueAdjustment = -1 };
}

transaction.Complete();
Expand Down Expand Up @@ -139,6 +146,23 @@ public void TestManyIds()
Assert.That(session.Query.All<TagType>().Count(t => t.ProjectedValueAdjustment == 2 && t.Id <= 200), Is.EqualTo(100));
Assert.That(session.Query.All<TagType>().Count(t => t.ProjectedValueAdjustment == -1 && t.Id > 700), Is.EqualTo(1));
}

}

[Test]
public void TestManyGuids()
{
using (var session = Domain.OpenSession())
using (var tx = session.OpenTransaction()) {
var ids = guids.Concat(Enumerable.Range(4000, 5000).Select(IntToGuid));
var updatedRows = session.Query.All<TagType>()
.Where(t => t.Guid.In(ids))
.Set(t => t.ProjectedValueAdjustment, 2)
.Update();
Assert.That(updatedRows, Is.EqualTo(100));
Assert.That(session.Query.All<TagType>().Count(t => t.ProjectedValueAdjustment == 2 && t.Id <= 200), Is.EqualTo(100));
Assert.That(session.Query.All<TagType>().Count(t => t.ProjectedValueAdjustment == -1 && t.Id > 700), Is.EqualTo(1));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Xtensive.Core;
using Xtensive.Orm.Linq;
using Xtensive.Orm.Model;
using Xtensive.Orm.Providers;
Expand Down Expand Up @@ -82,7 +78,7 @@ protected override int ExecuteInternal()
#region Non-public methods

private bool CanUseTvp(Type fieldType) =>
(fieldType == typeof(long) || fieldType == typeof(int) || fieldType == typeof(string))
TypeHelper.TypesWithTvpSupport.Contains(fieldType)
&& DomainHandler.Handlers.ProviderInfo.Supports(ProviderFeatures.TableValuedParameters);

protected abstract SqlTableRef GetStatementTable(SqlStatement statement);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
// Created by: Denis Krjuchkov
// Created: 2009.07.07

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

protected override void RegisterCustomMappings(TypeMappingRegistryBuilder builder)
{
base.RegisterCustomMappings(builder);
builder.Add(typeof(List<long>), null, builder.Mapper.BindLongList, null);
builder.Add(typeof(List<Guid>), null, builder.Mapper.BindGuidList, null);
builder.Add(typeof(List<string>), null, builder.Mapper.BindStringList, null);

// As far as SqlGeometry and SqlGeography have no support in .Net Standard
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ public IEnumerator<SqlDataRecord> GetEnumerator()
}
}
} break;
case SqlDbType.UniqueIdentifier: {
SqlMetaData[] metaData = [new("Value", sqlDbType)];
SqlDataRecord record = new(metaData);
HashSet<Guid> added = [];
foreach (var valueObj in tuples.Select(t => t.GetValueOrDefault(0)).Where(o => o != null)) {
Guid castValue = (Guid) valueObj;
if (added.Add(castValue)) {
record.SetSqlGuid(0, castValue);
yield return record;
}
}
} break;
case SqlDbType.NVarChar: {
SqlMetaData[] metaData = [new("Value", sqlDbType, tuples.Max(t => (t.GetValueOrDefault(0) as string)?.Length ?? 20))];
SqlDataRecord record = new(metaData);
Expand Down
66 changes: 35 additions & 31 deletions Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/TypeMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,50 @@
// Created by: Denis Krjuchkov
// Created: 2009.07.02

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using Microsoft.Data.SqlClient;
using Tuple = Xtensive.Tuples.Tuple;

namespace Xtensive.Sql.Drivers.SqlServer.v10
{
internal class TypeMapper(SqlDriver driver) : v09.TypeMapper(driver)
{
public const string
LongListTypeName = "_DO_LongList",
StringListTypeName = "_DO_StringList";
namespace Xtensive.Sql.Drivers.SqlServer.v10;

public override void BindDateTime(DbParameter parameter, object value)
{
parameter.DbType = DbType.DateTime2;
parameter.Value = value ?? DBNull.Value;
}
internal class TypeMapper(SqlDriver driver) : v09.TypeMapper(driver)
{
public const string
LongListTypeName = "_DO_LongList",
GuidListTypeName = "_DO_GuidList",
StringListTypeName = "_DO_StringList";

public override DateTime ReadDateTime(DbDataReader reader, int index)
{
string type = reader.GetDataTypeName(index);
if (type=="time") {
var time = (TimeSpan) reader.GetValue(index);
return new DateTime(time.Ticks / 100);
}
return base.ReadDateTime(reader, index);
}
public override void BindDateTime(DbParameter parameter, object value)
{
parameter.DbType = DbType.DateTime2;
parameter.Value = value ?? DBNull.Value;
}

private static void BindList(DbParameter parameter, object value, SqlDbType sqlDbType)
{
var sqlParameter = (SqlParameter) parameter;
sqlParameter.SqlDbType = SqlDbType.Structured;
sqlParameter.Value = new SqlDataRecordList((List<Tuple>) value, sqlDbType) switch { var o => o.IsEmpty ? null : o };
sqlParameter.TypeName = sqlDbType == SqlDbType.BigInt ? LongListTypeName : StringListTypeName;
public override DateTime ReadDateTime(DbDataReader reader, int index)
{
string type = reader.GetDataTypeName(index);
if (type=="time") {
var time = (TimeSpan) reader.GetValue(index);
return new DateTime(time.Ticks / 100);
}
return base.ReadDateTime(reader, index);
}

public override void BindLongList(DbParameter parameter, object value) => BindList(parameter, value, SqlDbType.BigInt);
public override void BindStringList(DbParameter parameter, object value) => BindList(parameter, value, SqlDbType.NVarChar);
private static void BindList(DbParameter parameter, object value, SqlDbType sqlDbType)
{
var sqlParameter = (SqlParameter) parameter;
sqlParameter.SqlDbType = SqlDbType.Structured;
sqlParameter.Value = new SqlDataRecordList((List<Tuple>) value, sqlDbType) switch { var o => o.IsEmpty ? null : o };
sqlParameter.TypeName =
sqlDbType switch {
SqlDbType.BigInt => LongListTypeName,
SqlDbType.UniqueIdentifier => GuidListTypeName,
_ => StringListTypeName
};
}

public override void BindLongList(DbParameter parameter, object value) => BindList(parameter, value, SqlDbType.BigInt);
public override void BindGuidList(DbParameter parameter, object value) => BindList(parameter, value, SqlDbType.UniqueIdentifier);
public override void BindStringList(DbParameter parameter, object value) => BindList(parameter, value, SqlDbType.NVarChar);
}
9 changes: 5 additions & 4 deletions Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.Include.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ internal protected override SqlProvider VisitInclude(IncludeProvider provider)
&& tableValuedParametersSupported
&& provider.FilteredColumnsExtractionTransform.Descriptor.Count == 1) {
var fieldType = provider.FilteredColumnsExtractionTransform.Descriptor[0];
if (fieldType == WellKnownTypes.Int64 || fieldType == WellKnownTypes.Int32 || fieldType == WellKnownTypes.String) {
if (TypeHelper.TypesWithTvpSupport.Contains(fieldType)) {
tvpType = fieldType;
}
}
Expand Down Expand Up @@ -104,9 +104,10 @@ internal protected override SqlProvider VisitInclude(IncludeProvider provider)
Type tableValuedParameterType,
bool enforceTvp)
{
var tvpMapping = Driver.GetTypeMapping(tableValuedParameterType == WellKnownTypes.String
? typeof(List<string>)
: typeof(List<long>));
var tvpMapping = Driver.GetTypeMapping(
tableValuedParameterType == WellKnownTypes.String ? typeof(List<string>)
: tableValuedParameterType == WellKnownTypes.Guid ? typeof(List<Guid>)
: typeof(List<long>));
QueryRowFilterParameterBinding binding = new(mappings, valueAccessor, tvpMapping, enforceTvp);
return (SqlDml.TvpDynamicFilter(binding, provider.FilteredColumns.Select(index => sourceColumns[index]).ToArray()), binding);
}
Expand Down
4 changes: 4 additions & 0 deletions Orm/Xtensive.Orm/Reflection/TypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
Expand Down Expand Up @@ -1280,5 +1281,8 @@ private static string CorrectGenericSuffix(string typeName, int argumentCount)
}

#endregion


public static readonly FrozenSet<Type> TypesWithTvpSupport = [typeof(int), typeof(long), typeof(Guid), typeof(string)];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably also add support for int type separately from long. Otherwise there is conversion operation involved in SQL runtime to convert from one to another, and performance of such queries can be much worse.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For general purposes, yes
But in practice, the app almost never uses int lists.

}
}
3 changes: 0 additions & 3 deletions Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Xtensive.Orm.Linq;
Expand Down
5 changes: 3 additions & 2 deletions Orm/Xtensive.Orm/Sql/ValueTypeMapping/TypeMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,9 @@ public virtual void BindByteArray(DbParameter parameter, object value)
parameter.Value = value ?? DBNull.Value;
}

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

#endregion

Expand Down
2 changes: 1 addition & 1 deletion Version.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<PropertyGroup>
<DoVersion>7.2.189</DoVersion>
<DoVersion>7.2.190</DoVersion>
<DoVersionSuffix>servicetitan</DoVersionSuffix>
</PropertyGroup>

Expand Down