Skip to content

Commit a52c918

Browse files
committed
Linq/LinqLogging.cs: Avoid initializing proxies present in logged linq expressions (NH-3429).
1 parent 42c0752 commit a52c918

File tree

2 files changed

+65
-1
lines changed

2 files changed

+65
-1
lines changed

src/NHibernate.Test/Linq/LoggingTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Linq;
22
using NHibernate.Cfg;
3+
using NHibernate.DomainModel.Northwind.Entities;
34
using NUnit.Framework;
45

56
namespace NHibernate.Test.Linq
@@ -26,5 +27,35 @@ public void PageBetweenProjections()
2627
}
2728
}
2829

30+
31+
[Test]
32+
public void CanLogLinqExpressionWithoutInitializingContainedProxy()
33+
{
34+
var productId = db.Products.Select(p => p.ProductId).First();
35+
36+
using (var logspy = new LogSpy("NHibernate.Linq"))
37+
{
38+
var productProxy = session.Load<Product>(productId);
39+
Assert.That(NHibernateUtil.IsInitialized(productProxy), Is.False);
40+
41+
var result = from product in db.Products
42+
where product == productProxy
43+
select product;
44+
45+
Assert.That(result.Count(), Is.EqualTo(1));
46+
47+
// Verify that the expected logging did happen.
48+
var actualLog = logspy.GetWholeLog();
49+
50+
const string expectedLog =
51+
"Expression (partially evaluated): value(NHibernate.Linq.NhQueryable`1[NHibernate.DomainModel.Northwind.Entities.Product])" +
52+
".Where(product => (product = Product#1)).Count()";
53+
Assert.That(actualLog, Is.StringContaining(expectedLog));
54+
55+
// And verify that the proxy in the expression wasn't initialized.
56+
Assert.That(NHibernateUtil.IsInitialized(productProxy), Is.False,
57+
"ERROR: We expected the proxy to NOT be initialized.");
58+
}
59+
}
2960
}
3061
}

src/NHibernate/Linq/LinqLogging.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
using System.Linq.Expressions;
2+
using NHibernate.Linq.Visitors;
3+
using NHibernate.Proxy;
4+
using NHibernate.Util;
25

36
namespace NHibernate.Linq
47
{
@@ -12,7 +15,37 @@ internal static class LinqLogging
1215
internal static void LogExpression(string msg, Expression expression)
1316
{
1417
if (Log.IsDebugEnabled)
15-
Log.DebugFormat("{0}: {1}", msg, expression.ToString());
18+
{
19+
// If the expression contains NHibernate proxies, those will be initialized
20+
// when we call ToString() on the exception. The string representation is
21+
// generated by a class internal to System.Linq.Expression, so we cannot
22+
// actually override that logic. Circumvent it by replacing such ConstantExpressions
23+
// with ParameterExpression, having their name set to the string we wish to display.
24+
var visitor = new ProxyReplacingExpressionTreeVisitor();
25+
var preparedExpression = visitor.VisitExpression(expression);
26+
27+
Log.DebugFormat("{0}: {1}", msg, preparedExpression.ToString());
28+
}
29+
}
30+
31+
32+
/// <summary>
33+
/// Replace all occurances of ConstantExpression where the value is an NHibernate
34+
/// proxy with a ParameterExpression. The name of the parameter will be a string
35+
/// representing the proxied entity, without initializing it.
36+
/// </summary>
37+
private class ProxyReplacingExpressionTreeVisitor : NhExpressionTreeVisitor
38+
{
39+
// See also e.g. Remotion.Linq.Clauses.ExpressionTreeVisitors.FormattingExpressionTreeVisitor
40+
// for another example of this technique.
41+
42+
protected override Expression VisitConstantExpression(ConstantExpression expression)
43+
{
44+
if (expression.Value.IsProxy())
45+
return Expression.Parameter(expression.Type, ObjectUtils.IdentityToString(expression.Value));
46+
47+
return base.VisitConstantExpression(expression);
48+
}
1649
}
1750
}
1851
}

0 commit comments

Comments
 (0)