Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

No FindAsync() method on IDbSet<T>

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");
like image 467
Mister Epic Avatar asked Feb 15 '14 17:02

Mister Epic


3 Answers

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.

like image 185
Keith Payne Avatar answered Nov 03 '22 00:11

Keith Payne


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.

like image 33
Ron Deijkers Avatar answered Nov 03 '22 01:11

Ron Deijkers


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

like image 22
Johann Strydom Avatar answered Nov 02 '22 23:11

Johann Strydom