Skip to content

Commit cc0cb81

Browse files
authored
Merge pull request #313 from DataObjects-NET/master-defaultexpression-support
DefaultExpression support in Linq
2 parents da68a4f + a3521bf commit cc0cb81

File tree

11 files changed

+366
-43
lines changed

11 files changed

+366
-43
lines changed

ChangeLog/7.1.0-RC2-dev.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[main] Added support for DefaultExpression within Linq queries

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
// Copyright (C) 2019-2020 Xtensive LLC.
1+
// Copyright (C) 2019-2023 Xtensive LLC.
22
// This code is distributed under MIT license terms.
33
// See the License.txt file in the project root for more information.
44

55
using System;
66
using System.Collections.Generic;
77
using System.Collections.ObjectModel;
88
using System.Linq.Expressions;
9+
using Xtensive.Core;
910
using Xtensive.Linq;
1011

1112
namespace Xtensive.Orm.BulkOperations
@@ -95,6 +96,8 @@ protected virtual Expression Visit(Expression exp)
9596
return VisitConditional((ConditionalExpression) exp);
9697
case ExpressionType.Constant:
9798
return VisitConstant((ConstantExpression) exp);
99+
case ExpressionType.Default:
100+
return VisitDefault((DefaultExpression) exp);
98101
case ExpressionType.Parameter:
99102
return VisitParameter((ParameterExpression) exp);
100103
case ExpressionType.MemberAccess:
@@ -180,6 +183,11 @@ private Expression VisitConstant(ConstantExpression c)
180183
return c;
181184
}
182185

