internal static void ValidateMethodCallInLinqExpression(InvocationExpressionSyntax node, EFCodeFirstClassInfo rootQueryableType, SyntaxNodeAnalysisContext context, EFUsageContext efContext, Dictionary <string, ContextualLinqParameter> parameterNodes, bool treatAsWarning) { string methodName = null; var memberExpr = node.Expression as MemberAccessExpressionSyntax; var identExpr = node.Expression as IdentifierNameSyntax; if (memberExpr != null) { methodName = memberExpr?.Name?.Identifier.ValueText; //This is a LINQ operator (Where, Select, etc) if (CanonicalMethodNames.IsLinqOperator(methodName)) { var expr = memberExpr.Expression as MemberAccessExpressionSyntax; if (expr != null) //a.b.<linq operator>() { //What is b? var member = expr.Name as IdentifierNameSyntax; if (member != null) { string memberName = member.Identifier.ValueText; var applicableClasses = efContext.GetClassForProperty(memberName) .Where(c => c.HasProperty(memberName)); if (applicableClasses.Count() > 1) { //See if semantic model can help us disambiguate var si = context.SemanticModel.GetSymbolInfo(expr.Expression); var type = si.Symbol?.TryGetType(); if (type != null) { var cls = efContext.GetClassInfo(type); //There is only one class with this property and it is confirmed to be a collection //navigation property if (cls != null && cls.IsCollectionNavigationProperty(memberName)) { ValidateNavigationPropertyAccess(node, context, efContext, treatAsWarning, memberName, cls); } } else { //This potential navigation property resolves to multiple classes, see if we can resolve to a //single one via contextual variables var inst = expr.Expression as IdentifierNameSyntax; if (inst != null) { string name = inst.Identifier.ValueText; ContextualLinqParameter cparam; if (parameterNodes.TryGetValue(name, out cparam) && cparam.ParameterType == ContextualLinqParameterType.Queryable && applicableClasses.Any(c => c.ClassType == cparam.QueryableType.ClassType)) { //TODO: Code fix candidate // //In such a case, inject an .AsQueryable() before the LINQ operator call //and add using System.Linq if required var diagnostic = Diagnostic.Create(treatAsWarning ? DiagnosticCodes.EFLINQ009 : DiagnosticCodes.EFLINQ008, member.GetLocation(), memberName, cparam.QueryableType.Name); context.ReportDiagnostic(diagnostic); } else { //TODO: Code fix candidate // //In such a case, inject an .AsQueryable() before the LINQ operator call //and add using System.Linq if required var diagnostic = Diagnostic.Create(DiagnosticCodes.EFLINQ010, member.GetLocation(), memberName); context.ReportDiagnostic(diagnostic); } } else { //TODO: Code fix candidate // //In such a case, inject an .AsQueryable() before the LINQ operator call //and add using System.Linq if required var diagnostic = Diagnostic.Create(DiagnosticCodes.EFLINQ010, member.GetLocation(), memberName); context.ReportDiagnostic(diagnostic); } } } else { var cls = applicableClasses.FirstOrDefault(); //There is only one class with this property and it is confirmed to be a collection //navigation property if (cls != null && cls.IsCollectionNavigationProperty(memberName)) { ValidateNavigationPropertyAccess(node, context, efContext, treatAsWarning, memberName, cls); } } } //TODO: If not, check that the preceding member is IQueryable<T> and that T is a known //entity type } } else { //TODO: AsQueryable() shouldn't be a blanket exception. //We obviously should check what precedes it if (methodName != EFSpecialIdentifiers.AsQueryable) { bool bValid = IsSupportedLinqToEntitiesMethod(node, memberExpr, rootQueryableType, efContext, context); if (!bValid) { var diagnostic = Diagnostic.Create(treatAsWarning ? DiagnosticCodes.EFLINQ007 : DiagnosticCodes.EFLINQ004, node.GetLocation(), methodName); context.ReportDiagnostic(diagnostic); } } } } else if (identExpr != null) //A non-instance (static) method call, most certainly illegal { if (!CanonicalMethodNames.IsKnownMethod(identExpr)) { methodName = identExpr.Identifier.ValueText; var diagnostic = Diagnostic.Create(treatAsWarning ? DiagnosticCodes.EFLINQ006 : DiagnosticCodes.EFLINQ003, node.GetLocation(), methodName); context.ReportDiagnostic(diagnostic); } } }
private static void AnalyzeLambdaInLinqExpression(SyntaxNodeAnalysisContext context, EFUsageContext efContext, LambdaExpressionSyntax lambda) { var lambdaAssign = lambda.Parent as EqualsValueClauseSyntax; var arg = lambda.Parent as ArgumentSyntax; if (arg != null) //The lambda in question is being passed as an argument { var parent = arg.Parent; while (parent != null && !(parent is ArgumentListSyntax)) { parent = parent.Parent; } if (parent != null) //Which should be part of an ArgumentList { var argList = parent; var invoc = argList?.Parent as InvocationExpressionSyntax; if (invoc != null) //Which should be part of an invocation { var memberExpr = invoc.Expression as MemberAccessExpressionSyntax; if (memberExpr != null) { if (CanonicalMethodNames.IsLinqOperator(memberExpr?.Name?.Identifier.ValueText)) { var si = context.SemanticModel.GetSymbolInfo(memberExpr.Expression); var lts = si.Symbol as ILocalSymbol; var pts = si.Symbol as IPropertySymbol; //Is this method called on a property? if (pts != null) { var nts = pts.Type as INamedTypeSymbol; if (nts != null) { //Like a DbSet<T>? if (nts.IsDbSet()) { //That is part of a class derived from DbContext? if (pts?.ContainingType?.BaseType?.Name == EFSpecialIdentifiers.DbContext) { var typeArg = nts.TypeArguments[0]; //Let's give our method some assistance, by checking what T actually is var clsInfo = efContext.GetClassInfo(typeArg); if (clsInfo != null) { //Okay now let's see if this lambda is valid in the EF context LinqExpressionValidator.ValidateLinqToEntitiesExpression(lambda, clsInfo, context, efContext); } } } } } else if (lts != null) //The linq method was called on a local variable { var nts = lts.Type as INamedTypeSymbol; if (nts != null && nts.TypeArguments.Length == 1) { //This is some generic type with one type argument var typeArg = nts.TypeArguments[0]; var clsInfo = efContext.GetClassInfo(typeArg); if (clsInfo != null) { if (nts.IsDbSet()) { //TODO: Should still actually check that it is ultimately assigned //from a DbSet<T> property of a DbContext derived class LinqExpressionValidator.ValidateLinqToEntitiesExpression(lambda, clsInfo, context, efContext); } else if (nts.IsQueryable()) { bool treatAsWarning = !LinqExpressionValidator.SymbolCanBeTracedBackToDbContext(lts, context, efContext, clsInfo); LinqExpressionValidator.ValidateLinqToEntitiesExpression(lambda, clsInfo, context, efContext, treatAsWarning); } } } } } } } } } else if (lambdaAssign != null) //The lambda in question is being assigned { var localLambdaDecl = lambdaAssign?.Parent?.Parent?.Parent as LocalDeclarationStatementSyntax; if (localLambdaDecl != null) { var declType = localLambdaDecl?.Declaration?.Type as GenericNameSyntax; if (declType != null) { //Is Expression<T> if (declType.Identifier.ValueText == EFSpecialIdentifiers.Expression && declType.TypeArgumentList.Arguments.Count == 1) { //The T is Func<TInput, TOutput> var exprTypeArg = declType.TypeArgumentList.Arguments[0] as GenericNameSyntax; if (exprTypeArg != null && exprTypeArg.Identifier.ValueText == EFSpecialIdentifiers.Func && exprTypeArg.TypeArgumentList.Arguments.Count == 2) { var inputType = exprTypeArg.TypeArgumentList.Arguments[0] as IdentifierNameSyntax; var outputType = exprTypeArg.TypeArgumentList.Arguments[1] as PredefinedTypeSyntax; //The TOutput in Func<TInput, TOutput> is bool if (inputType != null && outputType != null && outputType.Keyword.ValueText == EFSpecialIdentifiers.BooleanShort) { var si = context.SemanticModel.GetSymbolInfo(inputType); var ts = efContext.EntityTypes.FirstOrDefault(t => t == si.Symbol); if (ts != null) { var clsInfo = efContext.GetClassInfo(ts); if (clsInfo != null) { LinqExpressionValidator.ValidateLinqToEntitiesExpression(lambda, clsInfo, context, efContext); } } } } } } } } }
private static bool IsSupportedLinqToEntitiesMethod(InvocationExpressionSyntax node, MemberAccessExpressionSyntax memberExpr, EFCodeFirstClassInfo rootQueryableType, EFUsageContext efContext, SyntaxNodeAnalysisContext context) => CanonicalMethodNames.IsKnownMethod(node, memberExpr, rootQueryableType, efContext, context);