C# avoid DocumentNotFoundException

Hi,
is there any way to avoid getting AggregateException / DocumentNotFoundException on cache access with an unknown key, and make it just return null ?
In my usecase, that makes exception watching and debugging for real problems almost impossible with hundreds of couchbase exceptions messing up logging and exception breaking.
Thanks in advance

1 Like

@ube

Can you provide more specifics? Are you referring to Couchbase.Extensions.Caching? Because I believe it does currently return null when the cache misses. Is there some particular case where it doesn’t?

I’m using CouchbaseNetClient 3.3.5 in a .NET 6 app, and that driver returns an AggregateException with a DocumentNotFound inner exception on cache miss.

So you’re referring to using the ICouchbaseCollection directly, not using a specific caching layer on top of the SDK? In that case, no, exceptions are the way all errors are returned, including missing documents. If you are using it as a caching system rather than a database then the intention is that you will catch the errors.

That said, there are some approaches which may help you capture the exception more easily. The primary difficulty lies in the AggregateException, which is how .NET is designed to handle exceptions returned by asynchronous code.

Your first option is to use async/await. If you await the task, the await logic in C# will “unwrap” the AggregateException and throw DocumentNotFoundException at the await point. You can then do a try { await cluster.GetAsync(...); } catch (DocumentNotFoundException ex) { ... } to easily handle those specific exceptions.

If you aren’t using async/await and are using some other mechanism to await completion, like ContinueWith, you can use task.GetAwaiter().GetResult() to get the same behavior. For example, task.ContinueWith(t => t.GetAwaiter().GetResult()) will throw the DocumentNotFoundException at the GetResult call, unwrapping the AggregateException.

Thank you !

But sadly, its not about catching the exception, thats straightforward. The code works and handles the exceptions. The question is about how to avoid getting the exception in the first place, so one can reasonably debug for real exceptions.

In my eyes, an exception is, by definition, an exception: An unexpected, exceptional, condition.
In my case, it’s not an error, but a condition thats occuring in almost all cases, specially during testing.

Specially during testing, getting loads of non-exceptional exceptions is, well, somewhat irritating. Thats why i was looking for a to just make the client return a null value or some other non-exception result that says “nope”.
Suppressing all AggregateException cases isn’t a reasonable option either, as i’m testing for error occurances triggering exceptions, handled or unhandled…

So based on your reply, i shouldn’t use the client but the netcore caching system instead?

If your primary purpose is to act as a cache within a modern .NET Core/6 application, then yes I’d consider using Couchbase.Extensions.Caching. This is an implementation of IDistributedCache from ASP.NET Core, which then allows it to be used by many different systems within your application that may need a cache. It also supports a lot of useful cache paradigms as exposed by IDistributedCache.

OK, thank you very much!
Seems i’ll have to rewrite that part… If thats a working solution, its great

Check if the document exists before trying to get it.

https://docs.couchbase.com/sdk-api/couchbase-net-client/api/Couchbase.KeyValue.ICouchbaseCollection.html#Couchbase_KeyValue_ICouchbaseCollection_ExistsAsync_System_String_Couchbase_KeyValue_ExistsOptions_

Thanks for the feedback, this is something we will consider when designing the next Major version.

Jeff

@mreiche thank you, but that would mean doing two server roundtrips instead of one, to avoid triggering an error (exception) in a condition that is not an error (exceptional) state at all?

While I can somewhat see a reason for your decision to handle a not-found state this way, it’s, well, a slightly unusual architectural decision for a database.

If I request data from SQL and the query returns no value, i get no value, plain and simple, and if the query causes an error, I get an exception.

In my case, I think btburnett3 offered a good solution which I’ll implement, thank you again.

But I would still recommend as @jmorris offered to overthink that architectural decision and at least offer a way (read: switch) to return null instead of an exception to have a clearer separation between an error state and a non-error. Implemented as a config toggle, it could even be a minor release feature :grinning:

Thanks again for all your help, I’ll try the Couchbase.Extensions.Caching way tomorrow to avoid getting AggregateExceptions with DocumentNotFound inner exceptions. (repeated for search engine lookups)

Likewise, you can use N1QL which has the same behavior as SQL.
The difference between the SQL/N1QL API versus the kv API is that SQL/N1QL returns 0 or more rows, while the kv api returns exactly one document. There is no possibility of it returning 0 documents, because …

and at least offer a way (read: switch) to return null instead of an exception

This is where things get hairy. If a cache can store anything (as couchbase can), then null is a valid value in the cache, and returning null on a cache-miss is indistinguishable from a cache-hit that had a value of null. (Although I believe that the couchbase SDKs prevent the storing of a null document - at least the java SDK does). The couchbase implementation of spring-data cache does support caching of null (when AllowCacheNullValues==true) and can distinguish it from a cache-miss. See cache.put("key",null) fails [DATACOUCH-628] · Issue #939 · spring-projects/spring-data-couchbase · GitHub. For discussion of this in the Couchbase Java SDK see Loading.... ECMA-404 allows a null document.

Lets agree to disagree then on architectural decisions :wink: You had reasons to implement it your way, i would go a different way (return object with value property), thats life.
Thankfully, you offered an alternative solution which i’m implementing right now.

Note after trying: DistributedCache seems to be a great alternative.

Limitation: You have to have exactly one bucket to handle. Sadly, i got 7… So usecase specific not an option for me

You can use spring data cache with couchbase or use n1ql select which behaves the same as sql select.

“by definition, an exception: An unexpected, exceptional, condition.
In my case, it’s not an error,”

We do agree, it is DocumentNotFoundException, it is not DocumentNotFoundError.

The proposition of returning null for a missing document is ambiguous as it is indistinguishable from an existing document that is null. :wink: Returning an object with a value is a different proposition. This would require that the result of every get() be checked to ensure that the document exists. Folks get tired of that pretty quick. Throwing the DocumentNotFoundException is consistent with the other kv methods - replace() and remove()

You can submit an enhancement request at issues.couchbase.com. There is already a ticket for this in the java sdk - JCBC-1920. There is even a pull request for it with a note on it “Abandoning for now given the pushback”

Hello, we have the same issue.
In .Net exception cost a lot to handle.
And adding an if ExitstAsync() adds a back and forth that seems unnecessary)

We would much prefer to have either:

  • an option in the GetOptions() to ignore missing values
  • a property in the IGetResult that would tell us the the document did not exist
  • or a new method GetIfExistAsync() that would always return a non null type, with a property to tell us if the document exist or not

Hi -

We’ve added a TryGetAsync method in 3.4.3 which should be GA this week.

Jeff