-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make it easier to pass a CancellationToken to FindAsync #22667
Comments
Duplicate of #12012. @ChristopherHaws Solution 1.1 was discussed in #12012. Solution 1.2 would not work because keys can be arrays with a value converter (or without if your database supports them). An analyzer is interesting, although it's worth keeping in mind that analyzers have turned out to be expensive to create and tend to have a long bug tail, so I think we would only add one if the value was very high. |
Thanks for the response. I will remove solution 1.2.
Could this overhead be avoided by checking the last element of the array only if the key count doesn't match? This check is already happening somewhere since there is an exception for it.
Unfortunately I have had this happen more times than I wish to admit 😎, and it's not a fun one to track down in a dll with optimizations enabled. If we don't want to change the functionality of the |
@ChristopherHaws This comment:
Made me reconsider and I talked about it with the team today. I think we can use this to allow the cancellation token to be passed as the last value in the array. Thanks for bringing this up! |
How is the code fix incorrect? Your method takes cancellation token, you should pass it forward to any async methods invoked inside if they support it. |
I understand the logic behind the fix suggestion, the problem is if you apply the code fix as is then you end up with the following error because EF Core decided to put the cancellation token argument after the key values array:
EF does not have this design flaw because it puts the token argument before the params key values argument, compare the two signatures: EF DBSet EF Core DBSet So the correct analyzer code fix for EF Core specifically should unfortunately be:
|
Seems like the code fix is erroneous here though. Argument 1 type actually changes between both methods. It is not just overload which takes extra cancellation token parameter. So it should not suggest that code fix. |
@bcronje Regardless of the EF Core behavior, this bug with the analyzer should be reported against the analyzer. (I'm not sure where analyzer issues are tracked.) |
@ajcvickers I agree and apologize if it came across that I wanted to hijack this issue, I just mentioned it because the OP's solution 2 mentioned analyzers and I ran into this issue yesterday. |
https://github.com/dotnet/roslyn-analyzers is the repo. dotnet/roslyn-analyzers#3641 implemented that particular code fix. |
@bcronje Sorry, I didn't mean it to come across like that. I just wanted to make sure the analyzer bug didn't get missed. :-) |
I opened dotnet/roslyn-analyzers#4842 to track the analyzer bug. |
Hey @bcronje, I wrote that analyzer. I remember we narrowed its behavior to only fire when the If you don't want to propagate the You can propagate the pragma warning disable CA2016
... your invocation ...
#pragma warning restore CA2016 Thanks @Youssef1313 for opening the issue in roslyn-analyzers, I'll take a look when I get a chance. |
Update: We do not support the scenario where the cancellation token is not in the last position: So the problem seems to be that |
So this does not seem to be a problem with CA2016. We are boxing the I can't repro the error case though. See my code snippet here: dotnet/roslyn-analyzers#4842 (comment) |
I've created this bug for myself about three times using EFCore now, and I found this issue while seeing if there was already one open related to it. Would there be any value in adding an in-box extension method for namespace Microsoft.EntityFrameworkCore
{
public static class DbSetExtensions
{
public static ValueTask<TEntity?> FindItemAsync<TEntity, TKey>(
this DbSet<TEntity> set,
TKey keyValue,
CancellationToken cancellationToken = default)
where TEntity : class
{
if (keyValue is null)
{
throw new ArgumentNullException(nameof(keyValue));
}
return set.FindAsync(new object[] { keyValue }, cancellationToken);
}
}
} |
@martincostello Or you could do this (which is similar to my proposed solution) public static class DbSetExtensions {
public static async ValueTask<T?> FindItemAsync<T>(this DbSet<T> set, params object[] keyValues) where T : class =>
keyValues[^1] is CancellationToken ct ? await set.FindAsync(keyValues[0..^1], ct) : await set.FindAsync(keyValues);
} I also tend to make my own extension methods for every entity so that I have type safety: public static class DbSetExtensions {
public static async Task<Blog?> GetAsync(this DbSet<Blog> set, int blogId, CancellationToken ct = default) =>
await set.FindAsync(new object[] { blogId }, ct);
public static async Task<Blog> GetRequiredAsync(this DbSet<Blog> set, int blogId, CancellationToken ct = default) =>
await set.GetAsync(blogId, ct) ?? throw new Exception($"Could not find blog {blogId}.");
} |
So, two years later, and the issue still exists in .NET 8? And I didn't see a workaround? |
@SoftCircuits This issue was fixed in EF7. If you are still encountering problems, then please open a new issue and attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate. |
FindAsync currently takes a params object array as its first parameter and cancellation token as its second parameter. In entities that use struct's as keys (which is probably the norm for most entities) it is easy to mess up the parameters to this method.
For example, given the following entity context:
Attempt One
The first attempt to use
FindAsync
would probably look like this, which will compile but is actually passing both parameters to the object array and not passing the cancellation token to the correct parameter:This results in the following error:
Attempt Two
The second attempt to use
FindAsync
would probably be to change the first parameter to be an array so the type system passes the correct parameters:This however does not work because the array is not specifically an
object[]
, so you still get the following error:NOTE: If BlogId were a reference type (i.e. string) instead of a value type, this usage actually works.
Correct Usage
Possible Solutions
Solution 1
The first, an my prefered, solution would be to add some checks in
FindAsync
to detect if the last item in thekeyValues
parameter is aCancellationToken
if the count of keys in the entity being queried does not match the count of keys passed in. In this scenario, if the last item is aCancellationToken
, use it as aCancellationToken
instead of part of the key.Solution 2
Add analyzers to detect this incorrect usage of the API to provide build time errors instead of runtime errors.
The text was updated successfully, but these errors were encountered: