Skip to content

Commit 20ce4f4

Browse files
authored
Merge pull request #86 from romantitov/73_ExpressionVisitor
73 expression visitor
2 parents 7f02ca9 + 914e93d commit 20ce4f4

22 files changed

+1850
-1082
lines changed

.github/workflows/dotnetcore.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
- name: Setup .NET Core
1313
uses: actions/setup-dotnet@v1
1414
with:
15-
dotnet-version: '6.0.x'
15+
dotnet-version: '8.0.x'
1616

1717
- name: Build with dotnet
1818
run: dotnet build src/MockQueryable/*.sln --configuration Release

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Do you prefer *DbSet*?
5656

5757
```csharp
5858
//2 - build mock by extension
59-
var mock = users.AsQueryable().BuildMockDbSet();
59+
var mock = users.BuildMockDbSet();
6060

6161
//3 - setup DbSet for Moq
6262
var userRepository = new TestDbSetRepository(mock.Object);
@@ -77,7 +77,7 @@ var users = new List<UserEntity>
7777
new UserEntity{Id = userId,LastName = "ExistLastName", DateOfBirth = DateTime.Parse("01/20/2012")},
7878
//etc.
7979
};
80-
var mock = users.AsQueryable().BuildMockDbSet();
80+
var mock = users.BuildMockDbSet();
8181

8282
//Aditional setup for FindAsync
8383
mock.Setup(x => x.FindAsync(userId)).ReturnsAsync((object[] ids) =>
@@ -91,6 +91,22 @@ var userRepository = new TestDbSetRepository(mock.Object);
9191
var user = await ((DbSet<UserEntity>) userRepository.GetQueryable()).FindAsync(userId);
9292
```
9393

94+
You can also add your custom expression visitor with custom logic:
95+
96+
```C#
97+
98+
var users = new List<UserEntity>
99+
{
100+
new UserEntity{Id = userId,LastName = "ExistLastName", DateOfBirth = DateTime.Parse("01/20/2012")},
101+
//etc.
102+
};
103+
104+
//Bould mock with custom SampleLikeExpressionVisitor, that emulates EF.Functions.Like
105+
var mockDbSet = users.BuildMockDbSet<UserEntity, SampleLikeExpressionVisitor>();
106+
var userRepository = new TestDbSetRepository(mockDbSet.Object);
107+
108+
```
109+
94110
Check out the [sample project](https://github.com/romantitov/MockQueryable/tree/master/src/MockQueryable/MockQueryable.Sample)
95111

96112
### Where can I get it?

src/MockQueryable/MockQueryable.Core/MockQueryable.Core.csproj

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netstandard2.1</TargetFramework>
4+
<TargetFramework>net8</TargetFramework>
55
<PackageId>MockQueryable.Core</PackageId>
66
<Authors>Roman Titov</Authors>
77
<Description>
@@ -14,11 +14,14 @@
1414
<PackageTags>Mock EntityFrameworkCore Queryable mock EF UnitTests EntityFrameworkCore</PackageTags>
1515
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
1616
<PackageReleaseNotes>
17-
#80 Altered namespace for extension method to revert a breaking change - Thanks @StevePy
17+
#81 Add support to ExecuteDelete and ExecuteUpdate - Thanks @lazaro-ansaldi and @Catlandor
18+
#82 Update versions of Moq, NSubstitute and FakeItEasy to newer versions without known vulnerabilities - Thanks @Catlandor
19+
#73 Supporting EF.Functions.Like and/or prossibility to provide cusotm ExpressionVisitor - Thanks @Catlandor
20+
#66 Not suported with EF7: ExecuteDeleteAsync and ExecuteUpdateAsync - Thanks @Catlandor
1821
</PackageReleaseNotes>
19-
<Version>7.0.3</Version>
20-
<AssemblyVersion>7.0.0.3</AssemblyVersion>
21-
<FileVersion>7.0.0.3</FileVersion>
22+
<Version>8.0.0</Version>
23+
<AssemblyVersion>8.0.0.0</AssemblyVersion>
24+
<FileVersion>8.0.0.0</FileVersion>
2225
<Company></Company>
2326
<PackageLicenseFile>LICENSE</PackageLicenseFile>
2427
<PackageReadmeFile>README.md</PackageReadmeFile>

src/MockQueryable/MockQueryable.Core/TestQueryProvider.cs

Lines changed: 70 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -4,80 +4,82 @@
44
using System.Linq;
55
using System.Linq.Expressions;
66

7-
namespace MockQueryable.Core
7+
namespace MockQueryable.Core;
8+
9+
public abstract class TestQueryProvider<T, TExpressionVisitor> : IOrderedQueryable<T>, IQueryProvider
10+
where TExpressionVisitor : ExpressionVisitor, new()
811
{
9-
public abstract class TestQueryProvider<T> : IOrderedQueryable<T>, IQueryProvider
10-
{
11-
private IEnumerable<T> _enumerable;
12+
private IEnumerable<T> _enumerable;
13+
1214

1315
protected TestQueryProvider(Expression expression)
14-
{
15-
Expression = expression;
16-
}
16+
{
17+
Expression = expression;
18+
}
1719

1820
protected TestQueryProvider(IEnumerable<T> enumerable)
19-
{
20-
_enumerable = enumerable;
21-
Expression = enumerable.AsQueryable().Expression;
22-
}
23-
24-
public IQueryable CreateQuery(Expression expression)
25-
{
26-
if (expression is MethodCallExpression m)
27-
{
28-
var resultType = m.Method.ReturnType; // it should be IQueryable<T>
29-
var tElement = resultType.GetGenericArguments().First();
30-
return (IQueryable) CreateInstance(tElement, expression);
31-
}
32-
33-
return CreateQuery<T>(expression);
34-
}
35-
36-
public IQueryable<TEntity> CreateQuery<TEntity>(Expression expression)
37-
{
38-
return (IQueryable<TEntity>) CreateInstance(typeof(TEntity), expression);
21+
{
22+
_enumerable = enumerable;
23+
Expression = enumerable.AsQueryable().Expression;
24+
25+
}
26+
27+
public IQueryable CreateQuery(Expression expression)
28+
{
29+
if (expression is MethodCallExpression m)
30+
{
31+
var resultType = m.Method.ReturnType; // it should be IQueryable<T>
32+
var tElement = resultType.GetGenericArguments().First();
33+
return (IQueryable)CreateInstance(tElement, expression);
34+
}
35+
36+
return CreateQuery<T>(expression);
37+
}
38+
39+
public IQueryable<TEntity> CreateQuery<TEntity>(Expression expression)
40+
{
41+
return (IQueryable<TEntity>)CreateInstance(typeof(TEntity), expression);
3942
}
4043

41-
private object CreateInstance(Type tElement, Expression expression)
44+
protected abstract object CreateInstance(Type tElement, Expression expression);
45+
46+
47+
public object Execute(Expression expression)
4248
{
43-
var queryType = GetType().GetGenericTypeDefinition().MakeGenericType(tElement);
44-
return Activator.CreateInstance(queryType, expression);
45-
}
46-
47-
public object Execute(Expression expression)
48-
{
49-
return CompileExpressionItem<object>(expression);
50-
}
51-
52-
public TResult Execute<TResult>(Expression expression)
53-
{
54-
return CompileExpressionItem<TResult>(expression);
55-
}
56-
57-
IEnumerator<T> IEnumerable<T>.GetEnumerator()
58-
{
59-
if (_enumerable == null) _enumerable = CompileExpressionItem<IEnumerable<T>>(Expression);
60-
return _enumerable.GetEnumerator();
61-
}
62-
63-
IEnumerator IEnumerable.GetEnumerator()
64-
{
65-
if (_enumerable == null) _enumerable = CompileExpressionItem<IEnumerable<T>>(Expression);
66-
return _enumerable.GetEnumerator();
67-
}
68-
69-
public Type ElementType => typeof(T);
70-
71-
public Expression Expression { get; }
72-
73-
public IQueryProvider Provider => this;
74-
75-
private static TResult CompileExpressionItem<TResult>(Expression expression)
76-
{
77-
var visitor = new TestExpressionVisitor();
78-
var body = visitor.Visit(expression);
79-
var f = Expression.Lambda<Func<TResult>>(body ?? throw new InvalidOperationException($"{nameof(body)} is null"), (IEnumerable<ParameterExpression>) null);
80-
return f.Compile()();
81-
}
82-
}
49+
return CompileExpressionItem<object>(expression);
50+
}
51+
52+
public virtual TResult Execute<TResult>(Expression expression)
53+
{
54+
55+
return CompileExpressionItem<TResult>(expression);
56+
}
57+
58+
IEnumerator<T> IEnumerable<T>.GetEnumerator()
59+
{
60+
_enumerable ??= CompileExpressionItem<IEnumerable<T>>(Expression);
61+
return _enumerable.GetEnumerator();
62+
}
63+
64+
IEnumerator IEnumerable.GetEnumerator()
65+
{
66+
_enumerable ??= CompileExpressionItem<IEnumerable<T>>(Expression);
67+
return _enumerable.GetEnumerator();
68+
}
69+
70+
public Type ElementType => typeof(T);
71+
72+
public Expression Expression { get; }
73+
74+
public IQueryProvider Provider => this;
75+
76+
private static TResult CompileExpressionItem<TResult>(Expression expression)
77+
{
78+
var visitor = new TExpressionVisitor();
79+
var body = visitor.Visit(expression);
80+
var f = Expression.Lambda<Func<TResult>>(
81+
body ?? throw new InvalidOperationException($"{nameof(body)} is null"),
82+
(IEnumerable<ParameterExpression>)null);
83+
return f.Compile()();
84+
}
8385
}

src/MockQueryable/MockQueryable.EntityFrameworkCore/MockQueryable.EntityFrameworkCore.csproj

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6</TargetFramework>
4+
<TargetFramework>net8</TargetFramework>
55
<PackageId>MockQueryable.EntityFrameworkCore</PackageId>
66
<Authors>Roman Titov</Authors>
77
<Description>
@@ -15,11 +15,14 @@
1515
<PackageTags>Mock EntityFrameworkCore Queryable mock EF UnitTests EntityFrameworkCore</PackageTags>
1616
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
1717
<PackageReleaseNotes>
18-
#80 Altered namespace for extension method to revert a breaking change - Thanks @StevePy
18+
#81 Add support to ExecuteDelete and ExecuteUpdate - Thanks @lazaro-ansaldi and @Catlandor
19+
#82 Update versions of Moq, NSubstitute and FakeItEasy to newer versions without known vulnerabilities - Thanks @Catlandor
20+
#73 Supporting EF.Functions.Like and/or prossibility to provide cusotm ExpressionVisitor - Thanks @Catlandor
21+
#66 Not suported with EF7: ExecuteDeleteAsync and ExecuteUpdateAsync - Thanks @Catlandor
1922
</PackageReleaseNotes>
20-
<Version>7.0.3</Version>
21-
<AssemblyVersion>7.0.0.3</AssemblyVersion>
22-
<FileVersion>7.0.0.3</FileVersion>
23+
<Version>8.0.0</Version>
24+
<AssemblyVersion>8.0.0.0</AssemblyVersion>
25+
<FileVersion>8.0.0.0</FileVersion>
2326
<Company></Company>
2427
<PackageLicenseFile>LICENSE</PackageLicenseFile>
2528
<PackageReadmeFile>README.md</PackageReadmeFile>
@@ -45,7 +48,7 @@
4548
</None>
4649
</ItemGroup>
4750
<ItemGroup>
48-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.0" />
51+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.7" />
4952
</ItemGroup>
5053

5154
<ItemGroup>
Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
using MockQueryable.EntityFrameworkCore;
1+
using MockQueryable.Core;
2+
using MockQueryable.EntityFrameworkCore;
23
using System.Collections.Generic;
34
using System.Linq;
5+
using System.Linq.Expressions;
46

57
// Moving MockQueryableExtensions BuildMock into the MockQueryable.EntityFrameworkCore
68
// namespace had breaking changes with earlier extensions added to MockQueryable.Moq
@@ -9,11 +11,19 @@
911
// is dependent on the EF Core AsyncEnumerable.
1012
namespace MockQueryable
1113
{
12-
public static class MockQueryableExtensions
14+
public static class MockQueryableExtensions
15+
{
16+
public static IQueryable<TEntity> BuildMock<TEntity>(this ICollection<TEntity> data)
17+
where TEntity : class
1318
{
14-
public static IQueryable<TEntity> BuildMock<TEntity>(this IEnumerable<TEntity> data) where TEntity : class
15-
{
16-
return new TestAsyncEnumerableEfCore<TEntity>(data);
17-
}
19+
return new TestAsyncEnumerableEfCore<TEntity, TestExpressionVisitor>(data, entity => data.Remove(entity));
1820
}
21+
22+
public static IQueryable<TEntity> BuildMock<TEntity, TExpressionVisitor>(this ICollection<TEntity> data)
23+
where TEntity : class
24+
where TExpressionVisitor : ExpressionVisitor, new()
25+
{
26+
return new TestAsyncEnumerableEfCore<TEntity, TExpressionVisitor>(data, entity => data.Remove(entity));
27+
}
28+
}
1929
}

0 commit comments

Comments
 (0)