No FindAsync() method on IDbSet<T>
Asked Answered
A

5

24

Is there a reason that the FindAsync() method is omitted from the IDbSet<T> interface? Find is part of the interface, it seems odd the async version isn't available. I'm needing to cast to DbSet<T> to access it, which is a bit cumbersome:

User user = await ((DbSet<User>)db.Users)
    .FindAsync("de7d5d4a-9d0f-48ff-9478-d240cd5eb035");
Arsenical answered 15/2, 2014 at 17:21 Comment(0)
B
16

If you own the consumer of IDbSet<T>, which I assume that you do because you want to have access to FindAsync() from within the consumer, then a simple solution is to create your own interface that includes IDbSet and contains whichever FindAsync() method that you want to use:

public interface IAsyncDbSet<T> : IDbSet<T>
    where T : class
{
    Task<T> FindAsync(params Object[] keyValues);
}

This solves the problem of not having to cast to DbSet - which, by the way, blows away the abstractness benefit of contract coding. But this also introduces its own set of problems.

A better solution (imo) that requires a bit more work is to define an interface that contains only the members that you want to use in what would otherwise be your DbSet object, subclass DbSet while implementing the interface, then use that interface in your code:

public interface IMyAsyncDbSet<TEntity>
    where TEntity : class
{
    TEntity Add(TEntity entity);
    TEntity Remove(TEntity entity);

    // Copy other methods from IDbSet<T> as needed.

    Task<Object> FindAsync(params Object[] keyValues);
}

public class MyDbSet<T> : DbSet<T>, IMyAsyncDbSet<T>
    where T : class
{
}

This is an Adapter pattern, really. It decouples the interface that your code expects from the interface that Entity Framework provides. Right now, they are identical - which is why the implementation does nothing except inherit DbSet<T>. But later on they might diverge. At that point you will still be able to use the latest DbSet without breaking your code.

Benenson answered 15/2, 2014 at 18:16 Comment(3)
Please make this a separate question because the answer is yes, but the code will not fit into a comment.Benenson
I’ve created a new question: #23731449Dumbhead
can you post you’re the code as answer to the new question?Dumbhead
C
14

Here is how I tackled this in one of our projects:

using System.Threading.Tasks;

namespace System.Data.Entity
{
    public static class IDbSetExtensions
    {
        /// <summary>
        /// If possible asynchronously finds an entity with the given primary key values 
        /// otherwise finds the entity synchronously.  
        /// If an entity with the given primary key values exists in the context, then it is
        /// returned immediately without making a request to the store. Otherwise, a
        /// request is made to the store for an entity with the given primary key values
        /// and this entity, if found, is attached to the context and returned. If no
        /// entity is found in the context or the store, then null is returned.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="this"></param>
        /// <param name="keyValues">The values of the primary key for the entity to be found.</param>
        /// <returns>A task that represents the asynchronous find operation. The task result contains 
        /// the entity found, or null.</returns>
        /// <exception cref="System.InvalidOperationException"></exception>
        public static async Task<TEntity> FindAsync<TEntity>(this IDbSet<TEntity> @this, params object[] keyValues)
        where TEntity : class
        {
            DbSet<TEntity> thisDbSet = @this as DbSet<TEntity>;
            if (thisDbSet != null)
            {
                return await thisDbSet.FindAsync(keyValues);
            }
            else
            {
                return @this.Find(keyValues);
            }
        }
    }
}

One might consider wrapping the the Find method in a async-over-sync pattern which would provide offloading (and no scalability as true asynchronous methods do). However the caller must then be aware of this to make sure they aren't going to call methods on the context after calling the FindAsync method that might interfer. Making callers aware of a specific implementation isn't a very good design imho however because it can easily lead to problems. For the OP the IDbSet is a DbSet however so the call will be asynchronous.

Chiapas answered 16/4, 2015 at 13:29 Comment(4)
Thanks a lot for the implementation, I find it cleaner this way. I got a question: I see that we implement an "unboxing" when "DbSet<TEntity> thisDbSet = (DbSet<TEntity>)@this;". Is this unboxing expensive?Dagger
It is only an explicit cast so it won't be too expensive. An optimization to the code would be to use the "as" operator. Unboxing only happens when a value type is converted from type "object" or an interface.Chiapas
RonDeijkers: Thanks a bunch! I'm using Resharper and it did some cool trick to it: this is what happened (inside the method): var set = @this as DbSet<TEntity>; if(set == null) return @this.Find(keyValues); DbSet<TEntity> thisDbSet = set; return await thisDbSet.FindAsync(keyValues);Dagger
In fact, in C#7, this becomes a one liner with pattern matching if (@this is DbSet<TEntity> thisDbSet) {...}Cloudlet
R
4

I believe the correct way these days (since EF 6) involves inheriting from DbSet instead of implementing IDbSet.

Rummer answered 31/5, 2016 at 12:54 Comment(0)
D
1

Change the FindAsync method to FirstOrDefaultAsync(x => x.Id == yourId);

Duad answered 3/8, 2015 at 21:51 Comment(1)
This is not always the case. Find method is kind of special because "If an entity with the given primary key values exists in the context, then it is returned immediately without making a request to the store." msdn.microsoft.com/en-us/library/gg696418%28v=vs.113%29.aspxDagger
M
-5

Use this extension to solved the FindAsync problem

/// <summary>
/// IDbSet extension
/// </summary>
public static class IDbSetExtension
{
    public static Task<TEntity> FindAsync<TEntity>(this IDbSet<TEntity> set, params object[] keyValues) 
        where TEntity : class
    {
        return Task<TEntity>.Run(() =>
        {
            var entity = set.Find(keyValues);
            return entity;
        });
    }
}
Megalopolis answered 2/4, 2015 at 14:43 Comment(2)
This method is using the "async over sync" pattern, which is an anti-pattern. If the work should be done asynchronously, then one should be using inherently asynchronous operations of the query provider.Vevine
spawning a thread to do IO work is a big no-no, at least in library code. in an asp.net context, spawning threads will be bad for performance too, since you're basically stealing a thread from asp that could be used for serving requests.Precarious

© 2022 - 2024 — McMap. All rights reserved.