Skip to content

Commit

Permalink
Add support in DomainServiceTestHost for asynchronous queries that re…
Browse files Browse the repository at this point in the history
…turn a single entity (#464)

* Add support in DomainServiceTestHost for asynchronous queries
* QuerySingleAsync with two overloads
Expression<Func<TDomainService, TEntity>>,
Expression<Func<TDomainService, Task<TEntity>>> queryOperation
* Added overload for TryQuerySingle to handle async query

* update comment

* review fixes

---------

Co-authored-by: Erik Öijwall <erik.oijwall@crm.se>
  • Loading branch information
erikoijwall and Erik Öijwall authored Dec 18, 2023
1 parent 9c949f1 commit 27c1999
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,37 @@ public TEntity QuerySingle<TEntity>(Expression<Func<TDomainService, TEntity>> qu
return this.QueryCore<TEntity>(queryOperation).SingleOrDefault();
}


/// <summary>
/// Invokes the specified <paramref name="queryOperation"/> asynchronously and returns the result
/// </summary>
/// <remarks>
/// This method should be used for query signatures that do no return a collection
/// </remarks>
/// <typeparam name="TEntity">The type of entity to return</typeparam>
/// <param name="queryOperation">The <see cref="Expression"/> identifying the query operation to invoke</param>
/// <returns>The entity returned from the specified operation</returns>
/// <exception cref="DomainServiceTestHostException">is thrown if there are any validation errors</exception>
public async Task<TEntity> QuerySingleAsync<TEntity>(Expression<Func<TDomainService, TEntity>> queryOperation, CancellationToken ct = default) where TEntity : class
{
return (await this.QueryCoreAsync<TEntity>(queryOperation, ct)).SingleOrDefault();
}

/// <summary>
/// Invokes the specified <paramref name="queryOperation"/> asynchronously and returns the result
/// </summary>
/// <remarks>
/// This method should be used for query signatures that do no return a collection
/// </remarks>
/// <typeparam name="TEntity">The type of entity to return</typeparam>
/// <param name="queryOperation">The <see cref="Expression"/> identifying the query operation to invoke</param>
/// <returns>The entity returned from the specified operation</returns>
/// <exception cref="DomainServiceTestHostException">is thrown if there are any validation errors</exception>
public async Task<TEntity> QuerySingleAsync<TEntity>(Expression<Func<TDomainService, Task<TEntity>>> queryOperation, CancellationToken ct = default) where TEntity : class
{
return (await this.QueryCoreAsync<TEntity>(queryOperation, ct)).SingleOrDefault();
}

/// <summary>
/// Invokes the specified <paramref name="queryOperation"/> and returns the results, the validation errors,
/// and whether the operation completed successfully
Expand Down Expand Up @@ -299,6 +330,26 @@ public bool TryQuerySingle<TEntity>(Expression<Func<TDomainService, TEntity>> qu
return success;
}

/// <summary>
/// Invokes the specified <paramref name="queryOperation"/> and returns the result, the validation errors,
/// and whether the operation completed successfully
/// </summary>
/// <remarks>
/// This method should be used for query signatures that do no return a collection
/// </remarks>
/// <typeparam name="TEntity">The type of entity in the result</typeparam>
/// <param name="queryOperation">The <see cref="Expression"/> identifying the query operation to invoke</param>
/// <param name="result">The entity returned from the specified operation</param>
/// <param name="validationErrors">The validation errors that occurred</param>
/// <returns>Whether the operation completed without error</returns>
public bool TryQuerySingle<TEntity>(Expression<Func<TDomainService, Task<TEntity>>> queryOperation, out TEntity result, out IList<ValidationResult> validationErrors) where TEntity : class
{
IEnumerable<TEntity> results;
bool success = this.TryQueryCore<TEntity>(queryOperation, out results, out validationErrors);
result = (results == null) ? null : results.SingleOrDefault();
return success;
}

#endregion

#region Insert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,46 @@ public async Task IEnumerableQueryAsync()
Assert.AreEqual(3, result.Count());
}

