Database operations should be async, you shouldn't consume them synchronously, as they do IO. Async/Await came out in 2012, almost a decade ago (and with great first party library support, I might add).
Moralizing aside, sometimes you do want to call async APIs as synchronous code. I don't think there should be a synchronous version of the API implemented as well, you just need to do
var myValue = DoSomethingAsync().ConfigureAwait(false).Result;
which will avoid deadlocks, and execute your call synchronously
> var myValue = DoSomethingAsync().ConfigureAwait(false).Result;
Came out a decade ago and we still don't know how to use it safely.
This example doesn't compile because there is no Result on ConfiguredTaskAwaitable. Regardless, ConfigureAwait(false) does absolutely nothing here because this Task is not being awaited.
If you're going to block this thread, you must push the work to another thread or it's going to deadlock when the implementation tries to resume a continuation (unless the implementation is 100% perfect and the SynchronizationContext smiles upon you).
var result = Task.Run(() => CalculateAsync()).GetAwaiter().GetResult();
>> var myValue = DoSomethingAsync().ConfigureAwait(false).Result;
Doing this inside ASP.NET request processing code (e.g. a controller method) will result in thread pool starvation [1], if you see about 50-100 (the numbers are off the top of my head, so check for yourself) requests per minute hitting that line of code.
P.S.: Sorry for a medium link, but couldn't really find an alternative.
If this really needs to happen then I like .GetAwaiter().GetResult() instead of .Result to get the same exception behavior as await rather than the wrapped AggregateException that .Result throws. This is especially helpful if DoSomethingAsync sometimes throws synchronously rather than returning a task.
But there should be an easy way to do it right. Needing to call async functions from non-async code is not exactly an unusual thing. Or they need to make absolutely everything async which is also problematic, especially in terms of raw performance.
To avoid deadlocks, you don't need the ConfigureAwait(false) here, but you do need it to have been applied correctly in all the async code you're calling.
Some things simply don't have an async API at the low level, e.g. DNS lookup: there is no asynchronous version of getaddrinfo(3). So if you look at the .NET sources, you'll see that Dns.GetHostEntryAsync pushes a task to a thread pool that calls getaddrinfo(3).
In the end, you arrive at a "sync top-level APIs -- async library APIs -- sync low-level OS APIs" sandwich of dubious efficiency.
Because there are sometimes better things to do than block more threads. We can asynchronously queue dns requests to the same address (this is what we do in .NET 6)
There's plenty of database operations I don't do async. We heavily make use of ETLs. By their nature those processes are very linear and don't benefit at all from being async.
Moralizing aside, sometimes you do want to call async APIs as synchronous code. I don't think there should be a synchronous version of the API implemented as well, you just need to do
var myValue = DoSomethingAsync().ConfigureAwait(false).Result;
which will avoid deadlocks, and execute your call synchronously