diff --git a/Extensions/Xtensive.Orm.BulkOperations.Tests/ContainsTest.cs b/Extensions/Xtensive.Orm.BulkOperations.Tests/ContainsTest.cs index af0817a24..2464ec503 100644 --- a/Extensions/Xtensive.Orm.BulkOperations.Tests/ContainsTest.cs +++ b/Extensions/Xtensive.Orm.BulkOperations.Tests/ContainsTest.cs @@ -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; @@ -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; } } } @@ -32,6 +34,7 @@ namespace Xtensive.Orm.BulkOperations.Tests public class ContainsTest : BulkOperationBaseTest { private long[] tagIds; + private Guid[] guids; protected override DomainConfiguration BuildConfiguration() { @@ -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(); @@ -139,6 +146,23 @@ public void TestManyIds() Assert.That(session.Query.All().Count(t => t.ProjectedValueAdjustment == 2 && t.Id <= 200), Is.EqualTo(100)); Assert.That(session.Query.All().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() + .Where(t => t.Guid.In(ids)) + .Set(t => t.ProjectedValueAdjustment, 2) + .Update(); + Assert.That(updatedRows, Is.EqualTo(100)); + Assert.That(session.Query.All().Count(t => t.ProjectedValueAdjustment == 2 && t.Id <= 200), Is.EqualTo(100)); + Assert.That(session.Query.All().Count(t => t.ProjectedValueAdjustment == -1 && t.Id > 700), Is.EqualTo(1)); + } } } } diff --git a/Extensions/Xtensive.Orm.BulkOperations/Internals/QueryOperation.cs b/Extensions/Xtensive.Orm.BulkOperations/Internals/QueryOperation.cs index d6e840a23..931818b67 100644 --- a/Extensions/Xtensive.Orm.BulkOperations/Internals/QueryOperation.cs +++ b/Extensions/Xtensive.Orm.BulkOperations/Internals/QueryOperation.cs @@ -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; @@ -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); diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Driver.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Driver.cs index 17226de1a..99abe57df 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Driver.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Driver.cs @@ -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; @@ -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), null, builder.Mapper.BindLongList, null); + builder.Add(typeof(List), null, builder.Mapper.BindGuidList, null); builder.Add(typeof(List), null, builder.Mapper.BindStringList, null); // As far as SqlGeometry and SqlGeography have no support in .Net Standard diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/SqlDataRecordList.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/SqlDataRecordList.cs index 07e050d12..d1207cb0a 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/SqlDataRecordList.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/SqlDataRecordList.cs @@ -45,6 +45,18 @@ public IEnumerator GetEnumerator() } } } break; + case SqlDbType.UniqueIdentifier: { + SqlMetaData[] metaData = [new("Value", sqlDbType)]; + SqlDataRecord record = new(metaData); + HashSet 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); diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/TypeMapper.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/TypeMapper.cs index e1871cbc6..0610c3a5c 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/TypeMapper.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/TypeMapper.cs @@ -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) 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) 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); } diff --git a/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.Include.cs b/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.Include.cs index 4ad7b3d25..2fe9fd30a 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.Include.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.Include.cs @@ -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; } } @@ -104,9 +104,10 @@ internal protected override SqlProvider VisitInclude(IncludeProvider provider) Type tableValuedParameterType, bool enforceTvp) { - var tvpMapping = Driver.GetTypeMapping(tableValuedParameterType == WellKnownTypes.String - ? typeof(List) - : typeof(List)); + var tvpMapping = Driver.GetTypeMapping( + tableValuedParameterType == WellKnownTypes.String ? typeof(List) + : tableValuedParameterType == WellKnownTypes.Guid ? typeof(List) + : typeof(List)); QueryRowFilterParameterBinding binding = new(mappings, valueAccessor, tvpMapping, enforceTvp); return (SqlDml.TvpDynamicFilter(binding, provider.FilteredColumns.Select(index => sourceColumns[index]).ToArray()), binding); } diff --git a/Orm/Xtensive.Orm/Reflection/TypeHelper.cs b/Orm/Xtensive.Orm/Reflection/TypeHelper.cs index ad1dcfda2..db221d7b4 100644 --- a/Orm/Xtensive.Orm/Reflection/TypeHelper.cs +++ b/Orm/Xtensive.Orm/Reflection/TypeHelper.cs @@ -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; @@ -1280,5 +1281,8 @@ private static string CorrectGenericSuffix(string typeName, int argumentCount) } #endregion + + + public static readonly FrozenSet TypesWithTvpSupport = [typeof(int), typeof(long), typeof(Guid), typeof(string)]; } } diff --git a/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs b/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs index 82ad10f09..59d82ae72 100644 --- a/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs +++ b/Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs @@ -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; diff --git a/Orm/Xtensive.Orm/Sql/ValueTypeMapping/TypeMapper.cs b/Orm/Xtensive.Orm/Sql/ValueTypeMapping/TypeMapper.cs index a49c0715e..e7cbcf460 100644 --- a/Orm/Xtensive.Orm/Sql/ValueTypeMapping/TypeMapper.cs +++ b/Orm/Xtensive.Orm/Sql/ValueTypeMapping/TypeMapper.cs @@ -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 diff --git a/Version.props b/Version.props index 057be5e6f..bce2916db 100644 --- a/Version.props +++ b/Version.props @@ -2,7 +2,7 @@ - 7.2.189 + 7.2.190 servicetitan