[TestMethod]
public async Task QuerySingleAsync()
{
var testHost = new DomainServiceTestHost<CityDomainService>();

var result = await testHost.QuerySingleAsync(s => s.GetZipByFourDigitCodeAsync(8625), CancellationToken.None);

Assert.IsNotNull(result);
}

[TestMethod]
public async Task QuerySingleAsyncWithSynchronousQuery()
{
var testHost = new DomainServiceTestHost<CityDomainService>();

var result = await testHost.QuerySingleAsync(s => s.GetZipByFourDigitCode2(8625), CancellationToken.None);

Assert.IsNotNull(result);
}

[TestMethod]
public void TryQuerySingleTaskOverload()
{
var testHost = new DomainServiceTestHost<CityDomainService>();

testHost.TryQuerySingle(s => s.GetZipByFourDigitCodeAsync(8625), out Zip result, out var validationErrors);

Assert.IsNotNull(result);
}

[TestMethod]
public void TryQuerySingle()
{
var testHost = new DomainServiceTestHost<CityDomainService>();

testHost.TryQuerySingle(s => s.GetZipByFourDigitCode2(8625), out Zip result, out var validationErrors);

Assert.IsNotNull(result);
}

[TestMethod]
public async Task SubmitAsync()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
Expand Down Expand Up @@ -635,6 +634,32 @@ public EntityQuery<State> GetStatesInShippingZoneQuery(ShippingZone shippingZone
return base.CreateQuery<State>("GetStatesInShippingZone", parameters, false, true);
}

/// <summary>
/// Gets an EntityQuery instance that can be used to load <see cref="Zip"/> entity instances using the 'GetZipByFourDigitCode' query.
/// </summary>
/// <param name="fourDigitCode">The value for the 'fourDigitCode' parameter of the query.</param>
/// <returns>An EntityQuery that can be loaded to retrieve <see cref="Zip"/> entity instances.</returns>
public EntityQuery<Zip> GetZipByFourDigitCodeQuery(int fourDigitCode)
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("fourDigitCode", fourDigitCode);
this.ValidateMethod("GetZipByFourDigitCodeQuery", parameters);
return base.CreateQuery<Zip>("GetZipByFourDigitCode", parameters, false, false);
}

/// <summary>
/// Gets an EntityQuery instance that can be used to load <see cref="Zip"/> entity instances using the 'GetZipByFourDigitCode2' query.
/// </summary>
/// <param name="fourDigitCode">The value for the 'fourDigitCode' parameter of the query.</param>
/// <returns>An EntityQuery that can be loaded to retrieve <see cref="Zip"/> entity instances.</returns>
public EntityQuery<Zip> GetZipByFourDigitCode2Query(int fourDigitCode)
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("fourDigitCode", fourDigitCode);
this.ValidateMethod("GetZipByFourDigitCode2Query", parameters);
return base.CreateQuery<Zip>("GetZipByFourDigitCode2", parameters, false, false);
}

/// <summary>
/// Gets an EntityQuery instance that can be used to load <see cref="Zip"/> entity instances using the 'GetZips' query.
/// </summary>
Expand Down Expand Up @@ -1175,6 +1200,42 @@ public interface ICityDomainServiceContract
/// <returns>The 'QueryResult' returned from the 'GetStatesInShippingZone' operation.</returns>
QueryResult<State> EndGetStatesInShippingZone(IAsyncResult result);

/// <summary>
/// Asynchronously invokes the 'GetZipByFourDigitCode' operation.
/// </summary>
/// <param name="fourDigitCode">The value for the 'fourDigitCode' parameter of this action.</param>
/// <param name="callback">Callback to invoke on completion.</param>
/// <param name="asyncState">Optional state object.</param>
/// <returns>An IAsyncResult that can be used to monitor the request.</returns>
[HasSideEffects(false)]
[OperationContract(AsyncPattern=true, Action="http://tempuri.org/CityDomainService/GetZipByFourDigitCode", ReplyAction="http://tempuri.org/CityDomainService/GetZipByFourDigitCodeResponse")]
IAsyncResult BeginGetZipByFourDigitCode(int fourDigitCode, AsyncCallback callback, object asyncState);

