Skip to content

Commit aa87a59

Browse files
committed
Fix cache key compilation
1 parent 939ba15 commit aa87a59

3 files changed

Lines changed: 239 additions & 8 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using NUnit.Framework;
4+
5+
namespace ServiceStack.OrmLite.Tests.Issues;
6+
7+
public class UpdateIssueTests
8+
{
9+
[Test]
10+
public async Task Can_update()
11+
{
12+
var dbFactory = new OrmLiteConnectionFactory(
13+
":memory:",
14+
SqliteDialect.Provider);
15+
16+
using var db = await dbFactory.OpenDbConnectionAsync();
17+
18+
db.DropAndCreateTable<Entity1>();
19+
db.DropAndCreateTable<Entity2>();
20+
21+
var entityId = 1;
22+
var now = DateTime.UtcNow.AddMinutes(-10);
23+
24+
await db.InsertAsync(new Entity1
25+
{
26+
Id = entityId,
27+
Count = 10,
28+
ModifiedDate = now
29+
});
30+
31+
await db.InsertAsync(new Entity2
32+
{
33+
Id = 1,
34+
Entity1Id = entityId,
35+
Count = 20,
36+
ModifiedDate = now
37+
});
38+
39+
await db.UpdateAddAsync(
40+
() => new Entity1 { Count = 5 },
41+
where: p => p.Id == entityId);
42+
43+
await db.UpdateAddAsync(
44+
() => new Entity2 { Count = 5 },
45+
where: p => p.Entity1Id == entityId);
46+
47+
var updatedEntity1 = await db.SingleByIdAsync<Entity1>(entityId);
48+
var updatedEntity2 = await db.SingleAsync<Entity2>(x => x.Entity1Id == entityId);
49+
}
50+
51+
public class Entity1
52+
{
53+
public int Id { get; set; }
54+
55+
public int Count { get; set; }
56+
57+
public DateTime ModifiedDate { get; set; }
58+
}
59+
60+
public class Entity2
61+
{
62+
public int Id { get; set; }
63+
64+
public int Entity1Id { get; set; }
65+
66+
public int Count { get; set; }
67+
68+
public DateTime ModifiedDate { get; set; }
69+
}
70+
71+
}

ServiceStack/src/ServiceStack.Common/CachedExpressionCompiler.cs

Lines changed: 129 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,40 @@ private static string Hash(string input)
9393

9494
private sealed class CanonicalExpressionPrinter(StringBuilder sb) : ExpressionVisitor
9595
{
96+
public override Expression Visit(Expression node)
97+
{
98+
if (node == null)
99+
{
100+
sb.Append("NULL");
101+
return null;
102+
}
103+
104+
sb.Append("NODE(");
105+
sb.Append(node.NodeType);
106+
sb.Append(":");
107+
AppendType(node.Type);
108+
sb.Append(")");
109+
110+
return base.Visit(node);
111+
}
112+
96113
protected override Expression VisitConstant(ConstantExpression node)
97114
{
98115
// Remove closure object identity
99116
if (node.Type.Name.Contains("DisplayClass"))
100117
{
101-
sb.Append($"CONST(closure:{node.Type.FullName})");
118+
sb.Append("CONST(closure:");
119+
AppendType(node.Type);
120+
sb.Append(")");
102121
return node;
103122
}
104123

105124
// Normal constants
106-
sb.Append($"CONST({node.Value})");
125+
sb.Append("CONST(");
126+
sb.Append(node.Value);
127+
sb.Append(":");
128+
AppendType(node.Type);
129+
sb.Append(")");
107130
return node;
108131
}
109132

@@ -114,38 +137,136 @@ protected override Expression VisitMember(MemberExpression node)
114137
{
115138
// Include the closure declaring type + member type to handle array/ref struct differences
116139
sb.Append("CLOSURE_MEMBER(");
117-
sb.Append(c.Type.FullName);
140+
AppendType(c.Type);
118141
sb.Append(".");
119142
sb.Append(node.Member.Name);
120143
sb.Append(":");
121-
sb.Append(node.Type.FullName);
144+
AppendType(node.Type);
122145
sb.Append(")");
123146
return node;
124147
}
125-
sb.Append($"MEMBER({node.Member.DeclaringType}.{node.Member.Name}:{node.Type.FullName})");
148+
sb.Append("MEMBER(");
149+
AppendType(node.Member.DeclaringType);
150+
sb.Append(".");
151+
sb.Append(node.Member.Name);
152+
sb.Append(":");
153+
AppendType(node.Type);
154+
sb.Append(")");
126155
return base.VisitMember(node);
127156
}
128157

129158
protected override Expression VisitLambda<T>(Expression<T> node)
130159
{
131160
sb.Append("LAMBDA(");
132161
foreach (var p in node.Parameters)
133-
sb.Append(p.Type.FullName + ";");
162+
{
163+
AppendType(p.Type);
164+
sb.Append(";");
165+
}
166+
sb.Append("):");
167+
AppendType(node.ReturnType);
134168
sb.Append(")=>");
135169
return base.VisitLambda(node);
136170
}
137171