186+
private Expression VisitDefault(DefaultExpression d)
187+
{
188+
return d.ToConstantExpression();
189+
}
190+
183191
private ElementInit VisitElementInitializer(ElementInit initializer)
184192
{
185193
IEnumerable<Expression> arguments = VisitExpressionList(initializer.Arguments);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
// Copyright (C) 2023 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
4+
5+
using System;
6+
using System.Linq;
7+
using System.Linq.Expressions;
8+
using NUnit.Framework;
9+
using Xtensive.Core;
10+
using Xtensive.Orm.Configuration;
11+
using Xtensive.Orm.Tests.Issues.IssueGithub312_DefaultExpressionProblemModel;
12+
13+
namespace Xtensive.Orm.Tests.Issues
14+
{
15+
namespace IssueGithub312_DefaultExpressionProblemModel
16+
{
17+
[HierarchyRoot]
18+
public sealed class TestEntity : Entity
19+
{
20+
[Field, Key]
21+
public int Id { get; private set; }
22+
23+
[Field]
24+
public string Name { get; set; }
25+
26+
[Field]
27+
public int Value { get; set; }
28+
29+
public TestEntity(Session session)
30+
: base(session)
31+
{
32+
}
33+
}
34+
}
35+
36+
public class IssueGithub312_DefaultExpressionProblem : AutoBuildTest
37+
{
38+
protected override DomainConfiguration BuildConfiguration()
39+
{
40+
var configuration = base.BuildConfiguration();
41+
configuration.Types.Register(typeof(TestEntity));
42+
configuration.UpgradeMode = DomainUpgradeMode.Recreate;
43+
return configuration;
44+
}
45+
46+
protected override void PopulateData()
47+
{
48+
using (var session = Domain.OpenSession())
49+
using (var tx = session.OpenTransaction()) {
50+
_ = new TestEntity(session) { Name = null, Value = 0 };
51+
_ = new TestEntity(session) { Name = string.Empty, Value = 0 };
52+
_ = new TestEntity(session) { Name = "-2", Value = -2 };
53+
_ = new TestEntity(session) { Name = "-1", Value = -1 };
54+
_ = new TestEntity(session) { Name = "1", Value = 1 };
55+
_ = new TestEntity(session) { Name = "2", Value = 2 };
56+
tx.Complete();
57+
}
58+
}
59+
60+
[Test]
61+
public void DefaultOfObject()
62+
{
63+
using (var session = Domain.OpenSession())
64+
using (var tx = session.OpenTransaction()) {
65+
var param = Expression.Parameter(typeof(TestEntity), "o");
66+
var lambda = Expression.Lambda<Func<TestEntity, bool>>(
67+
Expression.Equal(
68+
Expression.Property(param, nameof(TestEntity.Name)),
69+
Expression.Default(typeof(object))),
70+
new[] { param });
71+
72+
Assert.That(session.Query.All<TestEntity>().Where(lambda).Count(), Is.EqualTo(1));
73+
74+
param = Expression.Parameter(typeof(TestEntity), "o");
75+
lambda = Expression.Lambda<Func<TestEntity, bool>>(
76+
Expression.NotEqual(
77+
Expression.Property(param, nameof(TestEntity.Name)),
78+
Expression.Default(typeof(object))),
79+
new[] { param });
80+
81+
Assert.That(session.Query.All<TestEntity>().Where(lambda).Count(), Is.EqualTo(5));
82+
}
83+
}
84+
85+
[Test]
86+
public void DefaultOfValueType()
87+
{
88+
using (var session = Domain.OpenSession())
89+
using (var tx = session.OpenTransaction()) {
90+
var param = Expression.Parameter(typeof(TestEntity), "o");
91+
var lambda = Expression.Lambda<Func<TestEntity, bool>>(
92+
Expression.Equal(
93+
Expression.Property(param, nameof(TestEntity.Value)),
94+
Expression.Default(typeof(int))),
95+
new[] { param });
96+
97+
Assert.That(session.Query.All<TestEntity>().Where(lambda).Count(), Is.EqualTo(2));
98+
99+
param = Expression.Parameter(typeof(TestEntity), "o");
100+
lambda = Expression.Lambda<Func<TestEntity, bool>>(
101+
Expression.NotEqual(
102+
Expression.Property(param, nameof(TestEntity.Value)),
103+
Expression.Default(typeof(int))),
104+
new[] { param });
105+
106+
Assert.That(session.Query.All<TestEntity>().Where(lambda).Count(), Is.EqualTo(4));
107+
}
108+
}
109+
110+
[Test]
111+
public void DefaultOfReferenceType()
112+
{
113+
using (var session = Domain.OpenSession())
114+
using (var tx = session.OpenTransaction()) {
115+
var param = Expression.Parameter(typeof(TestEntity), "o");
116+
var lambda = Expression.Lambda<Func<TestEntity, bool>>(
117+
Expression.Equal(
118+
Expression.Property(param, nameof(TestEntity.Name)),
119+
Expression.Default(typeof(string))),
120+
new[] { param });
121+
122+
Assert.That(session.Query.All<TestEntity>().Where(lambda).Count(), Is.EqualTo(1));
123+
124+
param = Expression.Parameter(typeof(TestEntity), "o");
125+
lambda = Expression.Lambda<Func<TestEntity, bool>>(
126+
Expression.NotEqual(
127+
Expression.Property(param, nameof(TestEntity.Name)),
128+
Expression.Default(typeof(string))),
129+
new[] { param });
130+
131+
Assert.That(session.Query.All<TestEntity>().Where(lambda).Count(), Is.EqualTo(5));
132+
}
133+
}
134+
135+
[Test]
136+
public void ConstantNullObjectValue()
137+
{
138+
using (var session = Domain.OpenSession())
139+
using (var tx = session.OpenTransaction()) {
140+
var param = Expression.Parameter(typeof(TestEntity), "o");
141+
var lambda = Expression.Lambda<Func<TestEntity, bool>>(
142+
Expression.Equal(
143+
Expression.Property(param, nameof(TestEntity.Name)),
144+
Expression.Constant((object) null)),
145+
new[] { param });
146+
147+
Assert.That(session.Query.All<TestEntity>().Where(lambda).Count(), Is.EqualTo(1));
148+
149+
param = Expression.Parameter(typeof(TestEntity), "o");
150+
lambda = Expression.Lambda<Func<TestEntity, bool>>(
151+
Expression.NotEqual(
152+
Expression.Property(param, nameof(TestEntity.Name)),
153+
Expression.Constant((object) null)),
154+
new[] { param });
155+
156+
Assert.That(session.Query.All<TestEntity>().Where(lambda).Count(), Is.EqualTo(5));
157+
}
158+
}
159+
160+
[Test]
161+
public void ConstantValueTypeDefalut()
162+
{
163+
using (var session = Domain.OpenSession())
164+
using (var tx = session.OpenTransaction()) {
165+
var param = Expression.Parameter(typeof(TestEntity), "o");
166+
var lambda = Expression.Lambda<Func<TestEntity, bool>>(
167+
Expression.Equal(
168+
Expression.Property(param, nameof(TestEntity.Value)),
169+
Expression.Constant(0)),
170+
new[] { param });
171+
172+
Assert.That(session.Query.All<TestEntity>().Where(lambda).Count(), Is.EqualTo(2));
173+
174+
param = Expression.Parameter(typeof(TestEntity), "o");
175+
lambda = Expression.Lambda<Func<TestEntity, bool>>(
176+
Expression.NotEqual(
177+
Expression.Property(param, nameof(TestEntity.Value)),
178+
Expression.Constant(0)),
179+
new[] { param });
180+
181+
Assert.That(session.Query.All<TestEntity>().Where(lambda).Count(), Is.EqualTo(4));
182+
}
183+
}
184+
185+
[Test]
186+
public void ConstantNullStringValue()
187+
{
188+
using (var session = Domain.OpenSession())
189+
using (var tx = session.OpenTransaction()) {
190+
var param = Expression.Parameter(typeof(TestEntity), "o");
191+
var lambda = Expression.Lambda<Func<TestEntity, bool>>(
192+
Expression.Equal(
193+
Expression.Property(param, nameof(TestEntity.Name)),
194+
Expression.Constant((string) null)),
195+
new[] { param });
196+
197+
Assert.That(session.Query.All<TestEntity>().Where(lambda).Count(), Is.EqualTo(1));
198+
199+
param = Expression.Parameter(typeof(TestEntity), "o");
200+
lambda = Expression.Lambda<Func<TestEntity, bool>>(
201+
Expression.NotEqual(
202+
Expression.Property(param, nameof(TestEntity.Name)),
203+
Expression.Constant((string) null)),
204+
new[] { param });
205+
206+
Assert.That(session.Query.All<TestEntity>().Where(lambda).Count(), Is.EqualTo(5));
207+
}
208+
}
209+
}
210+
}

Orm/Xtensive.Orm/Core/Extensions/ExpressionExtensions.cs

+35-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2009-2020 Xtensive LLC.
1+
// Copyright (C) 2009-2023 Xtensive LLC.
22
// This code is distributed under MIT license terms.
33
// See the License.txt file in the project root for more information.
44
// Created by: Alexis Kochetov
@@ -12,6 +12,8 @@
1212
using Xtensive.Linq.SerializableExpressions.Internals;
1313

1414
using System.Linq;
15+
using Xtensive.Reflection;
16+
using System.Collections.Concurrent;
1517

1618
namespace Xtensive.Core
1719
{
@@ -20,6 +22,8 @@ namespace Xtensive.Core
2022
/// </summary>
2123
public static class ExpressionExtensions
2224
{
25+
private readonly static ConcurrentDictionary<Type, object> StructDefaultValues = new();
26+
2327
/// <summary>
2428
/// Formats the <paramref name="expression"/>.
2529
/// </summary>
@@ -105,12 +109,41 @@ public static SerializableExpression ToSerializableExpression(this Expression ex
105109
/// Converts specified <see cref="SerializableExpression"/> to <see cref="Expression"/>.
106110
/// </summary>
107111
/// <param name="expression">The expression to convert.</param>
108-
/// <returns></returns>
112+
/// <returns>Expression that represents given <see cref="SerializableExpression"/>.</returns>
109113
public static Expression ToExpression(this SerializableExpression expression)
110114
{
111115
return new SerializableExpressionToExpressionConverter(expression).Convert();
112116
}
113117

118+
/// <summary>
119+
/// Converts <see cref="DefaultExpression"/> to <see cref="ConstantExpression"/>
120+
/// with value of default value of type in the <paramref name="defaultExpression"/>.
121+
/// </summary>
122+
/// <param name="defaultExpression">The expression to convert.</param>
123+
/// <returns>Result constant expression.</returns>
124+
public static ConstantExpression ToConstantExpression(this DefaultExpression defaultExpression)
125+
{
126+
var value = GetDefaultValue(defaultExpression);
127+
128+
return Expression.Constant(value, defaultExpression.Type);
129+
}
130+
131+
/// <summary>
132+
/// Gets the value represented by given <see cref="DefaultExpression"/>.
133+
/// </summary>
134+
/// <param name="defaultExpression">The default value expression.</param>
135+
/// <returns>Object value of default value.</returns>
136+
public static object GetDefaultValue(this DefaultExpression defaultExpression)
137+
{
138+
if (defaultExpression.Type.IsValueType) {
139+
return StructDefaultValues.GetOrAdd<DefaultExpression>(
140+
defaultExpression.Type,
141+
(type, expr) => { return ((Func<object>) Expression.Lambda(Expression.Convert(expr, WellKnownTypes.Object)).Compile()).Invoke(); },
142+
defaultExpression);
143+
}
144+
return null;
145+
}
146+
114147
/// <summary>
115148
/// Gets return type of <see cref="LambdaExpression"/>.
116149
/// This method is used to write code that is compilable on .NET 3.5,

Orm/Xtensive.Orm/Linq/ExpressionVisitor.cs

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
// Copyright (C) 2003-2010 Xtensive LLC.
2-
// All rights reserved.
3-
// For conditions of distribution and use, see license.
1+
// Copyright (C) 2008-2023 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
44
// Created by: Alexey Kochetov
55
// Created: 2008.11.11
66

77
using System;
88
using System.Collections.Generic;
99
using System.Collections.ObjectModel;
1010
using System.Linq.Expressions;
11+
using Xtensive.Core;
1112

1213
namespace Xtensive.Linq
1314
{
@@ -96,6 +97,12 @@ protected override Expression VisitConstant(ConstantExpression c)
9697
return c;
9798
}
9899

100+
/// <inheritdoc/>
101+
protected override Expression VisitDefault(DefaultExpression d)
102+
{
103+
return d.ToConstantExpression();
104+
}
105+
99106
/// <inheritdoc/>
100107
protected override Expression VisitConditional(ConditionalExpression c)
101108
{

Orm/Xtensive.Orm/Linq/ExpressionVisitor{TResult}.cs

+10
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ protected virtual TResult Visit(Expression e)
9696
case ExpressionType.Constant:
9797
result = VisitConstant((ConstantExpression) e);
9898
break;
99+
case ExpressionType.Default:
100+
result = VisitDefault((DefaultExpression) e);
101+
break;
99102
case ExpressionType.Parameter:
100103
result = VisitParameter((ParameterExpression) e);
101104
break;
@@ -189,6 +192,13 @@ protected virtual TResult VisitUnknown(Expression e)
189192
/// <param name="c">The constant expression.</param>
190193
/// <returns>Visit result.</returns>
191194
protected abstract TResult VisitConstant(ConstantExpression c);
195+
196+
/// <summary>
197+
/// Visits the default expression.
198+
/// </summary>
199+
/// <param name="d">The default expression.</param>
200+
/// <returns>Visit result.</returns>
201+
protected abstract TResult VisitDefault(DefaultExpression d);
192202

193203
/// <summary>
194204
/// Visits the conditional expression.

0 commit comments

Comments
 (0)