/// <summary>
/// Completes the asynchronous operation begun by 'BeginGetZipByFourDigitCode'.
/// </summary>
/// <param name="result">The IAsyncResult returned from 'BeginGetZipByFourDigitCode'.</param>
/// <returns>The 'QueryResult' returned from the 'GetZipByFourDigitCode' operation.</returns>
QueryResult<Zip> EndGetZipByFourDigitCode(IAsyncResult result);

/// <summary>
/// Asynchronously invokes the 'GetZipByFourDigitCode2' operation.
/// </summary>
/// <param name="fourDigitCode">The value for the 'fourDigitCode' parameter of this action.</param>
/// <param name="callback">Callback to invoke on completion.</param>
/// <param name="asyncState">Optional state object.</param>
/// <returns>An IAsyncResult that can be used to monitor the request.</returns>
[HasSideEffects(false)]
[OperationContract(AsyncPattern=true, Action="http://tempuri.org/CityDomainService/GetZipByFourDigitCode2", ReplyAction="http://tempuri.org/CityDomainService/GetZipByFourDigitCode2Response")]
IAsyncResult BeginGetZipByFourDigitCode2(int fourDigitCode, AsyncCallback callback, object asyncState);

/// <summary>
/// Completes the asynchronous operation begun by 'BeginGetZipByFourDigitCode2'.
/// </summary>
/// <param name="result">The IAsyncResult returned from 'BeginGetZipByFourDigitCode2'.</param>
/// <returns>The 'QueryResult' returned from the 'GetZipByFourDigitCode2' operation.</returns>
QueryResult<Zip> EndGetZipByFourDigitCode2(IAsyncResult result);

/// <summary>
/// Asynchronously invokes the 'GetZips' operation.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'------------------------------------------------------------------------------
' <auto-generated>
' This code was generated by a tool.
' Runtime Version:4.0.30319.42000
'
' Changes to this file may cause incorrect behavior and will be lost if
' the code is regenerated.
Expand Down Expand Up @@ -581,6 +580,30 @@ Namespace Cities
Return MyBase.CreateQuery(Of State)("GetStatesInShippingZone", parameters, false, true)
End Function

''' <summary>
''' Gets an EntityQuery instance that can be used to load <see cref="Zip"/> entity instances using the 'GetZipByFourDigitCode' query.
''' </summary>
''' <param name="fourDigitCode">The value for the 'fourDigitCode' parameter of the query.</param>
''' <returns>An EntityQuery that can be loaded to retrieve <see cref="Zip"/> entity instances.</returns>
Public Function GetZipByFourDigitCodeQuery(ByVal fourDigitCode As Integer) As EntityQuery(Of Zip)
Dim parameters As Dictionary(Of String, Object) = New Dictionary(Of String, Object)()
parameters.Add("fourDigitCode", fourDigitCode)
Me.ValidateMethod("GetZipByFourDigitCodeQuery", parameters)
Return MyBase.CreateQuery(Of Zip)("GetZipByFourDigitCode", parameters, false, false)
End Function

''' <summary>
''' Gets an EntityQuery instance that can be used to load <see cref="Zip"/> entity instances using the 'GetZipByFourDigitCode2' query.
''' </summary>
''' <param name="fourDigitCode">The value for the 'fourDigitCode' parameter of the query.</param>
''' <returns>An EntityQuery that can be loaded to retrieve <see cref="Zip"/> entity instances.</returns>
Public Function GetZipByFourDigitCode2Query(ByVal fourDigitCode As Integer) As EntityQuery(Of Zip)
Dim parameters As Dictionary(Of String, Object) = New Dictionary(Of String, Object)()
parameters.Add("fourDigitCode", fourDigitCode)
Me.ValidateMethod("GetZipByFourDigitCode2Query", parameters)
Return MyBase.CreateQuery(Of Zip)("GetZipByFourDigitCode2", parameters, false, false)
End Function

