diff --git a/ChangeLog/7.2.2-dev.txt b/ChangeLog/7.2.2-dev.txt index 51851af91..38b6795c5 100644 --- a/ChangeLog/7.2.2-dev.txt +++ b/ChangeLog/7.2.2-dev.txt @@ -1 +1,2 @@ -[main] Query.CreateDelayedQuery(key, Func>) applies external key instead of default computed, as it suppose to \ No newline at end of file +[main] Query.CreateDelayedQuery(key, Func>) applies external key instead of default computed, as it suppose to +[main] QueryEndpoint.SingleAsync()/SingleOrDefaultAsync() get overloads that can recieve one key value as parameter without need to create array explicitly \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Storage/Prefetch/FetchByKeyWithCachingTest.cs b/Orm/Xtensive.Orm.Tests/Storage/Prefetch/FetchByKeyWithCachingTest.cs index d8cc4bfe2..8050903ee 100644 --- a/Orm/Xtensive.Orm.Tests/Storage/Prefetch/FetchByKeyWithCachingTest.cs +++ b/Orm/Xtensive.Orm.Tests/Storage/Prefetch/FetchByKeyWithCachingTest.cs @@ -143,7 +143,7 @@ public void SingleByExistingIdTest() } [Test] - public async Task SingleByExistingIdAsyncTest() + public async Task SingleByExistingIdAsyncTest1() { await RunWithinSessionAsync(async (s) => { var existingId = (int) existingKeys[customerType][0].Value.GetValue(0, out var _); @@ -164,6 +164,28 @@ await RunWithinSessionAsync(async (s) => { }); } + [Test] + public async Task SingleByExistingIdAsyncTest2() + { + await RunWithinSessionAsync(async (s) => { + var existingId = (int) existingKeys[customerType][0].Value.GetValue(0, out var _); + var detector = new QueryExecutionDetector(); + using (detector.Attach(s)) { + // Entity is not in cache yet + var existingEntity = await s.Query.SingleAsync(existingId); + } + Assert.That(detector.DbCommandsDetected, Is.True); + detector.Reset(); + + using (detector.Attach(s)) { + // now it is in cache + var existingEntity = await s.Query.SingleAsync(existingId); + } + Assert.That(detector.DbCommandsDetected, Is.False); + detector.Reset(); + }); + } + [Test] public void SingleByInexistentIdTest() { @@ -186,7 +208,7 @@ public void SingleByInexistentIdTest() } [Test] - public async Task SingleByInexistentIdAsyncTest() + public async Task SingleByInexistentIdAsyncTest1() { await RunWithinSessionAsync(async (s) => { var inexistentId = 9999; @@ -207,6 +229,28 @@ await RunWithinSessionAsync(async (s) => { }); } + [Test] + public async Task SingleByInexistentIdAsyncTest2() + { + await RunWithinSessionAsync(async (s) => { + var inexistentId = 9999; + + var detector = new QueryExecutionDetector(); + using (detector.Attach(s)) { + _ = Assert.ThrowsAsync(async () => await s.Query.SingleAsync(inexistentId)); + } + Assert.That(detector.DbCommandsDetected, Is.True); + detector.Reset(); + + using (detector.Attach(s)) { + _ = Assert.ThrowsAsync(async () => await s.Query.SingleAsync(inexistentId)); + } + Assert.That(detector.DbCommandsDetected, Is.False); + detector.Reset(); + await Task.CompletedTask; + }); + } + [Test] public void SingleOrDefaultByExistingKeyTest() { @@ -327,7 +371,7 @@ public void SingleOrDefaultByExistingIdTest() } [Test] - public async Task SingleOrDefaultByExistingIdAsyncTest() + public async Task SingleOrDefaultByExistingIdAsyncTest1() { await RunWithinSessionAsync(async (s) => { var existingId = (int) existingKeys[customerType][0].Value.GetValue(0, out var _); @@ -348,6 +392,28 @@ await RunWithinSessionAsync(async (s) => { }); } + [Test] + public async Task SingleOrDefaultByExistingIdAsyncTest2() + { + await RunWithinSessionAsync(async (s) => { + var existingId = (int) existingKeys[customerType][0].Value.GetValue(0, out var _); + var detector = new QueryExecutionDetector(); + using (detector.Attach(s)) { + // Entity is not in cache yet + var existingEntity = await s.Query.SingleOrDefaultAsync(existingId); + } + Assert.That(detector.DbCommandsDetected, Is.True); + detector.Reset(); + + using (detector.Attach(s)) { + // now it is in cache + var existingEntity = await s.Query.SingleOrDefaultAsync(existingId); + } + Assert.That(detector.DbCommandsDetected, Is.False); + detector.Reset(); + }); + } + [Test] public void SingleOrDefaultByInexistentIdTest() { @@ -372,7 +438,7 @@ public void SingleOrDefaultByInexistentIdTest() } [Test] - public async Task SingleOrDefaultByInexistentIdAsyncTest() + public async Task SingleOrDefaultByInexistentIdAsyncTest1() { await RunWithinSessionAsync(async (s) => { var inexistentId = 9999; @@ -395,6 +461,29 @@ await RunWithinSessionAsync(async (s) => { }); } + [Test] + public async Task SingleOrDefaultByInexistentIdAsyncTest2() + { + await RunWithinSessionAsync(async (s) => { + var inexistentId = 9999; + + var detector = new QueryExecutionDetector(); + using (detector.Attach(s)) { + var shouldBeNull = await s.Query.SingleOrDefaultAsync(inexistentId); + Assert.That(shouldBeNull, Is.Null); + } + Assert.That(detector.DbCommandsDetected, Is.True); + detector.Reset(); + + using (detector.Attach(s)) { + var shouldBeNull = await s.Query.SingleOrDefaultAsync(inexistentId); + Assert.That(shouldBeNull, Is.Null); + } + Assert.That(detector.DbCommandsDetected, Is.False); + detector.Reset(); + await Task.CompletedTask; + }); + } private void RunWithinSession(Action testAction) { diff --git a/Orm/Xtensive.Orm/Orm/Query.cs b/Orm/Xtensive.Orm/Orm/Query.cs index add4f4483..53a0b3019 100644 --- a/Orm/Xtensive.Orm/Orm/Query.cs +++ b/Orm/Xtensive.Orm/Orm/Query.cs @@ -316,6 +316,23 @@ public static Task SingleAsync(object[] keyValues, CancellationToken token return Session.Demand().Query.SingleAsync(keyValues, token); } + /// + /// Resolves (gets) the by the specified + /// in the current . + /// + /// Type of the entity. + /// Key value. + /// The token to cancel this operation. + /// + /// The specified identify. + /// + /// Entity with the specified key is not found. + public static Task SingleAsync(object keyValue, CancellationToken token) + where T : class, IEntity + { + return Session.Demand().Query.SingleAsync(keyValue, token); + } + /// /// Resolves (gets) the by the specified /// in the current . @@ -385,6 +402,23 @@ public static Task SingleOrDefaultAsync(object[] keyValues, CancellationTo return Session.Demand().Query.SingleOrDefaultAsync(keyValues, token); } + /// + /// Resolves (gets) the by the specified + /// in the current . + /// + /// Type of the entity. + /// Key value. + /// The token to cancel this operation. + /// + /// The specified identify. + /// , if there is no such entity. + /// + public static Task SingleOrDefaultAsync(object keyValue, CancellationToken token) + where T : class, IEntity + { + return Session.Demand().Query.SingleOrDefaultAsync(keyValue, token); + } + #region Execute /// diff --git a/Orm/Xtensive.Orm/Orm/QueryEndpoint.cs b/Orm/Xtensive.Orm/Orm/QueryEndpoint.cs index 3389dea52..365684bf5 100644 --- a/Orm/Xtensive.Orm/Orm/QueryEndpoint.cs +++ b/Orm/Xtensive.Orm/Orm/QueryEndpoint.cs @@ -428,6 +428,23 @@ public async Task SingleAsync(object[] keyValues, CancellationToken token return (T) (object) (await SingleAsync(GetKeyByValues(keyValues), token).ConfigureAwait(false)); } + /// + /// Resolves (gets) the by the specified + /// in the current . + /// + /// Type of the entity. + /// Key value. + /// The token to cancel this operation. + /// + /// The specified identify. + /// + /// Entity with the specified key is not found. + public async Task SingleAsync(object keyValue, CancellationToken token = default) + where T : class, IEntity + { + return (T) (object) (await SingleAsync(GetKeyByValue(keyValue), token).ConfigureAwait(false)); + } + /// /// Resolves (gets) the by the specified /// in the current . @@ -496,6 +513,23 @@ public async Task SingleOrDefaultAsync(object[] keyValues, CancellationTok return (T) (object) (await SingleOrDefaultAsync(GetKeyByValues(keyValues), token).ConfigureAwait(false)); } + /// + /// Resolves (gets) the by the specified + /// in the current . + /// + /// Type of the entity. + /// Key value. + /// The token to cancel this operation. + /// + /// The specified identify. + /// , if there is no such entity. + /// + public async Task SingleOrDefaultAsync(object keyValue, CancellationToken token = default) + where T : class, IEntity + { + return (T) (object) (await SingleOrDefaultAsync(GetKeyByValue(keyValue), token).ConfigureAwait(false)); + } + /// /// Fetches multiple instances of specified type by provided . /// @@ -970,6 +1004,18 @@ private Key GetKeyByValues(object[] keyValues) return Key.Create(session.Domain, session.StorageNodeId, typeof(T), TypeReferenceAccuracy.BaseType, keyValues); } + private Key GetKeyByValue(object keyValue) + { + ArgumentNullException.ThrowIfNull(keyValue); + switch (keyValue) { + case Key key: + return key; + case Entity entity: + return entity.Key; + } + return Key.Create(session.Domain, session.StorageNodeId, typeof(T), TypeReferenceAccuracy.BaseType, keyValue); + } + private Expression BuildRootExpression(Type elementType) { return RootBuilder!=null