Skip to content

Commit c523cab

Browse files
authored
Process Span<>/ReadOnlySpan<>/.Contains() cases in LINQ Expression (#328)
* Process `ReadOnlySpan<>.Contains()` case in LINQ espression * Simplify * Fix .NET8 build * Process Span<> case * Correct test-sql.yaml * Bump Version
1 parent 3065d43 commit c523cab

File tree

8 files changed

+49
-17
lines changed

8 files changed

+49
-17
lines changed

.github/workflows/test-sql.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ jobs:
1919
timezoneLinux: "UTC"
2020
- name: Setup .NET
2121
uses: actions/setup-dotnet@v4
22-
with: { dotnet-version: 9 }
22+
with:
23+
dotnet-version: 9.x
2324
- name: Setup MSSQL Tools
2425
uses: rails-sqlserver/setup-mssql@v1
2526
with:

Orm/Xtensive.Orm.Tests/Linq/InTest.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,5 +657,18 @@ where track.Name.In(IncludeAlgorithm.TableValuedParameter, names)
657657
select track
658658
).ToList();
659659
}
660+
661+
// Related to https://github.com/DataObjects-NET/dataobjects-net/issues/402
662+
[Test]
663+
public void ReadOnlySpanContains_Test()
664+
{
665+
var result = ReadOnlySpanContains_GetCustomers(["Michelle", "Jack"]);
666+
Assert.AreEqual(2, result.Count);
667+
Assert.IsTrue(result.Contains("Michelle"));
668+
Assert.IsTrue(result.Contains("Jack"));
669+
}
670+
671+
private List<string> ReadOnlySpanContains_GetCustomers(string[] customerNames) =>
672+
(from c in Session.Query.All<Customer>() where MemoryExtensions.Contains(customerNames, c.FirstName) select c.FirstName).ToList();
660673
}
661674
}

Orm/Xtensive.Orm/Orm/Linq/ExpressionExtensions.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ public static bool IsLocalCollection(this Expression expression, TranslatorConte
7777
var expressionType => !IsEntitySet(expressionType)
7878
&& !expression.IsSubqueryExpression()
7979
&& expressionType != WellKnownTypes.String
80-
&& expressionType.IsOfGenericInterface(WellKnownInterfaces.EnumerableOfT)
80+
&& (expressionType.IsOfGenericInterface(WellKnownInterfaces.EnumerableOfT)
81+
|| expressionType.IsOfGenericType(WellKnownTypes.ReadOnlySpanOfT)
82+
|| expressionType.IsOfGenericType(WellKnownTypes.SpanOfT))
8183
&& (IsEvaluableCollection(context, expression, expressionType) ||
8284
IsForeignQuery(expression))
8385
};
@@ -203,4 +205,4 @@ public static ParameterInfo[] GetConstructorParameters(this NewExpression expres
203205
return expression.Constructor.GetParameters();
204206
}
205207
}
206-
}
208+
}

Orm/Xtensive.Orm/Orm/Linq/QueryHelper.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,8 @@ public static void TryAddConvarianceCast(ref Expression source, Type baseType)
202202

203203
public static Type GetSequenceElementType(Type type)
204204
{
205-
var sequenceType = type.GetGenericInterface(WellKnownInterfaces.EnumerableOfT);
205+
var sequenceType = type.GetGenericInterface(WellKnownInterfaces.EnumerableOfT)
206+
?? (type.IsOfGenericType(WellKnownTypes.ReadOnlySpanOfT) || type.IsOfGenericType(WellKnownTypes.SpanOfT) ? type : null);
206207
return sequenceType?.GetGenericArguments()[0];
207208
}
208209

Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -541,15 +541,20 @@ protected override Expression VisitMethodCall(MethodCallExpression mc)
541541
}
542542
}
543543

544-
545544
// Process local collections
546-
if (mc.Object.IsLocalCollection(context)) {
547-
// IList.Contains
548-
// List.Contains
549-
// Array.Contains
550-
var parameters = method.GetParameters();
551-
if (methodName == nameof(ICollection<int>.Contains) && parameters.Length == 1)
552-
return VisitContains(mc.Object, mc.Arguments[0], false);
545+
if (methodName == nameof(ICollection<int>.Contains)) {
546+
if (mc.Object.IsLocalCollection(context)) {
547+
// IList.Contains
548+
// List.Contains
549+
// Array.Contains
550+
var parameters = method.GetParameters();
551+
if (parameters.Length == 1)
552+
return VisitContains(mc.Object, mc.Arguments[0], false);
553+
}
554+
else if (methodDeclaringType == typeof(MemoryExtensions)) {
555+
// ReadOnlySpan<>.Contains
556+
return VisitContains(mc.Arguments[0], mc.Arguments[1], false);
557+
}
553558
}
554559

555560
var result = base.VisitMethodCall(mc);

Orm/Xtensive.Orm/Orm/Linq/Translator.Queryable.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1767,9 +1767,16 @@ private ProjectionExpression VisitSequence(Expression sequenceExpression, Expres
17671767
string.Format(Strings.ExExpressionXIsNotASequence, expressionPart.ToString(true)));
17681768
}
17691769

1770-
private ProjectionExpression VisitLocalCollectionSequence<TItem>(Expression sequence) =>
1771-
CreateLocalCollectionProjectionExpression(typeof(TItem), ParameterAccessorFactory.CreateAccessorExpression<IEnumerable<TItem>>(
1770+
private ProjectionExpression VisitLocalCollectionSequence<TItem>(Expression sequence)
1771+
{
1772+
var type = sequence.Type;
1773+
if (type.IsOfGenericType(WellKnownTypes.ReadOnlySpanOfT) || type.IsOfGenericType(WellKnownTypes.SpanOfT)) {
1774+
sequence = Expression.Call(sequence, type.GetMethod("ToArray"));
1775+
}
1776+
1777+
return CreateLocalCollectionProjectionExpression(typeof(TItem), ParameterAccessorFactory.CreateAccessorExpression<IEnumerable<TItem>>(
17721778
compiledQueryScope is not null ? compiledQueryScope.QueryParameterReplacer.Replace(sequence) : sequence).CachingCompile(), this, sequence);
1779+
}
17731780

17741781
private Expression VisitContainsAny(Expression setA, Expression setB, bool isRoot, Type elementType)
17751782
{

Orm/Xtensive.Orm/Reflection/WellKnownTypes.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,8 @@ internal static class WellKnownTypes
8181
public static readonly Type ObjectArray = typeof(object[]);
8282

8383
public static readonly Type DefaultMemberAttribute = typeof(DefaultMemberAttribute);
84+
85+
public static readonly Type ReadOnlySpanOfT = typeof(ReadOnlySpan<>);
86+
public static readonly Type SpanOfT = typeof(Span<>);
8487
}
85-
}
88+
}

Version.props

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

44
<PropertyGroup>
5-
<DoVersion>7.2.160</DoVersion>
6-
<DoVersionSuffix>servicetitan</DoVersionSuffix>
5+
<DoVersion>7.2.161</DoVersion>
6+
<DoVersionSuffix>servicetitan-span</DoVersionSuffix>
77
</PropertyGroup>
88

99
</Project>

0 commit comments

Comments
 (0)