Closed YohanSciubukgian closed 4 years ago
The problem is the serialization is based on the type you passed in. It's not possible to deserialize an interface. The concrete type has to be passed into the method. The reason this works in v2 is it's doing a lazy deserialization so you don't get the exception until you try to read the object which your project never does. This optimization should be done to v3, but if your really looking for performance you should use the stream APIs instead.
Solutions 1: Pass the concrete type to the methods:
await _azureCosmosDbV2Connector.CreateDocumentAsync<CompanyBar>(DATABASE_ID_V2, collectionId, document);
Solution 2: Use the stream APIs. Don't serialize the response. Need to create a serializer. This will not throw exceptions for NotFound and other service side exceptions. The typed APIs will throw a CosmosException if the operation failed.
public async Task<bool> CreateDocumentAsync<T>(string databaseId, string collectionId, DocumentBase<T> item)
{
var container = _client.GetContainer(databaseId, collectionId);
PartitionKey pk = new PartitionKey(item.key);
Stream stream = serializer.ToStream<T>(item);
using(var response = await container.CreateItemStreamAsync(pk, stream))
{
// Cause it to throw an exception if it is a failure.
response.EnsureSuccessStatusCode();
return IsResponseValid(response.StatusCode);
}
}
Solution 3: This has not been verified, but you can try passing dynamic.
public async Task<bool> CreateDocumentAsync<T>(string databaseId, string collectionId, DocumentBase<T> item)
{
var container = _client.GetContainer(databaseId, collectionId);
PartitionKey pk = new PartitionKey(item.key);
var response = await container.CreateItemAsync<dynamic>(pk, item);
return IsResponseValid(response.StatusCode);
}
The Solution 1 does not match my current implementation because <T>
is a List<ICompany>
where each ICompany
is a different concret type.
The Solution 2 is half a solution because the project rely today on the current behavior of the SDK (Exceptions included) to ensure that the project is resilient.
The Solution 3 is working thanks ! 👍
Could it be possible to have an option to avoid getting the Document back to the response ?
@YohanSciubukgian that's great. For solution 2 I updated it. There is a method response.EnsureSuccessStatusCode();
that will throw the exception like normal if it is needed.
Cosmos DB is working on supporting this option for fire and forget. It's probably a month or two away from being released.
@j82w inserting as dynamic works. But what about querying after the document is already in the database? How can I make this work?
/// <summary>
/// Queries the documents that matches the given predicate
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="predicate"></param>
/// <returns></returns>
private async Task<IQueryable<T>> QueryAsync<T>(Expression<Func<T, bool>> predicate) where T : class, IEntity
{
Container container = _context.GetContainer<T>();
QueryRequestOptions requestOptions = new()
{
MaxItemCount = -1,
MaxConcurrency = -1
};
IQueryable<T> query = container.GetItemLinqQueryable<T>(true, requestOptions: requestOptions);
if (predicate != null)
{
query = query.Where(predicate);
}
return await Task.FromResult(query);
}
The following code should work. If it does not work please create a new issue with the exception you are seeing.
private async Task<IQueryable<T>> QueryAsync<T>(Expression<Func<T, bool>> predicate) where T : class, IEntity
{
Container container = _context.GetContainer<T>();
QueryRequestOptions requestOptions = new()
{
MaxItemCount = -1,
MaxConcurrency = -1
};
// Do not use allowSynchronousQueryExecution. It does blocking calls which cause deadlock and thread starvation
IQueryable<T> query = container.GetItemLinqQueryable<T>(allowSynchronousQueryExecution: false, requestOptions: requestOptions);
if (predicate != null)
{
query = query.Where(predicate);
}
List<T> results = new List<T>();
using (FeedIterator<T> setIterator = query.ToFeedIterator<T>())
{
//Asynchronous query execution
while (setIterator.HasMoreResults)
{
var response = await setIterator.ReadNextAsync();
results.AddRange(response);
}
}
return results;
}
Describe the bug The scenario where the method
InsertItemAsync
/ReplaceItemAsync
/UpsertItemAsync
is called with an interface is working with the SDK v2 but not on the SDK v3.It's a blocking point to work with a schemaless database and not being able to work with interfaces to insert/replace/upsert documents. We could have a
<T>
being aList<ICompany>
where eachICompany
has a list of common properties & each concrete has dedicated properties to the current company.This is the stacktrace I had using the SDK v3 :
To Reproduce I created a repository with a sample usage using the SDK v2 & the SDK v3, it's available here : https://github.com/YohanSciubukgian/CosmosDbConnector
Expected behavior SDK v3 should support interface to be passed to generic type
<T>
while calling InsertItemAsync (like SDK v2)Actual behavior An Exception is being raised when InsertItemAsync is being called.
Environment summary Azure Cosmos DB emulator : v2.9.2 Microsoft.Azure.Cosmos : v3.6.0 Microsoft.Azure.DocumentDB.Core : v2.10.1 OS Version: Windows
Additional context I have added the Type declaration
DocumentBase<T>
onCreateItemAsync
/ReplaceItemAsync
/UpsertItemAsync
method but the exception still occurs. It looks like the type is redundant :It looks like (from the stacktrace) that the SDK v3 is trying to create an object based on the generic type for the response. I have added a break point before the Dispose(), on the UnitTests part, in order to analyze the CosmosDB (local emulator) behavior. It looks like the document has been created properly on CosmosDB but the response cannot be processed by the SDK v3.