Skip to content

Make it easier to pass a CancellationToken to FindAsync #22667

@ChristopherHaws

Description

@ChristopherHaws

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:

public class ApplicationContext : DbContext {
    public ApplicationContext(DbContextOptions<ApplicationContext> options)
        : base(options) {
    }

    public DbSet<Blog> Blogs { get; set; } = default!;
}

public sealed class Blog {
    public Guid BlogId { get; set; }
    public string Name { get; set; } = default!;
}

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:

var blog = await db.Blogs.FindAsync(blogId, cancellationToken);

This results in the following error:

ArgumentException:
Entity type 'Blog' is defined with a single key property, but 2 values were passed to the 'DbSet.Find' method.

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:

var blog = await db.Blogs.FindAsync(new[] { blogId }, cancellationToken);

This however does not work because the array is not specifically an object[], so you still get the following error:

ArgumentException:
Entity type 'Blog' is defined with a single key property, but 2 values were passed to the 'DbSet.Find' method.

NOTE: If BlogId were a reference type (i.e. string) instead of a value type, this usage actually works.

Correct Usage

var blog = await db.Blogs.FindAsync(new object[] { blogId }, cancellationToken);

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 the keyValues parameter is a CancellationToken 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 a CancellationToken, use it as a CancellationToken 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.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions