Skip to content

Commit 759b1a4

Browse files
committed
Allow Linq Query to load entities as read-only
Fixes #908 (NH-3470)
1 parent 82e6e1c commit 759b1a4

File tree

6 files changed

+197
-33
lines changed

6 files changed

+197
-33
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System.Linq;
12+
using NHibernate.Linq;
13+
using NUnit.Framework;
14+
15+
namespace NHibernate.Test.Linq
16+
{
17+
using System.Threading.Tasks;
18+
[TestFixture]
19+
public class QueryReadOnlyTestsAsync : LinqTestCase
20+
{
21+
[Test]
22+
public async Task CanSetReadOnlyOnLinqQueriesAsync()
23+
{
24+
var result = await ((from e in db.Customers
25+
where e.CompanyName == "Bon app'"
26+
select e).WithOptions(o => o.SetReadOnly(true)).ToListAsync());
27+
28+
Assert.That(result.All(x => session.IsReadOnly(x)), Is.True);
29+
}
30+
31+
[Test]
32+
public async Task CanSetReadOnlyOnLinqPagingQueryAsync()
33+
{
34+
var result = await ((from e in db.Customers
35+
select e).Skip(1).Take(1).WithOptions(o => o.SetReadOnly(true)).ToListAsync());
36+
37+
Assert.That(result.All(x => session.IsReadOnly(x)), Is.True);
38+
}
39+
40+
[Test]
41+
public async Task CanSetReadOnlyBeforeSkipOnLinqOrderedPageQueryAsync()
42+
{
43+
var result = await ((from e in db.Customers
44+
orderby e.CompanyName
45+
select e).WithOptions(o => o.SetReadOnly(true)).Skip(5).Take(5).ToListAsync());
46+
47+
Assert.That(result.All(x => session.IsReadOnly(x)), Is.True);
48+
}
49+
}
50+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System.Linq;
2+
using NHibernate.Linq;
3+
using NUnit.Framework;
4+
5+
namespace NHibernate.Test.Linq
6+
{
7+
[TestFixture]
8+
public class QueryReadOnlyTests : LinqTestCase
9+
{
10+
[Test]
11+
public void CanSetReadOnlyOnLinqQueries()
12+
{
13+
var result = (from e in db.Customers
14+
where e.CompanyName == "Bon app'"
15+
select e).WithOptions(o => o.SetReadOnly(true)).ToList();
16+
17+
Assert.That(result.All(x => session.IsReadOnly(x)), Is.True);
18+
}
19+
20+
[Test]
21+
public void CanSetReadOnlyOnLinqPagingQuery()
22+
{
23+
var result = (from e in db.Customers
24+
select e).Skip(1).Take(1).WithOptions(o => o.SetReadOnly(true)).ToList();
25+
26+
Assert.That(result.All(x => session.IsReadOnly(x)), Is.True);
27+
}
28+
29+
[Test]
30+
public void CanSetReadOnlyBeforeSkipOnLinqOrderedPageQuery()
31+
{
32+
var result = (from e in db.Customers
33+
orderby e.CompanyName
34+
select e).WithOptions(o => o.SetReadOnly(true)).Skip(5).Take(5).ToList();
35+
36+
Assert.That(result.All(x => session.IsReadOnly(x)), Is.True);
37+
}
38+
}
39+
}

src/NHibernate/Async/Linq/DefaultQueryProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public Task<int> ExecuteDmlAsync<T>(QueryMode queryMode, Expression expression,
6969
var query = Session.CreateQuery(nhLinqExpression);
7070

7171
SetParameters(query, nhLinqExpression.ParameterValuesByName);
72-
ApplyOptions(query);
72+
_options?.Apply(query);
7373
return query.ExecuteUpdateAsync(cancellationToken);
7474
}
7575
catch (Exception ex)

src/NHibernate/Linq/DefaultQueryProvider.cs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -175,30 +175,12 @@ protected virtual NhLinqExpression PrepareQuery(Expression expression, out IQuer
175175
}
176176

177177
SetParameters(query, nhLinqExpression.ParameterValuesByName);
178-
ApplyOptions(query);
178+
_options?.Apply(query);
179179
SetResultTransformerAndAdditionalCriteria(query, nhLinqExpression, nhLinqExpression.ParameterValuesByName);
180180

181181
return nhLinqExpression;
182182
}
183183