138172
protected override Expression VisitBinary(BinaryExpression node)
139173
{
140-
sb.Append($"BIN({node.NodeType})");
174+
sb.Append("BIN(");
175+
sb.Append(node.NodeType);
176+
sb.Append(":");
177+
AppendType(node.Type);
178+
if (node.Method != null)
179+
{
180+
sb.Append(":");
181+
AppendMethod(node.Method);
182+
}
183+
sb.Append(")");
141184
return base.VisitBinary(node);
142185
}
143186

144187
protected override Expression VisitParameter(ParameterExpression node)
145188
{
146-
sb.Append($"PARAM({node.Type.FullName})");
189+
sb.Append("PARAM(");
190+
AppendType(node.Type);
191+
sb.Append(")");
147192
return node;
148193
}
194+
195+
protected override Expression VisitMethodCall(MethodCallExpression node)
196+
{
197+
sb.Append("CALL(");
198+
AppendMethod(node.Method);
199+
sb.Append(":");
200+
AppendType(node.Type);
201+
sb.Append(")");
202+
return base.VisitMethodCall(node);
203+
}
204+
205+
protected override Expression VisitNew(NewExpression node)
206+
{
207+
sb.Append("NEW(");
208+
AppendType(node.Type);
209+
if (node.Constructor != null)
210+
{
211+
sb.Append(":");
212+
AppendConstructor(node.Constructor);
213+
}
214+
sb.Append(")");
215+
return base.VisitNew(node);
216+
}
217+
218+
protected override Expression VisitUnary(UnaryExpression node)
219+
{
220+
sb.Append("UNARY(");
221+
sb.Append(node.NodeType);
222+
sb.Append(":");
223+
AppendType(node.Type);
224+
if (node.Method != null)
225+
{
226+
sb.Append(":");
227+
AppendMethod(node.Method);
228+
}
229+
sb.Append(")");
230+
return base.VisitUnary(node);
231+
}
232+
233+
private void AppendConstructor(ConstructorInfo constructor)
234+
{
235+
AppendType(constructor.DeclaringType);
236+
sb.Append("(");
237+
var parameters = constructor.GetParameters();
238+
for (int i = 0; i < parameters.Length; i++)
239+
{
240+
if (i > 0)
241+
sb.Append(",");
242+
243+
AppendType(parameters[i].ParameterType);
244+
}
245+
sb.Append(")");
246+
}
247+
248+
private void AppendMethod(MethodInfo method)
249+
{
250+
AppendType(method.DeclaringType);
251+
sb.Append(".");
252+
sb.Append(method.Name);
253+
sb.Append("(");
254+
var parameters = method.GetParameters();
255+
for (int i = 0; i < parameters.Length; i++)
256+
{
257+
if (i > 0)
258+
sb.Append(",");
259+
260+
AppendType(parameters[i].ParameterType);
261+
}
262+
sb.Append("):");
263+
AppendType(method.ReturnType);
264+
}
265+
266+
private void AppendType(Type type)
267+
{
268+
sb.Append(type?.ToString() ?? "<null>");
269+
}
149270
}
150271

151272
private sealed class CacheableExpressionVisitor : ExpressionVisitor
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System;
2+
using System.Linq.Expressions;
3+
using NUnit.Framework;
4+
using ServiceStack.ExpressionUtil;
5+
6+
namespace ServiceStack.Common.Tests.Expressions;
7+
8+
[TestFixture]
9+
public class CachedExpressionCompilerTests
10+
{
11+
private class TypedBox<T>
12+
{
13+
}
14+
15+
[Test]
16+
public void Expression_cache_keys_include_expression_types()
17+
{
18+
Expression<Func<object, object>> intExpr = _ => new TypedBox<int>();
19+
Expression<Func<object, object>> stringExpr = _ => new TypedBox<string>();
20+
21+
Assert.That(ExpressionCacheKey.TryGetKey(intExpr, out var intKey), Is.True);
22+
Assert.That(ExpressionCacheKey.TryGetKey(stringExpr, out var stringKey), Is.True);
23+
Assert.That(intKey, Is.Not.EqualTo(stringKey));
24+
}
25+
26+
[Test]
27+
public void Evaluate_does_not_reuse_cached_delegate_for_different_expression_types()
28+
{
29+
var intExpr = Expression.New(typeof(TypedBox<int>));
30+
var stringExpr = Expression.New(typeof(TypedBox<string>));
31+
32+
var intResult = CachedExpressionCompiler.Evaluate(intExpr);
33+
var stringResult = CachedExpressionCompiler.Evaluate(stringExpr);
34+
35+
Assert.That(intResult, Is.TypeOf<TypedBox<int>>());
36+
Assert.That(stringResult, Is.TypeOf<TypedBox<string>>());
37+
}
38+
}
39+

0 commit comments

Comments
 (0)