Skip to content

Commit

Permalink
Merge pull request #81 from lazaro-ansaldi/master
Browse files Browse the repository at this point in the history
Add support to ExecuteDelete and ExecuteUpdate
  • Loading branch information
romantitov authored Sep 20, 2024
2 parents 7f02ca9 + 2023477 commit 534d79f
Show file tree
Hide file tree
Showing 6 changed files with 513 additions and 428 deletions.
160 changes: 87 additions & 73 deletions src/MockQueryable/MockQueryable.Core/TestQueryProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,78 +6,92 @@

namespace MockQueryable.Core
{
public abstract class TestQueryProvider<T> : IOrderedQueryable<T>, IQueryProvider
{
private IEnumerable<T> _enumerable;

protected TestQueryProvider(Expression expression)
{
Expression = expression;
}

protected TestQueryProvider(IEnumerable<T> enumerable)
{
_enumerable = enumerable;
Expression = enumerable.AsQueryable().Expression;
}

public IQueryable CreateQuery(Expression expression)
{
if (expression is MethodCallExpression m)
{
var resultType = m.Method.ReturnType; // it should be IQueryable<T>
var tElement = resultType.GetGenericArguments().First();
return (IQueryable) CreateInstance(tElement, expression);
}

return CreateQuery<T>(expression);
}

public IQueryable<TEntity> CreateQuery<TEntity>(Expression expression)
{
return (IQueryable<TEntity>) CreateInstance(typeof(TEntity), expression);
}

private object CreateInstance(Type tElement, Expression expression)
public abstract class TestQueryProvider<T> : IOrderedQueryable<T>, IQueryProvider
{
var queryType = GetType().GetGenericTypeDefinition().MakeGenericType(tElement);
return Activator.CreateInstance(queryType, expression);
}

public object Execute(Expression expression)
{
return CompileExpressionItem<object>(expression);
}

public TResult Execute<TResult>(Expression expression)
{
return CompileExpressionItem<TResult>(expression);
}

IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
if (_enumerable == null) _enumerable = CompileExpressionItem<IEnumerable<T>>(Expression);
return _enumerable.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
if (_enumerable == null) _enumerable = CompileExpressionItem<IEnumerable<T>>(Expression);
return _enumerable.GetEnumerator();
}

public Type ElementType => typeof(T);

public Expression Expression { get; }

public IQueryProvider Provider => this;

private static TResult CompileExpressionItem<TResult>(Expression expression)
{
var visitor = new TestExpressionVisitor();
var body = visitor.Visit(expression);
var f = Expression.Lambda<Func<TResult>>(body ?? throw new InvalidOperationException($"{nameof(body)} is null"), (IEnumerable<ParameterExpression>) null);
return f.Compile()();
}
}
// Hardcoding this constants to avoid the reference to EFCore
private const string EF_EXECUTE_UPDATE_METHOD_NAME = "ExecuteUpdate";
private const string EF_EXECUTE_DELETE_METHOD_NAME = "ExecuteDelete";

private IEnumerable<T> _enumerable;

protected TestQueryProvider(Expression expression)
{
Expression = expression;
}

protected TestQueryProvider(IEnumerable<T> enumerable)
{
_enumerable = enumerable;
Expression = enumerable.AsQueryable().Expression;
}

public IQueryable CreateQuery(Expression expression)
{
if (expression is MethodCallExpression m)
{
var resultType = m.Method.ReturnType; // it should be IQueryable<T>
var tElement = resultType.GetGenericArguments().First();
return (IQueryable) CreateInstance(tElement, expression);
}

return CreateQuery<T>(expression);
}

public IQueryable<TEntity> CreateQuery<TEntity>(Expression expression)
{
return (IQueryable<TEntity>) CreateInstance(typeof(TEntity), expression);
}

private object CreateInstance(Type tElement, Expression expression)
{
var queryType = GetType().GetGenericTypeDefinition().MakeGenericType(tElement);
return Activator.CreateInstance(queryType, expression);
}

public object Execute(Expression expression)
{
return CompileExpressionItem<object>(expression);
}

public TResult Execute<TResult>(Expression expression)
{
if (expression is MethodCallExpression methodCall && (methodCall.Method.Name == EF_EXECUTE_UPDATE_METHOD_NAME || methodCall.Method.Name == EF_EXECUTE_DELETE_METHOD_NAME)
&& typeof(TResult) == typeof(int))
{
// Intercept ExecuteDelete and ExecuteUpdate calls
var affectedItems = CompileExpressionItem<IEnumerable<T>>(Expression).ToList();
// Return the count of affected items
return (TResult)(object)affectedItems.Count;
}

// Fall back to default expression execution
return CompileExpressionItem<TResult>(expression);
}

IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
if (_enumerable == null) _enumerable = CompileExpressionItem<IEnumerable<T>>(Expression);
return _enumerable.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
if (_enumerable == null) _enumerable = CompileExpressionItem<IEnumerable<T>>(Expression);
return _enumerable.GetEnumerator();
}

public Type ElementType => typeof(T);

public Expression Expression { get; }

public IQueryProvider Provider => this;

