MockQueryable icon indicating copy to clipboard operation
MockQueryable copied to clipboard

Not suported with EF7: ExecuteDeleteAsync and ExecuteUpdateAsync

Open SerhiyBalan opened this issue 2 years ago • 11 comments

Hello!

I've been using MockQueryable with .NET6 a lot Recently my project migrated to .NET 7 (EF Core 7) And I started to use new EF7 features:

  • ExecuteDeleteAsync
            ...
            .Where(user => user.Id == id)
            .ExecuteDeleteAsync();

ExecuteDeleteAsync implementation is here https://github.com/dotnet/efcore/blob/main/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs#L316

  • ExecuteUpdateAsync
            .Where(user => user.Id == id)
            .ExecuteUpdateAsync(updater => updater
                .SetProperty(
                    user => user.Status,
                    user => status)
                .SetProperty(
                    user => user.ModifiedBy,
                    user => _userContextProvider.GetUserId())
                .SetProperty(
                    user => user.ModifiedOn,
                    user => DateTimeOffset.UtcNow));

ExecuteUpdateAsync implementation is here https://github.com/dotnet/efcore/blob/main/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs#L372

I use MockQueryable using a standard pattern:

           var mock = users.BuildMock();
           _userRepository.Setup(x => x.GetQueryable()).Returns(mock);

ToListAsync(), FirstOrDefaultAsync() and others works perfectly with this pattern.

Unfortunately ExecuteUpdateAsync / ExecuteDeleteAsync doesn't work at all :(

When I try to run a unit test, a mocked GetQueryable method works nicely as usual, but it throws an exception on ExecuteDeleteAsync

 <System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.InvalidOperationException: There is no method 'ExecuteDelete' on type 'Microsoft.EntityFrameworkCore.RelationalQueryableExtensions' that matches the specified arguments
   at System.Linq.EnumerableRewriter.FindMethod(Type type, String name, ReadOnlyCollection`1 args, Type[] typeArgs)
   at System.Linq.EnumerableRewriter.VisitMethodCall(MethodCallExpression m)
   at System.Linq.EnumerableExecutor`1.Execute()
   at System.Linq.EnumerableQuery`1.System.Linq.IQueryProvider.Execute[TElement](Expression expression)
   at Microsoft.EntityFrameworkCore.RelationalQueryableExtensions.ExecuteDelete[TSource](IQueryable`1 source)
   at lambda_method34(Closure)
   at MockQueryable.Core.TestQueryProvider`1.CompileExpressionItem[TResult](Expression expression)
   at MockQueryable.Core.TestQueryProvider`1.Execute[TResult](Expression expression)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
   --- End of inner exception stack trace ---
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at MockQueryable.EntityFrameworkCore.TestAsyncEnumerableEfCore`1.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.RelationalQueryableExtensions.ExecuteDeleteAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)

Exception throws here on .Invoke(this, new object[] { expression }); https://github.com/romantitov/MockQueryable/blob/master/src/MockQueryable/MockQueryable.EntityFrameworkCore/TestQueryProviderEfCore.cs#L25

Same exception throws at ExecuteUpdateAsync (but it says There is no method 'ExecuteUpdate' on type 'Microsoft.EntityFrameworkCore.RelationalQueryableExtensions' that matches the specified arguments)

The custom logic pattern doesn't work because ExecuteUpdateAsync / ExecuteDeleteAsync are IQueryable<T> extensions

Thank you very much

SerhiyBalan avatar Jan 24 '23 18:01 SerhiyBalan

Is there a suggested approach on how to support this? @SerhiyBalan - did you have a workaround?

StuartBale-Xero avatar Jun 13 '23 23:06 StuartBale-Xero

@StuartBale-Xero

My project uses UnitOfWork / GenericRepository pattern

So instead of using native EF's ExecuteUpdateAsync / ExecuteDeleteAsync extensions I use own methods at the Generic Repository:

public class GenericRepository<TEntity> : IRepository<TEntity>
    where TEntity : class, new()
{
    public GenericRepository(DbContext dbContext)
    {
        ArgumentNullException.ThrowIfNull(dbContext);
        DbSet = dbContext.Set<TEntity>();
    }

    protected DbSet<TEntity> DbSet { get; }

    ...

    public virtual Task<int> ExecuteUpdateAsync(
        Expression<Func<TEntity, bool>> predicate,
        Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> setPropertyCalls,
        CancellationToken cancellationToken = default)
        => DbSet
            .AsQueryable()
            .Where(predicate)
            .ExecuteUpdateAsync(setPropertyCalls, cancellationToken);

    public virtual Task<int> ExecuteDeleteAsync(
        Expression<Func<TEntity, bool>> predicate,
        CancellationToken cancellationToken = default)
        => DbSet
            .AsQueryable()
            .Where(predicate)
            .ExecuteDeleteAsync(cancellationToken);
}

In code instead of

        await _unitOfWork
            .GetRepository<User>()
            .AsQueryable()
            .Where(user => user.Id == id)
            .ExecuteDeleteAsync(); // Native Entity Framework extension

I use my own methods:

        await _unitOfWork
            .GetRepository<User>()
            .ExecuteDeleteAsync(user => user.Id == id); // My method in GenericRepository

Since they are no more extensions, I can easily mock them

SerhiyBalan avatar Jun 14 '23 05:06 SerhiyBalan

Hi, guys!

Any update on this? I don't use the UoW / repository and it would be really interesting if MockQueryable could support these methods.

marcoatribeiro avatar Jul 19 '23 23:07 marcoatribeiro

Hi, guys!

Any update on this? I don't use the UoW / repository and it would be really interesting if MockQueryable could support these methods.

same here...

tiagoseminotti avatar Sep 22 '23 18:09 tiagoseminotti

Hello. Thanks for you contribution. Sorry for the late answer. Unfortunately I'm very busy at the moment. If you provide a pull request with fix of the issue I would be happy to include it to the next release. Please don't forget to cover the case by additional tests to minimize possibility of regressions for the future. Thanks for the understanding.

romantitov avatar Jan 23 '24 18:01 romantitov