184-
private void ApplyOptions(IQuery query)
185-
{
186-
if (_options == null)
187-
return;
188-
189-
if (_options.Timeout.HasValue)
190-
query.SetTimeout(_options.Timeout.Value);
191-
192-
if (_options.Cacheable.HasValue)
193-
query.SetCacheable(_options.Cacheable.Value);
194-
195-
if (_options.CacheMode.HasValue)
196-
query.SetCacheMode(_options.CacheMode.Value);
197-
198-
if (_options.CacheRegion != null)
199-
query.SetCacheRegion(_options.CacheRegion);
200-
}
201-
202184
protected virtual object ExecuteQuery(NhLinqExpression nhLinqExpression, IQuery query, NhLinqExpression nhQuery)
203185
{
204186
IList results = query.List();
@@ -279,7 +261,7 @@ public int ExecuteDml<T>(QueryMode queryMode, Expression expression)
279261
var query = Session.CreateQuery(nhLinqExpression);
280262

281263
SetParameters(query, nhLinqExpression.ParameterValuesByName);
282-
ApplyOptions(query);
264+
_options?.Apply(query);
283265
return query.ExecuteUpdate();
284266
}
285267
}

src/NHibernate/Linq/IQueryableOptions.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
using System;
2+
13
namespace NHibernate.Linq
24
{
35
// Methods signatures taken from IQuery.
46
/// <summary>
57
/// Expose NH queryable options.
68
/// </summary>
9+
//Since v5.1
10+
[Obsolete("Please use NhQueryableOptions instead.")]
711
public interface IQueryableOptions
812
{
913
/// <summary>
@@ -29,7 +33,7 @@ public interface IQueryableOptions
2933
IQueryableOptions SetCacheMode(CacheMode cacheMode);
3034

3135
/// <summary>
32-
/// The timeout for the underlying ADO query.
36+
/// Set the timeout for the underlying ADO query.
3337
/// </summary>
3438
/// <param name="timeout">The timeout in seconds.</param>
3539
/// <returns><see langword="this"/> (for method chaining).</returns>
Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,134 @@
11
namespace NHibernate.Linq
22
{
3-
public class NhQueryableOptions: IQueryableOptions
3+
/// <summary>
4+
/// Expose NH queryable options.
5+
/// </summary>
6+
public class NhQueryableOptions
7+
#pragma warning disable 618
8+
: IQueryableOptions
9+
#pragma warning restore 618
410
{
5-
protected internal bool? Cacheable { get; private set; }
6-
protected internal CacheMode? CacheMode{ get; private set; }
7-
protected internal string CacheRegion { get; private set; }
8-
protected internal int? Timeout { get; private set; }
11+
protected bool? Cacheable { get; private set; }
12+
protected CacheMode? CacheMode { get; private set; }
13+
protected string CacheRegion { get; private set; }
14+
protected int? Timeout { get; private set; }
15+
protected bool? ReadOnly { get; private set; }
916

10-
public IQueryableOptions SetCacheable(bool cacheable)
17+
#pragma warning disable 618
18+
/// <inheritdoc />
19+
IQueryableOptions IQueryableOptions.SetCacheable(bool cacheable) => SetCacheable(cacheable);
20+
21+
/// <inheritdoc />
22+
IQueryableOptions IQueryableOptions.SetCacheMode(CacheMode cacheMode) => SetCacheMode(cacheMode);
23+
24+
/// <inheritdoc />
25+
IQueryableOptions IQueryableOptions.SetCacheRegion(string cacheRegion) => SetCacheRegion(cacheRegion);
26+
27+
/// <inheritdoc />
28+
IQueryableOptions IQueryableOptions.SetTimeout(int timeout) => SetTimeout(timeout);
29+
#pragma warning restore 618
30+
31+
/// <summary>
32+
/// Enable caching of this query result set.
33+
/// </summary>
34+
/// <param name="cacheable">Should the query results be cacheable?</param>
35+
/// <returns><see langword="this"/> (for method chaining).</returns>
36+
public NhQueryableOptions SetCacheable(bool cacheable)
1137
{
1238
Cacheable = cacheable;
1339
return this;
1440
}
1541

16-
public IQueryableOptions SetCacheMode(CacheMode cacheMode)
42+
/// <summary>
43+
/// Override the current session cache mode, just for this query.
44+
/// </summary>
45+
/// <param name="cacheMode">The cache mode to use.</param>
46+
/// <returns><see langword="this"/> (for method chaining).</returns>
47+
public NhQueryableOptions SetCacheMode(CacheMode cacheMode)
1748
{
1849
CacheMode = cacheMode;
1950
return this;
2051
}
2152

22-
public IQueryableOptions SetCacheRegion(string cacheRegion)
53+
/// <summary>
54+
/// Set the name of the cache region.
55+
/// </summary>
56+
/// <param name="cacheRegion">The name of a query cache region, or <see langword="null" />
57+
/// for the default query cache</param>
58+
/// <returns><see langword="this"/> (for method chaining).</returns>
59+
public NhQueryableOptions SetCacheRegion(string cacheRegion)
2360
{
2461
CacheRegion = cacheRegion;
2562
return this;
2663
}
2764

28-
public IQueryableOptions SetTimeout(int timeout)
65+
/// <summary>
66+
/// Set the timeout for the underlying ADO query.
67+
/// </summary>
68+
/// <param name="timeout">The timeout in seconds.</param>
69+
/// <returns><see langword="this"/> (for method chaining).</returns>
70+
public NhQueryableOptions SetTimeout(int timeout)
2971
{
3072
Timeout = timeout;
3173
return this;
3274
}
3375

34-
internal NhQueryableOptions Clone()
76+
/// <summary>
77+
/// Set the read-only mode for entities (and proxies) loaded by this query. This setting
78+
/// overrides the default setting for the session (see <see cref="ISession.DefaultReadOnly" />).
79+
/// </summary>
80+
/// <remarks>
81+
/// <para>
82+
/// Read-only entities can be modified, but changes are not persisted. They are not
83+
/// dirty-checked and snapshots of persistent state are not maintained.
84+
/// </para>
85+
/// <para>
86+
/// When a proxy is initialized, the loaded entity will have the same read-only setting
87+
/// as the uninitialized proxy, regardless of the session's current setting.
88+
/// </para>
89+
/// <para>
90+
/// The read-only setting has no impact on entities or proxies returned by the criteria
91+
/// that existed in the session before the criteria was executed.
92+
/// </para>
93+
/// </remarks>
94+
/// <param name="readOnly">
95+
/// If <c>true</c>, entities (and proxies) loaded by the query will be read-only.
96+
/// </param>
97+
/// <returns><c>this</c> (for method chaining)</returns>
98+
public NhQueryableOptions SetReadOnly(bool readOnly)
99+
{
100+
ReadOnly = readOnly;
101+
return this;
102+
}
103+
104+
protected internal NhQueryableOptions Clone()
35105
{
36106
return new NhQueryableOptions
37107
{
38108
Cacheable = Cacheable,
39109
CacheMode = CacheMode,
40110
CacheRegion = CacheRegion,
41-
Timeout = Timeout
111+
Timeout = Timeout,
112+
ReadOnly = ReadOnly
42113
};
43114
}
115+
116+
protected internal void Apply(IQuery query)
117+
{
118+
if (Timeout.HasValue)
119+
query.SetTimeout(Timeout.Value);
120+
121+
if (Cacheable.HasValue)
122+
query.SetCacheable(Cacheable.Value);
123+
124+
if (CacheMode.HasValue)
125+
query.SetCacheMode(CacheMode.Value);
126+
127+
if (CacheRegion != null)
128+
query.SetCacheRegion(CacheRegion);
129+
130+
if (ReadOnly.HasValue)
131+
query.SetReadOnly(ReadOnly.Value);
132+
}
44133
}
45134
}

0 commit comments

Comments
 (0)