private static TResult CompileExpressionItem<TResult>(Expression expression)
{
var visitor = new TestExpressionVisitor();
var body = visitor.Visit(expression);
var f = Expression.Lambda<Func<TResult>>(body ?? throw new InvalidOperationException($"{nameof(body)} is null"), (IEnumerable<ParameterExpression>) null);
return f.Compile()();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.20" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<ItemGroup>
<PackageReference Include="AutoMapper" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.20" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1">
Expand Down
177 changes: 90 additions & 87 deletions src/MockQueryable/MockQueryable.Sample/MyService.cs
Original file line number Diff line number Diff line change
@@ -1,108 +1,111 @@
using System;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;

namespace MockQueryable.Sample
{
public class MyService
{
private readonly IUserRepository _userRepository;

public static void Initialize()
public class MyService
{
Mapper.Initialize(cfg => cfg.CreateMap<UserEntity, UserReport>()
.ForMember(dto => dto.FirstName, conf => conf.MapFrom(ol => ol.FirstName))
.ForMember(dto => dto.LastName, conf => conf.MapFrom(ol => ol.LastName)));
Mapper.Configuration.AssertConfigurationIsValid();
private readonly IUserRepository _userRepository;

public static void Initialize()
{
Mapper.Initialize(cfg => cfg.CreateMap<UserEntity, UserReport>()
.ForMember(dto => dto.FirstName, conf => conf.MapFrom(ol => ol.FirstName))
.ForMember(dto => dto.LastName, conf => conf.MapFrom(ol => ol.LastName)));
Mapper.Configuration.AssertConfigurationIsValid();
}

public MyService(IUserRepository userRepository)
{
_userRepository = userRepository;
}

public async Task CreateUserIfNotExist(string firstName, string lastName, DateTime dateOfBirth)
{
var query = _userRepository.GetQueryable();

if (await query.AnyAsync(x => x.LastName == lastName && x.DateOfBirth == dateOfBirth))
{
throw new ApplicationException("User already exist");
}

var existUser = await query.FirstOrDefaultAsync(x => x.FirstName == firstName);
if (existUser != null)
{
throw new ApplicationException("User with FirstName already exist");
}

if (await query.CountAsync(x => x.DateOfBirth == dateOfBirth.Date) > 3)
{
throw new ApplicationException("Users with DateOfBirth more than limit");
}

await _userRepository.CreateUser(new UserEntity
{
FirstName = firstName,
LastName = lastName,
DateOfBirth = dateOfBirth.Date,
});

}

public async Task<List<UserReport>> GetUserReports(DateTime dateFrom, DateTime dateTo)
{
var query = _userRepository.GetQueryable();

query = query.Where(x => x.DateOfBirth >= dateFrom.Date);
query = query.Where(x => x.DateOfBirth <= dateTo.Date);

return await query.Select(x => new UserReport
{
FirstName = x.FirstName,
LastName = x.LastName,
}).ToListAsync();
}


public async Task<List<UserReport>> GetUserReportsAutoMap(DateTime dateFrom, DateTime dateTo)
{
var query = _userRepository.GetQueryable();

query = query.Where(x => x.DateOfBirth >= dateFrom.Date);
query = query.Where(x => x.DateOfBirth <= dateTo.Date);

return await query.ProjectTo<UserReport>().ToListAsync();
}
}

public MyService(IUserRepository userRepository)
public interface IUserRepository
{
_userRepository = userRepository;
}
IQueryable<UserEntity> GetQueryable();

public async Task CreateUserIfNotExist(string firstName, string lastName, DateTime dateOfBirth)
{
var query = _userRepository.GetQueryable();

if (await query.AnyAsync(x => x.LastName == lastName && x.DateOfBirth == dateOfBirth))
{
throw new ApplicationException("User already exist");
}

var existUser = await query.FirstOrDefaultAsync(x => x.FirstName == firstName);
if (existUser != null)
{
throw new ApplicationException("User with FirstName already exist");
}

if (await query.CountAsync(x => x.DateOfBirth == dateOfBirth.Date) > 3)
{
throw new ApplicationException("Users with DateOfBirth more than limit");
}

await _userRepository.CreateUser(new UserEntity
{
FirstName = firstName,
LastName = lastName,
DateOfBirth = dateOfBirth.Date,
});
Task CreateUser(UserEntity user);

}
Task<List<UserEntity>> GetAll();

public async Task<List<UserReport>> GetUserReports(DateTime dateFrom, DateTime dateTo)
{
var query = _userRepository.GetQueryable();
IAsyncEnumerable<UserEntity> GetAllAsync();

query = query.Where(x => x.DateOfBirth >= dateFrom.Date);
query = query.Where(x => x.DateOfBirth <= dateTo.Date);
Task<int> DeleteUserAsync(Guid id);

return await query.Select(x => new UserReport
{
FirstName = x.FirstName,
LastName = x.LastName,
}).ToListAsync();
Task<int> UpdateFirstNameByIdAsync(Guid id, string firstName);
}


public async Task<List<UserReport>> GetUserReportsAutoMap(DateTime dateFrom, DateTime dateTo)
public class UserReport
{
var query = _userRepository.GetQueryable();

query = query.Where(x => x.DateOfBirth >= dateFrom.Date);
query = query.Where(x => x.DateOfBirth <= dateTo.Date);

return await query.ProjectTo<UserReport>().ToListAsync();
public string FirstName { get; set; }
public string LastName { get; set; }
}
}

public interface IUserRepository
{
IQueryable<UserEntity> GetQueryable();

Task CreateUser(UserEntity user);

Task<List<UserEntity>> GetAll();

IAsyncEnumerable<UserEntity> GetAllAsync();
}


public class UserReport
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

public class UserEntity
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class UserEntity
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
}
Loading

0 comments on commit 534d79f

Please sign in to comment.