Skip to content

Commit 30708a1

Browse files
authored
Merge pull request #459 from DataObjects-NET/7.2-singleordefaultasync-for-one-key-value
QueryEndpoint.SingleAsync()/SingleOrDefaultAsync() for one key value
2 parents 4111bba + f6682fa commit 30708a1

4 files changed

Lines changed: 175 additions & 5 deletions

File tree

ChangeLog/7.2.2-dev.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
[main] Query.CreateDelayedQuery(key, Func<IOrderedQueryable<TElement>>) applies external key instead of default computed, as it suppose to
1+
[main] Query.CreateDelayedQuery(key, Func<IOrderedQueryable<TElement>>) applies external key instead of default computed, as it suppose to
2+
[main] QueryEndpoint.SingleAsync()/SingleOrDefaultAsync() get overloads that can recieve one key value as parameter without need to create array explicitly

Orm/Xtensive.Orm.Tests/Storage/Prefetch/FetchByKeyWithCachingTest.cs

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public void SingleByExistingIdTest()
143143
}
144144

145145
[Test]
146-
public async Task SingleByExistingIdAsyncTest()
146+
public async Task SingleByExistingIdAsyncTest1()
147147
{
148148
await RunWithinSessionAsync(async (s) => {
149149
var existingId = (int) existingKeys[customerType][0].Value.GetValue(0, out var _);
@@ -164,6 +164,28 @@ await RunWithinSessionAsync(async (s) => {
164164
});
165165
}
166166

167+
[Test]
168+
public async Task SingleByExistingIdAsyncTest2()
169+
{
170+
await RunWithinSessionAsync(async (s) => {
171+
var existingId = (int) existingKeys[customerType][0].Value.GetValue(0, out var _);
172+
var detector = new QueryExecutionDetector();
173+
using (detector.Attach(s)) {
174+
// Entity is not in cache yet
175+
var existingEntity = await s.Query.SingleAsync<Customer>(existingId);
176+
}
177+
Assert.That(detector.DbCommandsDetected, Is.True);
178+
detector.Reset();
179+
180+
using (detector.Attach(s)) {
181+
// now it is in cache
182+
var existingEntity = await s.Query.SingleAsync<Customer>(existingId);
183+
}
184+
Assert.That(detector.DbCommandsDetected, Is.False);
185+
detector.Reset();
186+
});
187+
}
188+
167189
[Test]
168190
public void SingleByInexistentIdTest()
169191
{
@@ -186,7 +208,7 @@ public void SingleByInexistentIdTest()
186208
}
187209

188210
[Test]
189-
public async Task SingleByInexistentIdAsyncTest()
211+
public async Task SingleByInexistentIdAsyncTest1()
190212
{
191213
await RunWithinSessionAsync(async (s) => {
192214
var inexistentId = 9999;
@@ -207,6 +229,28 @@ await RunWithinSessionAsync(async (s) => {
207229
});
208230
}
209231

232+
[Test]
233+
public async Task SingleByInexistentIdAsyncTest2()
234+
{
235+
await RunWithinSessionAsync(async (s) => {
236+
var inexistentId = 9999;
237+
238+
var detector = new QueryExecutionDetector();
239+
using (detector.Attach(s)) {
240+
_ = Assert.ThrowsAsync<KeyNotFoundException>(async () => await s.Query.SingleAsync<Customer>(inexistentId));
241+
}
242+
Assert.That(detector.DbCommandsDetected, Is.True);
243+
detector.Reset();
244+
245+
using (detector.Attach(s)) {
246+
_ = Assert.ThrowsAsync<KeyNotFoundException>(async () => await s.Query.SingleAsync<Customer>(inexistentId));
247+
}
248+
Assert.That(detector.DbCommandsDetected, Is.False);
249+
detector.Reset();
250+
await Task.CompletedTask;
251+
});
252+
}
253+
210254
[Test]
211255
public void SingleOrDefaultByExistingKeyTest()
212256
{
@@ -327,7 +371,7 @@ public void SingleOrDefaultByExistingIdTest()
327371
}
328372

329373
[Test]
330-
public async Task SingleOrDefaultByExistingIdAsyncTest()
374+
public async Task SingleOrDefaultByExistingIdAsyncTest1()
331375
{
332376
await RunWithinSessionAsync(async (s) => {
333377
var existingId = (int) existingKeys[customerType][0].Value.GetValue(0, out var _);
@@ -348,6 +392,28 @@ await RunWithinSessionAsync(async (s) => {
348392
});
349393
}
350394

395+
[Test]
396+
public async Task SingleOrDefaultByExistingIdAsyncTest2()
397+
{
398+
await RunWithinSessionAsync(async (s) => {
399+
var existingId = (int) existingKeys[customerType][0].Value.GetValue(0, out var _);
400+
var detector = new QueryExecutionDetector();
401+
using (detector.Attach(s)) {
402+
// Entity is not in cache yet
403+
var existingEntity = await s.Query.SingleOrDefaultAsync<Customer>(existingId);
404+
}
405+
Assert.That(detector.DbCommandsDetected, Is.True);
406+
detector.Reset();
407+
408+
using (detector.Attach(s)) {
409+
// now it is in cache
410+
var existingEntity = await s.Query.SingleOrDefaultAsync<Customer>(existingId);
411+
}
412+
Assert.That(detector.DbCommandsDetected, Is.False);
413+
detector.Reset();
414+
});
415+
}
416+
351417
[Test]
352418
public void SingleOrDefaultByInexistentIdTest()
353419
{
@@ -372,7 +438,7 @@ public void SingleOrDefaultByInexistentIdTest()
372438
}
373439

374440
[Test]
375-
public async Task SingleOrDefaultByInexistentIdAsyncTest()
441+
public async Task SingleOrDefaultByInexistentIdAsyncTest1()
376442
{
377443
await RunWithinSessionAsync(async (s) => {
378444
var inexistentId = 9999;
@@ -395,6 +461,29 @@ await RunWithinSessionAsync(async (s) => {
395461
});
396462
}
397463

464+
[Test]
465+
public async Task SingleOrDefaultByInexistentIdAsyncTest2()
466+
{
467+
await RunWithinSessionAsync(async (s) => {
468+
var inexistentId = 9999;
469+
470+
var detector = new QueryExecutionDetector();
471+
using (detector.Attach(s)) {
472+
var shouldBeNull = await s.Query.SingleOrDefaultAsync<Customer>(inexistentId);
473+
Assert.That(shouldBeNull, Is.Null);
474+
}
475+
Assert.That(detector.DbCommandsDetected, Is.True);
476+
detector.Reset();
477+
478+
using (detector.Attach(s)) {
479+
var shouldBeNull = await s.Query.SingleOrDefaultAsync<Customer>(inexistentId);
480+
Assert.That(shouldBeNull, Is.Null);
481+
}
482+
Assert.That(detector.DbCommandsDetected, Is.False);
483+
detector.Reset();
484+
await Task.CompletedTask;
485+
});
486+
}
398487

399488
private void RunWithinSession(Action<Session> testAction)
400489
{

Orm/Xtensive.Orm/Orm/Query.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,23 @@ public static Task<T> SingleAsync<T>(object[] keyValues, CancellationToken token
316316
return Session.Demand().Query.SingleAsync<T>(keyValues, token);
317317
}
318318

319+
/// <summary>
320+
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="keyValue"/>
321+
/// in the current <see cref="Session"/>.
322+
/// </summary>
323+
/// <typeparam name="T">Type of the entity.</typeparam>
324+
/// <param name="keyValue">Key value.</param>
325+
/// <param name="token">The token to cancel this operation.</param>
326+
/// <returns>
327+
/// The <see cref="Entity"/> specified <paramref name="keyValue"/> identify.
328+
/// </returns>
329+
/// <exception cref="KeyNotFoundException">Entity with the specified key is not found.</exception>
330+
public static Task<T> SingleAsync<T>(object keyValue, CancellationToken token)
331+
where T : class, IEntity
332+
{
333+
return Session.Demand().Query.SingleAsync<T>(keyValue, token);
334+
}
335+
319336
/// <summary>
320337
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="key"/>
321338
/// in the current <see cref="Session"/>.
@@ -385,6 +402,23 @@ public static Task<T> SingleOrDefaultAsync<T>(object[] keyValues, CancellationTo
385402
return Session.Demand().Query.SingleOrDefaultAsync<T>(keyValues, token);
386403
}
387404

405+
/// <summary>
406+
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="keyValue"/>
407+
/// in the current <see cref="Session"/>.
408+
/// </summary>
409+
/// <typeparam name="T">Type of the entity.</typeparam>
410+
/// <param name="keyValue">Key value.</param>
411+
/// <param name="token">The token to cancel this operation.</param>
412+
/// <returns>
413+
/// The <see cref="Entity"/> specified <paramref name="keyValue"/> identify.
414+
/// <see langword="null"/>, if there is no such entity.
415+
/// </returns>
416+
public static Task<T> SingleOrDefaultAsync<T>(object keyValue, CancellationToken token)
417+
where T : class, IEntity
418+
{
419+
return Session.Demand().Query.SingleOrDefaultAsync<T>(keyValue, token);
420+
}
421+
388422
#region Execute
389423

390424
/// <summary>

Orm/Xtensive.Orm/Orm/QueryEndpoint.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,23 @@ public async Task<T> SingleAsync<T>(object[] keyValues, CancellationToken token
428428
return (T) (object) (await SingleAsync(GetKeyByValues<T>(keyValues), token).ConfigureAwait(false));
429429
}
430430

431+
/// <summary>
432+
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="keyValue"/>
433+
/// in the current <see cref="session"/>.
434+
/// </summary>
435+
/// <typeparam name="T">Type of the entity.</typeparam>
436+
/// <param name="keyValue">Key value.</param>
437+
/// <param name="token">The token to cancel this operation.</param>
438+
/// <returns>
439+
/// The <see cref="Entity"/> specified <paramref name="keyValue"/> identify.
440+
/// </returns>
441+
/// <exception cref="KeyNotFoundException">Entity with the specified key is not found.</exception>
442+
public async Task<T> SingleAsync<T>(object keyValue, CancellationToken token = default)
443+
where T : class, IEntity
444+
{
445+
return (T) (object) (await SingleAsync(GetKeyByValue<T>(keyValue), token).ConfigureAwait(false));
446+
}
447+
431448
/// <summary>
432449
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="key"/>
433450
/// in the current <see cref="session"/>.
@@ -496,6 +513,23 @@ public async Task<T> SingleOrDefaultAsync<T>(object[] keyValues, CancellationTok
496513
return (T) (object) (await SingleOrDefaultAsync(GetKeyByValues<T>(keyValues), token).ConfigureAwait(false));
497514
}
498515

516+
/// <summary>
517+
/// Resolves (gets) the <see cref="Entity"/> by the specified <paramref name="keyValue"/>
518+
/// in the current <see cref="session"/>.
519+
/// </summary>
520+
/// <typeparam name="T">Type of the entity.</typeparam>
521+
/// <param name="keyValue">Key value.</param>
522+
/// <param name="token">The token to cancel this operation.</param>
523+
/// <returns>
524+
/// The <see cref="Entity"/> specified <paramref name="keyValues"/> identify.
525+
/// <see langword="null"/>, if there is no such entity.
526+
/// </returns>
527+
public async Task<T> SingleOrDefaultAsync<T>(object keyValue, CancellationToken token = default)
528+
where T : class, IEntity
529+
{
530+
return (T) (object) (await SingleOrDefaultAsync(GetKeyByValue<T>(keyValue), token).ConfigureAwait(false));
531+
}
532+
499533
/// <summary>
500534
/// Fetches multiple instances of specified type by provided <paramref name="keys"/>.
501535
/// </summary>
@@ -970,6 +1004,18 @@ private Key GetKeyByValues<T>(object[] keyValues)
9701004
return Key.Create(session.Domain, session.StorageNodeId, typeof(T), TypeReferenceAccuracy.BaseType, keyValues);
9711005
}
9721006

1007+
private Key GetKeyByValue<T>(object keyValue)
1008+
{
1009+
ArgumentNullException.ThrowIfNull(keyValue);
1010+
switch (keyValue) {
1011+
case Key key:
1012+
return key;
1013+
case Entity entity:
1014+
return entity.Key;
1015+
}
1016+
return Key.Create(session.Domain, session.StorageNodeId, typeof(T), TypeReferenceAccuracy.BaseType, keyValue);
1017+
}
1018+
9731019
private Expression BuildRootExpression(Type elementType)
9741020
{
9751021
return RootBuilder!=null

0 commit comments

Comments
 (0)