Skip to content

Commit e116a40

Browse files
VS-143: Enable the Analyzer to Display Alerts for Queries that Do NOT work on EF Provider (#80)
1 parent dab5582 commit e116a40

File tree

6 files changed

+159
-15
lines changed

6 files changed

+159
-15
lines changed

Diff for: src/MongoDB.Analyzer/Core/Linq/LinqAnalysisConstants.cs

+6
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,9 @@ internal static class LinqAnalysisErrorMessages
2525
{
2626
public const string MethodInvocationNotSupported = "Method referencing lambda parameter is not supported LINQ expression.";
2727
}
28+
29+
internal static class EFAnalysisErrorMessages
30+
{
31+
public const string ByteArraysNotSupported = "Byte array type is not supported by this version of the EF provider.";
32+
public const string GroupByMethodNotSupported = "GroupBy is not supported by this version of the EF provider.";
33+
}

Diff for: src/MongoDB.Analyzer/Core/Linq/LinqExpressionProcessor.cs

+46-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ mongoQueryableTypeInfo.Type is not INamedTypeSymbol mongoQueryableNamedType ||
9595

9696
try
9797
{
98-
if (PreanalyzeLinqExpression(node, semanticModel, invalidExpressionNodes))
98+
if ((analysisType == AnalysisType.Linq && PreanalyzeLinqExpression(node, semanticModel, invalidExpressionNodes)) ||
99+
(analysisType == AnalysisType.EF && PreanalyzeEFExpression(node, semanticModel, invalidExpressionNodes, mongoQueryableNamedType)))
99100
{
100101
var generatedMongoQueryableTypeName = typesProcessor.ProcessTypeSymbol(mongoQueryableNamedType.TypeArguments[0]);
101102

@@ -172,4 +173,48 @@ private static bool PreanalyzeLinqExpression(SyntaxNode linqExpressionNode, Sema
172173

173174
return result;
174175
}
176+
177+
private static bool PreanalyzeEFExpression(SyntaxNode efExpressionNode, SemanticModel semanticModel, List<InvalidExpressionAnalysisNode> invalidEFExpressionNodes, INamedTypeSymbol mongoQueryableNamedType)
178+
{
179+
var result = true;
180+
var typeArgument = mongoQueryableNamedType.TypeArguments[0];
181+
182+
foreach (var member in typeArgument.GetMembers())
183+
{
184+
if (member is IPropertySymbol propertySymbol)
185+
{
186+
// Check TypeArgument for Binary/Byte Array Properties
187+
if (propertySymbol.Type is IArrayTypeSymbol arrayTypeSymbol &&
188+
arrayTypeSymbol.ElementType.SpecialType == SpecialType.System_Byte)
189+
{
190+
invalidEFExpressionNodes.Add(new(
191+
efExpressionNode,
192+
EFAnalysisErrorMessages.ByteArraysNotSupported));
193+
194+
return false;
195+
}
196+
}
197+
}
198+
199+
foreach (var methodInvocation in efExpressionNode.DescendantNodesAndSelf().OfType<InvocationExpressionSyntax>())
200+
{
201+
var symbolInfo = semanticModel.GetSymbolInfo(methodInvocation);
202+
203+
if (symbolInfo.Symbol is IMethodSymbol methodSymbol)
204+
{
205+
// Check for GroupBy Methods
206+
if (methodSymbol.Name == "GroupBy")
207+
{
208+
invalidEFExpressionNodes.Add(new(
209+
methodInvocation,
210+
EFAnalysisErrorMessages.GroupByMethodNotSupported));
211+
212+
result = false;
213+
break;
214+
}
215+
}
216+
}
217+
218+
return result;
219+
}
175220
}

Diff for: tests/MongoDB.Analyzer.Tests.Common.TestCases/EF/EFBasic.cs

+4-14
Original file line numberDiff line numberDiff line change
@@ -21,33 +21,23 @@ namespace MongoDB.Analyzer.Tests.Common.TestCases.EF;
2121

2222
public sealed class EFBasic
2323
{
24-
[MQLEF("db.coll.Aggregate([{ \"$group\" : { \"_id\" : \"$Address\", \"_elements\" : { \"$push\" : \"$$ROOT\" } } }])", DriverVersions.Linq3OrGreater)]
25-
[MQLEF("db.coll.Aggregate([{ \"$group\" : { \"_id\" : \"$LastName\", \"_elements\" : { \"$push\" : \"$$ROOT\" } } }])", DriverVersions.Linq3OrGreater)]
26-
public void GroupBy()
27-
{
28-
var dbContextOptions = new DbContextOptionsBuilder<MyDbContext>();
29-
var db = new MyDbContext(dbContextOptions.Options);
30-
var users_query = db.Users.GroupBy(u => u.Address);
31-
var customers_query = db.Customers.GroupBy(c => c.LastName);
32-
}
33-
3424
[MQLEF("db.coll.Aggregate([{ \"$match\" : { \"Name\" : \"Bob\", \"Age\" : { \"$gt\" : 16, \"$lte\" : 21 } } }])", DriverVersions.Linq3OrGreater)]
35-
[MQLEF("db.coll.Aggregate([{ \"$group\" : { \"_id\" : \"$LastName\", \"_elements\" : { \"$push\" : \"$$ROOT\" } } }])", DriverVersions.Linq3OrGreater)]
25+
[MQLEF("db.coll.Aggregate([{ \"$match\" : { \"LastName\" : \"LastName\" } }])", DriverVersions.Linq3OrGreater)]
3626
[MQLEF("db.coll.Aggregate([{ \"$sort\" : { \"Age\" : 1, \"Height\" : 1 } }])", DriverVersions.Linq3OrGreater)]
3727
public void Method()
3828
{
3929
var users_query = GetDbSet_Users().Where(u => u.Name == "Bob" && u.Age > 16 && u.Age <= 21);
40-
var customers_query = GetDbSet_Customers().GroupBy(c => c.LastName);
30+
var customers_query = GetDbSet_Customers().Where(c => c.LastName == "LastName");
4131
_ = GetDbContext().Users.OrderBy(u => u.Age).ThenBy(u => u.Height);
4232
}
4333

4434
[MQLEF("db.coll.Aggregate([{ \"$match\" : { \"Name\" : \"Bob\", \"Age\" : { \"$gt\" : 16, \"$lte\" : 21 } } }])", DriverVersions.Linq3OrGreater)]
45-
[MQLEF("db.coll.Aggregate([{ \"$group\" : { \"_id\" : \"$LastName\", \"_elements\" : { \"$push\" : \"$$ROOT\" } } }])", DriverVersions.Linq3OrGreater)]
35+
[MQLEF("db.coll.Aggregate([{ \"$match\" : { \"LastName\" : \"LastName\" } }])", DriverVersions.Linq3OrGreater)]
4636
[MQLEF("db.coll.Aggregate([{ \"$sort\" : { \"Age\" : 1, \"Height\" : 1 } }])", DriverVersions.Linq3OrGreater)]
4737
public void Object_invocation()
4838
{
4939
var users_query = new MyDbContext(new DbContextOptionsBuilder<MyDbContext>().Options).Users.Where(u => u.Name == "Bob" && u.Age > 16 && u.Age <= 21);
50-
var customers_query = new MyDbContext(new DbContextOptionsBuilder<MyDbContext>().Options).Customers.GroupBy(c => c.LastName);
40+
var customers_query = new MyDbContext(new DbContextOptionsBuilder<MyDbContext>().Options).Customers.Where(c => c.LastName == "LastName");
5141
_ = new MyDbContext(GetDbContextOptions()).Users.OrderBy(u => u.Age).ThenBy(u => u.Height);
5242
}
5343

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2021-present MongoDB Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License")
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System.Linq;
16+
using Microsoft.EntityFrameworkCore;
17+
using MongoDB.Analyzer.Tests.Common.DataModel;
18+
using MongoDB.Driver;
19+
20+
namespace MongoDB.Analyzer.Tests.Common.TestCases.EF;
21+
22+
public sealed class NotSupportedEFExpressions : TestCasesBase
23+
{
24+
[NotSupportedEF("Byte array type is not supported by this version of the EF provider.", DriverVersions.Linq3OrGreater)]
25+
[NotSupportedEF("Byte array type is not supported by this version of the EF provider.", DriverVersions.Linq3OrGreater)]
26+
[NotSupportedEF("Byte array type is not supported by this version of the EF provider.", DriverVersions.Linq3OrGreater)]
27+
[NotSupportedEF("Byte array type is not supported by this version of the EF provider.", DriverVersions.Linq3OrGreater)]
28+
public void ByteArrayProperties()
29+
{
30+
var dbContextOptions = new DbContextOptionsBuilder<DbContextUnsupportedEFExpressions>();
31+
var db = new DbContextUnsupportedEFExpressions(dbContextOptions.Options);
32+
_ = db.SimpleTypesArraysHolder.Where(s => s.ByteArray.Length == 1);
33+
_ = db.SimpleTypesArraysHolder.Where(s => s.IntArray.Length == 1).Where(s => s.IntArray.Length == 1).Where(s => s.ByteArray.Length == 1);
34+
_ = db.SimpleTypesArraysHolder.Where(s => s.IntArray.Length == 1).Where(s => s.ByteArray.Length == 1).Where(s => s.IntArray.Length == 1);
35+
_ = db.SimpleTypesArraysHolder.Where(s => s.ByteArray.Length == 1).Where(s => s.IntArray.Length == 1).Where(s => s.IntArray.Length == 1);
36+
}
37+
38+
[NotSupportedEF("GroupBy is not supported by this version of the EF provider.", DriverVersions.Linq3OrGreater)]
39+
[NotSupportedEF("GroupBy is not supported by this version of the EF provider.", DriverVersions.Linq3OrGreater)]
40+
[NotSupportedEF("GroupBy is not supported by this version of the EF provider.", DriverVersions.Linq3OrGreater)]
41+
[NotSupportedEF("GroupBy is not supported by this version of the EF provider.", DriverVersions.Linq3OrGreater)]
42+
public void GroupBy()
43+
{
44+
var dbContextOptions = new DbContextOptionsBuilder<DbContextUnsupportedEFExpressions>();
45+
var db = new DbContextUnsupportedEFExpressions(dbContextOptions.Options);
46+
var users_query = db.Users.GroupBy(u => u.Address);
47+
var customers_query = db.Customers.GroupBy(c => c.LastName);
48+
_ = db.Users.Where(u => u.Age == 21).GroupBy(u => u.Address);
49+
_ = db.Users.OrderBy(u => u.Age).ThenBy(u => u.Height).GroupBy(u => u.Address);
50+
}
51+
}
52+
53+
public class DbContextUnsupportedEFExpressions : DbContext
54+
{
55+
public DbSet<SimpleTypesArraysHolder> SimpleTypesArraysHolder { get; set; }
56+
public DbSet<User> Users { get; set; }
57+
public DbSet<Customer> Customers { get; set; }
58+
59+
public DbContextUnsupportedEFExpressions(DbContextOptions options) : base(options)
60+
{
61+
}
62+
63+
protected override void OnModelCreating(ModelBuilder modelBuilder)
64+
{
65+
base.OnModelCreating(modelBuilder);
66+
}
67+
}

Diff for: tests/MongoDB.Analyzer.Tests.Common/DiagnosticTestCaseAttribute.cs

+8
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ public MQLEFAttribute(
113113
}
114114
}
115115

116+
public sealed class NotSupportedEFAttribute : DiagnosticRuleTestCaseAttribute
117+
{
118+
public NotSupportedEFAttribute(string message, string version = null, LinqVersion linqProvider = LinqVersion.V3, DriverTargetFramework targetFramework = DriverTargetFramework.All, params int[] codeLines) :
119+
base(DiagnosticRulesConstants.NotSupportedEFExpression, message, version, linqProvider, targetFramework, codeLines: codeLines)
120+
{
121+
}
122+
}
123+
116124
public class InvalidLinqAttribute : DiagnosticRuleTestCaseAttribute
117125
{
118126
public InvalidLinqAttribute(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2021-present MongoDB Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License")
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Microsoft.VisualStudio.TestTools.UnitTesting;
16+
using MongoDB.Analyzer.Tests.Common.TestCases.EF;
17+
using MongoDB.Analyzer.Tests.Infrastructure;
18+
using System.Threading.Tasks;
19+
20+
namespace MongoDB.Analyzer.Tests.EF;
21+
22+
[TestClass]
23+
public sealed class EFNotSupportedExpressionsTests : DiagnosticsTestCasesRunner
24+
{
25+
[DataTestMethod]
26+
[CodeBasedTestCasesSource(typeof(NotSupportedEFExpressions))]
27+
public Task NotSupportedExpressions(DiagnosticTestCase testCase) => VerifyTestCase(testCase);
28+
}

0 commit comments

Comments
 (0)