''' <summary>
''' Gets an EntityQuery instance that can be used to load <see cref="Zip"/> entity instances using the 'GetZips' query.
''' </summary>
Expand Down Expand Up @@ -1091,6 +1114,42 @@ Namespace Cities
''' <returns>The 'QueryResult' returned from the 'GetStatesInShippingZone' operation.</returns>
Function EndGetStatesInShippingZone(ByVal result As IAsyncResult) As QueryResult(Of State)

''' <summary>
''' Asynchronously invokes the 'GetZipByFourDigitCode' operation.
''' </summary>
''' <param name="fourDigitCode">The value for the 'fourDigitCode' parameter of this action.</param>
''' <param name="callback">Callback to invoke on completion.</param>
''' <param name="asyncState">Optional state object.</param>
''' <returns>An IAsyncResult that can be used to monitor the request.</returns>
<HasSideEffects(false), _
OperationContract(AsyncPattern:=true, Action:="http://tempuri.org/CityDomainService/GetZipByFourDigitCode", ReplyAction:="http://tempuri.org/CityDomainService/GetZipByFourDigitCodeResponse")> _
Function BeginGetZipByFourDigitCode(ByVal fourDigitCode As Integer, ByVal callback As AsyncCallback, ByVal asyncState As Object) As IAsyncResult

''' <summary>
''' Completes the asynchronous operation begun by 'BeginGetZipByFourDigitCode'.
''' </summary>
''' <param name="result">The IAsyncResult returned from 'BeginGetZipByFourDigitCode'.</param>
''' <returns>The 'QueryResult' returned from the 'GetZipByFourDigitCode' operation.</returns>
Function EndGetZipByFourDigitCode(ByVal result As IAsyncResult) As QueryResult(Of Zip)

''' <summary>
''' Asynchronously invokes the 'GetZipByFourDigitCode2' operation.
''' </summary>
''' <param name="fourDigitCode">The value for the 'fourDigitCode' parameter of this action.</param>
''' <param name="callback">Callback to invoke on completion.</param>
''' <param name="asyncState">Optional state object.</param>
''' <returns>An IAsyncResult that can be used to monitor the request.</returns>
<HasSideEffects(false), _
OperationContract(AsyncPattern:=true, Action:="http://tempuri.org/CityDomainService/GetZipByFourDigitCode2", ReplyAction:="http://tempuri.org/CityDomainService/GetZipByFourDigitCode2Response")> _
Function BeginGetZipByFourDigitCode2(ByVal fourDigitCode As Integer, ByVal callback As AsyncCallback, ByVal asyncState As Object) As IAsyncResult

''' <summary>
''' Completes the asynchronous operation begun by 'BeginGetZipByFourDigitCode2'.
''' </summary>
''' <param name="result">The IAsyncResult returned from 'BeginGetZipByFourDigitCode2'.</param>
''' <returns>The 'QueryResult' returned from the 'GetZipByFourDigitCode2' operation.</returns>
Function EndGetZipByFourDigitCode2(ByVal result As IAsyncResult) As QueryResult(Of Zip)

''' <summary>
''' Asynchronously invokes the 'GetZips' operation.
''' </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,18 @@ public async Task<IEnumerable<Zip>> GetZipsAsEnumerable()
return await Task.FromResult(GetZips());
}

[Query(IsComposable = false)]
public Task<Zip> GetZipByFourDigitCodeAsync(int fourDigitCode)
{
return Task.FromResult(GetZipByFourDigitCode2(fourDigitCode));
}

[Query(IsComposable = false)]
public Zip GetZipByFourDigitCode2(int fourDigitCode)
{
return GetZips().Single(z => z.FourDigit == fourDigitCode);
}

[Query]
public async Task<IQueryable<Zip>> GetZipsWithDelay(TimeSpan delay)
{
Expand Down

0 comments on commit 27c1999

Please sign in to comment.