public static Dictionary<string, ContextualLinqParameter> BuildContext(IEnumerable<ParameterSyntax> parameters, SyntaxNodeAnalysisContext context, EFUsageContext efContext) { var cparams = new Dictionary<string, ContextualLinqParameter>(); foreach (var p in parameters) { string name = p.Identifier.ValueText; var si = context.SemanticModel.GetSymbolInfo(p); var type = si.Symbol?.TryGetType(); if (type != null) cparams[name] = new ContextualLinqParameter(name, type); else cparams[name] = new ContextualLinqParameter(name); } return cparams; }
/// <summary> /// Validates the series of syntax nodes for valid usages of LINQ to Entities constructs /// </summary> /// <param name="descendants"></param> /// <param name="rootQueryableType"></param> /// <param name="context"></param> /// <param name="efContext"></param> /// <param name="treatAsWarning"></param> public static void ValidateLinqToEntitiesUsageInSyntaxNodes(IEnumerable<SyntaxNode> descendants, EFCodeFirstClassInfo rootQueryableType, SyntaxNodeAnalysisContext context, EFUsageContext efContext, Dictionary<string, ContextualLinqParameter> parameterNodes, bool treatAsWarning) { var accessNodes = descendants.OfType<MemberAccessExpressionSyntax>(); var methodCallNodes = descendants.OfType<InvocationExpressionSyntax>(); var stringNodes = descendants.OfType<InterpolatedStringExpressionSyntax>(); var objCreationNodes = descendants.OfType<ObjectCreationExpressionSyntax>(); //Easy one, all interpolated strings are invalid, it's just a case of whether to raise an //error or warning // //TODO: Code fix candidate. Offer to replace the interpolated string with a raw concatenated //equivalent foreach (var node in stringNodes) { var diagnostic = Diagnostic.Create(treatAsWarning ? DiagnosticCodes.EFLINQ012 : DiagnosticCodes.EFLINQ011, node.GetLocation()); context.ReportDiagnostic(diagnostic); } //Another easy one, any object creation expressions inside a known LINQ expression cannot involve an entity type foreach (var node in objCreationNodes) { var objType = node.Type; var si = context.SemanticModel.GetSymbolInfo(objType); var ts = si.Symbol?.TryGetType(); if (ts != null) { var cls = efContext.GetClassInfo(ts); if (cls != null) { var diagnostic = Diagnostic.Create(treatAsWarning ? DiagnosticCodes.EFLINQ016 : DiagnosticCodes.EFLINQ015, node.Type.GetLocation(), cls.Name); context.ReportDiagnostic(diagnostic); } } } //Check for property accesses on read-only properties, expression-bodied members foreach (var node in accessNodes) { ValidateMemberAccessInLinqExpression(node, rootQueryableType, context, parameterNodes, treatAsWarning); } foreach (var node in methodCallNodes) { ValidateMethodCallInLinqExpression(node, rootQueryableType, context, efContext, parameterNodes, treatAsWarning); } }
private static void AnalyzeLinqExpression(SyntaxNodeAnalysisContext context) { var efContext = new EFUsageContext(context); //Found nothing, don't bother continuing if (!efContext.Build()) { return; } //Check if the lambda is part of an IQueryable call chain. If so, check that it only contains valid //EF LINQ constructs (initializers, entity members, entity navigation properties) var query = context.Node as QueryExpressionSyntax; var lambda = context.Node as LambdaExpressionSyntax; if (query != null) { AnalyzeQueryExpression(context, efContext, query); } else if (lambda != null) { AnalyzeLambdaInLinqExpression(context, efContext, lambda); } }
static bool GetRootEntityTypeFromLinqQuery(ExpressionSyntax expr, SyntaxNodeAnalysisContext context, EFUsageContext efContext, out EFCodeFirstClassInfo clsInfo) { clsInfo = null; // from $var in ??? var memberExpr = expr as MemberAccessExpressionSyntax; var ident = expr as IdentifierNameSyntax; if (memberExpr != null) //??? = $ident.$prop { return LinqExpressionValidator.MemberAccessIsAccessingDbContext(memberExpr, context, efContext, out clsInfo); } else if (ident != null) { var si = context.SemanticModel.GetSymbolInfo(ident); var local = si.Symbol as ILocalSymbol; if (local != null) { var nts = local.Type as INamedTypeSymbol; if (nts != null && nts.TypeArguments.Length == 1) { var typeArg = nts.TypeArguments[0]; clsInfo = efContext.GetClassInfo(typeArg); if (clsInfo != null) { return LinqExpressionValidator.SymbolCanBeTracedBackToDbContext(local, context, efContext, clsInfo); } } } } return false; }
private static void AnalyzeLinqExpression(SyntaxNodeAnalysisContext context) { var efContext = new EFUsageContext(context); //Found nothing, don't bother continuing if (!efContext.Build()) return; //Check if the lambda is part of an IQueryable call chain. If so, check that it only contains valid //EF LINQ constructs (initializers, entity members, entity navigation properties) var query = context.Node as QueryExpressionSyntax; var lambda = context.Node as LambdaExpressionSyntax; if (query != null) { AnalyzeQueryExpression(context, efContext, query); } else if (lambda != null) { AnalyzeLambdaInLinqExpression(context, efContext, lambda); } }
private static bool IsSupportedLinqToEntitiesMethod(InvocationExpressionSyntax node, MemberAccessExpressionSyntax memberExpr, EFCodeFirstClassInfo rootQueryableType, EFUsageContext efContext, SyntaxNodeAnalysisContext context) => CanonicalMethodNames.IsKnownMethod(node, memberExpr, rootQueryableType, efContext, context);
private static void ValidateNavigationPropertyAccess(InvocationExpressionSyntax node, SyntaxNodeAnalysisContext context, EFUsageContext efContext, bool treatAsWarning, string memberName, EFCodeFirstClassInfo cls) { if (node.ArgumentList.Arguments.Count == 1 && node.ArgumentList.Arguments[0].Expression.Kind() != SyntaxKind.SimpleLambdaExpression) { if (node.ArgumentList.Arguments[0].Expression.Kind() == SyntaxKind.IdentifierName) { //Follow the identifier back to its assignment var si = context.SemanticModel.GetSymbolInfo(node.ArgumentList.Arguments[0].Expression); if (si.Symbol?.Kind == SymbolKind.Local) { var type = si.Symbol?.TryGetType() as INamedTypeSymbol; //The variable inside our LINQ sub-operator is a Func<T, bool> where T //is a known entity type if (type != null && type.MetadataName == $"{EFSpecialIdentifiers.Func}`2" && efContext.GetClassInfo(type.TypeArguments[0]) != null && type.TypeArguments[1].MetadataName == EFSpecialIdentifiers.Boolean) { //TODO: Code fix candidate // //In such a case, inject an .AsQueryable() before the LINQ operator call //and add using System.Linq if required and convert the variable from Func<T, bool> to Expression<Func<T, bool>> var diagnostic = Diagnostic.Create(treatAsWarning ? DiagnosticCodes.EFLINQ009 : DiagnosticCodes.EFLINQ008, node.ArgumentList.Arguments[0].Expression.GetLocation(), memberName, cls.Name); context.ReportDiagnostic(diagnostic); } } } } }
public static bool MemberAccessIsAccessingDbContext(MemberAccessExpressionSyntax memberExpr, SyntaxNodeAnalysisContext context, EFUsageContext efContext, out EFCodeFirstClassInfo clsInfo) { clsInfo = null; var compIdent = memberExpr.Expression as IdentifierNameSyntax; var prop = memberExpr.Name; if (compIdent != null && prop != null) { var si = context.SemanticModel.GetSymbolInfo(compIdent); var type = si.Symbol?.TryGetType(); if (type != null) { //$ident is a DbContext if (efContext.DbContexts.Contains(type)) { //We're expecting $prop to be a symbol si = context.SemanticModel.GetSymbolInfo(prop); var ps = si.Symbol as IPropertySymbol; if (ps.IsDbSetProperty()) { var nts = ps.Type as INamedTypeSymbol; if (nts != null) { var typeArg = nts.TypeArguments[0]; clsInfo = efContext.GetClassInfo(typeArg); return true; } } } } } return false; }
internal static bool SymbolCanBeTracedBackToDbContext(ISymbol sym, SyntaxNodeAnalysisContext context, EFUsageContext efContext, EFCodeFirstClassInfo clsInfo) { bool bTraced = false; var assignments = sym.DeclaringSyntaxReferences .Where(decl => decl.GetSyntax()?.Kind() == SyntaxKind.EqualsValueClause) .Cast <EqualsValueClauseSyntax>() .Concat(sym.DeclaringSyntaxReferences.SelectMany(decl => decl.GetSyntax()?.DescendantNodes().OfType <EqualsValueClauseSyntax>())); //Find applicable assignments where: //var myVar = $SOME_EXPR; var applicableAssignments = assignments.Select(asn => asn.Parent) .OfType <VariableDeclaratorSyntax>() .Where(decl => decl.Identifier.ValueText == sym.Name); if (applicableAssignments.Any()) { //Check what the RHS is foreach (var asn in applicableAssignments) { var eq = asn.DescendantNodes() .OfType <EqualsValueClauseSyntax>() .FirstOrDefault(); if (eq != null) { switch (eq.Value.Kind()) { //var myVar = $SOME_METHOD() case SyntaxKind.InvocationExpression: { var invoc = (InvocationExpressionSyntax)eq.Value; var method = invoc.Expression as IdentifierNameSyntax; if (method != null) { return(DoesMethodReturnDbSet(method, context, efContext, clsInfo)); } } break; case SyntaxKind.SimpleMemberAccessExpression: { var member = (MemberAccessExpressionSyntax)eq.Value; EFCodeFirstClassInfo cls; bool isDbContext = MemberAccessIsAccessingDbContext(member, context, efContext, out cls); return(isDbContext && (cls == clsInfo)); } break; } } } } return(bTraced); }
/// <summary> /// Validates the given lambda to see if it is a valid LINQ to Entities expression /// </summary> /// <param name="lambda">The lambda syntax node</param> /// <param name="rootQueryableType">The type of the IQueryable instance where a known LINQ operator is invoked on with this lambda</param> /// <param name="context">The analysis context</param> /// <param name="efContext">The EF-specific view of the semantic model</param> /// <param name="treatAsWarning">If true, instructs any diagnostic reports to be flagged as warnings instead of errors. This is normally true when the analyzer cannot fully determine that the LINQ expression is made against an actual DbSet</param> public static void ValidateLinqToEntitiesExpression(LambdaExpressionSyntax lambda, EFCodeFirstClassInfo rootQueryableType, SyntaxNodeAnalysisContext context, EFUsageContext efContext, bool treatAsWarning = false) { var descendants = lambda.DescendantNodes(); var parameterNodes = ContextualLinqParameter.BuildContext(descendants.OfType <ParameterSyntax>(), context, efContext); ValidateLinqToEntitiesUsageInSyntaxNodes(descendants, rootQueryableType, context, efContext, parameterNodes, treatAsWarning); }
public static bool MemberAccessIsAccessingDbContext(MemberAccessExpressionSyntax memberExpr, SyntaxNodeAnalysisContext context, EFUsageContext efContext, out EFCodeFirstClassInfo clsInfo) { clsInfo = null; var compIdent = memberExpr.Expression as IdentifierNameSyntax; var prop = memberExpr.Name; if (compIdent != null && prop != null) { var si = context.SemanticModel.GetSymbolInfo(compIdent); var type = si.Symbol?.TryGetType(); if (type != null) { //$ident is a DbContext if (efContext.DbContexts.Contains(type)) { //We're expecting $prop to be a symbol si = context.SemanticModel.GetSymbolInfo(prop); var ps = si.Symbol as IPropertySymbol; if (ps.IsDbSetProperty()) { var nts = ps.Type as INamedTypeSymbol; if (nts != null) { var typeArg = nts.TypeArguments[0]; clsInfo = efContext.GetClassInfo(typeArg); return(true); } } } } } return(false); }
internal static bool IsKnownMethod(InvocationExpressionSyntax node, MemberAccessExpressionSyntax memberExpr, EFCodeFirstClassInfo rootQueryableType, EFUsageContext efContext, SyntaxNodeAnalysisContext context) { var member = memberExpr.Expression as MemberAccessExpressionSyntax; var identifier = memberExpr.Expression as IdentifierNameSyntax; if (identifier != null) { //If it's a supported type, then any static method under it (predicated by identifier //not being null, which hints at a static call. ie. If we get here, this is a static method //call) is considered supported if (IsSupportedType(identifier.Identifier.ValueText)) return true; } string methodName = memberExpr?.Name?.Identifier.ValueText; if (_methods.ContainsKey(methodName)) { var mi = _methods[methodName]; if (mi.IsStub) return true; //TODO: Based on the given syntax nodes, validate it against the known signatures and/or //the allowable types return true; } //The method is an instance method on some member if (member != null) { var si = context.SemanticModel.GetSymbolInfo(member.Name); //Is this on a property member? var pts = si.Symbol as IPropertySymbol; if (pts != null) { //Of a type that we know is an EF class? var cls = efContext.GetClassInfo(pts.ContainingType); if (cls != null) { //Is the property of a type that is EF whole heartedly //supports? if (IsSupportedType(pts.Type.MetadataName)) return true; } } } //Last chance, does this method have a special [DbFunction] attribute that //tells EF that there will be a DB server-side equivalent function? var symInfo = context.SemanticModel.GetSymbolInfo(memberExpr.Name); var miSym = symInfo.Symbol as IMethodSymbol; if (miSym != null) { if (miSym.GetAttributes().Any(a => a.AttributeClass.MetadataName == "DbFunctionAttribute")) return true; } return false; }
public static Dictionary <string, ContextualLinqParameter> BuildContext(QueryExpressionSyntax query, SyntaxNodeAnalysisContext context, EFUsageContext efContext) { var cparams = new Dictionary <string, ContextualLinqParameter>(); //From x var fromExpr = query.FromClause.Identifier; //in <expr> var inExpr = query.FromClause.Expression; string name = fromExpr.ValueText; var memberExpr = inExpr as MemberAccessExpressionSyntax; if (memberExpr != null) { EFCodeFirstClassInfo cls; if (LinqExpressionValidator.MemberAccessIsAccessingDbContext(memberExpr, context, efContext, out cls)) { cparams[name] = new ContextualLinqParameter(name, cls); } } //Still not set, just set as a contextual parameter with no known type if (!cparams.ContainsKey(name)) { cparams[name] = new ContextualLinqParameter(name); } return(cparams); }
public static Dictionary <string, ContextualLinqParameter> BuildContext(IEnumerable <ParameterSyntax> parameters, SyntaxNodeAnalysisContext context, EFUsageContext efContext) { var cparams = new Dictionary <string, ContextualLinqParameter>(); foreach (var p in parameters) { string name = p.Identifier.ValueText; var si = context.SemanticModel.GetSymbolInfo(p); var type = si.Symbol?.TryGetType(); if (type != null) { cparams[name] = new ContextualLinqParameter(name, type); } else { cparams[name] = new ContextualLinqParameter(name); } } return(cparams); }
/// <summary> /// Validates the series of syntax nodes for valid usages of LINQ to Entities constructs /// </summary> /// <param name="descendants"></param> /// <param name="rootQueryableType"></param> /// <param name="context"></param> /// <param name="efContext"></param> /// <param name="treatAsWarning"></param> public static void ValidateLinqToEntitiesUsageInSyntaxNodes(IEnumerable <SyntaxNode> descendants, EFCodeFirstClassInfo rootQueryableType, SyntaxNodeAnalysisContext context, EFUsageContext efContext, Dictionary <string, ContextualLinqParameter> parameterNodes, bool treatAsWarning) { var accessNodes = descendants.OfType <MemberAccessExpressionSyntax>(); var methodCallNodes = descendants.OfType <InvocationExpressionSyntax>(); var stringNodes = descendants.OfType <InterpolatedStringExpressionSyntax>(); var objCreationNodes = descendants.OfType <ObjectCreationExpressionSyntax>(); //Easy one, all interpolated strings are invalid, it's just a case of whether to raise an //error or warning // //TODO: Code fix candidate. Offer to replace the interpolated string with a raw concatenated //equivalent foreach (var node in stringNodes) { var diagnostic = Diagnostic.Create(treatAsWarning ? DiagnosticCodes.EFLINQ012 : DiagnosticCodes.EFLINQ011, node.GetLocation()); context.ReportDiagnostic(diagnostic); } //Another easy one, any object creation expressions inside a known LINQ expression cannot involve an entity type foreach (var node in objCreationNodes) { var objType = node.Type; var si = context.SemanticModel.GetSymbolInfo(objType); var ts = si.Symbol?.TryGetType(); if (ts != null) { var cls = efContext.GetClassInfo(ts); if (cls != null) { var diagnostic = Diagnostic.Create(treatAsWarning ? DiagnosticCodes.EFLINQ016 : DiagnosticCodes.EFLINQ015, node.Type.GetLocation(), cls.Name); context.ReportDiagnostic(diagnostic); } } } //Check for property accesses on read-only properties, expression-bodied members foreach (var node in accessNodes) { ValidateMemberAccessInLinqExpression(node, rootQueryableType, context, parameterNodes, treatAsWarning); } foreach (var node in methodCallNodes) { ValidateMethodCallInLinqExpression(node, rootQueryableType, context, efContext, parameterNodes, treatAsWarning); } }
internal static bool IsKnownMethod(InvocationExpressionSyntax node, MemberAccessExpressionSyntax memberExpr, EFCodeFirstClassInfo rootQueryableType, EFUsageContext efContext, SyntaxNodeAnalysisContext context) { var member = memberExpr.Expression as MemberAccessExpressionSyntax; var identifier = memberExpr.Expression as IdentifierNameSyntax; if (identifier != null) { //If it's a supported type, then any static method under it (predicated by identifier //not being null, which hints at a static call. ie. If we get here, this is a static method //call) is considered supported if (IsSupportedType(identifier.Identifier.ValueText)) { return(true); } } string methodName = memberExpr?.Name?.Identifier.ValueText; if (_methods.ContainsKey(methodName)) { var mi = _methods[methodName]; if (mi.IsStub) { return(true); } //TODO: Based on the given syntax nodes, validate it against the known signatures and/or //the allowable types return(true); } //The method is an instance method on some member if (member != null) { var si = context.SemanticModel.GetSymbolInfo(member.Name); //Is this on a property member? var pts = si.Symbol as IPropertySymbol; if (pts != null) { //Of a type that we know is an EF class? var cls = efContext.GetClassInfo(pts.ContainingType); if (cls != null) { //Is the property of a type that is EF whole heartedly //supports? if (IsSupportedType(pts.Type.MetadataName)) { return(true); } } } } //Last chance, does this method have a special [DbFunction] attribute that //tells EF that there will be a DB server-side equivalent function? var symInfo = context.SemanticModel.GetSymbolInfo(memberExpr.Name); var miSym = symInfo.Symbol as IMethodSymbol; if (miSym != null) { if (miSym.GetAttributes().Any(a => a.AttributeClass.MetadataName == "DbFunctionAttribute")) { return(true); } } return(false); }
private static bool DoesMethodReturnDbSet(IdentifierNameSyntax methodIdent, SyntaxNodeAnalysisContext context, EFUsageContext efContext, EFCodeFirstClassInfo clsInfo) { var method = methodIdent.GetDeclaringMethod(context); //TODO: Handle cases of lambda/Func<T, ...> invocation if (method != null) { var returnStatements = method.DescendantNodes().OfType <ReturnStatementSyntax>(); //It has to be all so that we can be conclusive that all points of return a DbSet<T> bool returnsDbSet = returnStatements.All(ret => ReturnStatementTracesBackToDbSet(ret, context, clsInfo)); return(returnsDbSet); } return(false); }
/// <summary> /// Validates the given lambda to see if it is a valid LINQ to Entities expression /// </summary> /// <param name="lambda">The lambda syntax node</param> /// <param name="rootQueryableType">The type of the IQueryable instance where a known LINQ operator is invoked on with this lambda</param> /// <param name="context">The analysis context</param> /// <param name="efContext">The EF-specific view of the semantic model</param> /// <param name="treatAsWarning">If true, instructs any diagnostic reports to be flagged as warnings instead of errors. This is normally true when the analyzer cannot fully determine that the LINQ expression is made against an actual DbSet</param> public static void ValidateLinqToEntitiesExpression(LambdaExpressionSyntax lambda, EFCodeFirstClassInfo rootQueryableType, SyntaxNodeAnalysisContext context, EFUsageContext efContext, bool treatAsWarning = false) { var descendants = lambda.DescendantNodes(); var parameterNodes = ContextualLinqParameter.BuildContext(descendants.OfType<ParameterSyntax>(), context, efContext); ValidateLinqToEntitiesUsageInSyntaxNodes(descendants, rootQueryableType, context, efContext, parameterNodes, treatAsWarning); }
internal static bool SymbolCanBeTracedBackToDbContext(ISymbol sym, SyntaxNodeAnalysisContext context, EFUsageContext efContext, EFCodeFirstClassInfo clsInfo) { bool bTraced = false; var assignments = sym.DeclaringSyntaxReferences .Where(decl => decl.GetSyntax()?.Kind() == SyntaxKind.EqualsValueClause) .Cast<EqualsValueClauseSyntax>() .Concat(sym.DeclaringSyntaxReferences.SelectMany(decl => decl.GetSyntax()?.DescendantNodes().OfType<EqualsValueClauseSyntax>())); //Find applicable assignments where: //var myVar = $SOME_EXPR; var applicableAssignments = assignments.Select(asn => asn.Parent) .OfType<VariableDeclaratorSyntax>() .Where(decl => decl.Identifier.ValueText == sym.Name); if (applicableAssignments.Any()) { //Check what the RHS is foreach (var asn in applicableAssignments) { var eq = asn.DescendantNodes() .OfType<EqualsValueClauseSyntax>() .FirstOrDefault(); if (eq != null) { switch (eq.Value.Kind()) { //var myVar = $SOME_METHOD() case SyntaxKind.InvocationExpression: { var invoc = (InvocationExpressionSyntax)eq.Value; var method = invoc.Expression as IdentifierNameSyntax; if (method != null) { return DoesMethodReturnDbSet(method, context, efContext, clsInfo); } } break; case SyntaxKind.SimpleMemberAccessExpression: { var member = (MemberAccessExpressionSyntax)eq.Value; EFCodeFirstClassInfo cls; bool isDbContext = MemberAccessIsAccessingDbContext(member, context, efContext, out cls); return isDbContext && (cls == clsInfo); } break; } } } } return bTraced; }
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 bool DoesMethodReturnDbSet(IdentifierNameSyntax methodIdent, SyntaxNodeAnalysisContext context, EFUsageContext efContext, EFCodeFirstClassInfo clsInfo) { var method = methodIdent.GetDeclaringMethod(context); //TODO: Handle cases of lambda/Func<T, ...> invocation if (method != null) { var returnStatements = method.DescendantNodes().OfType<ReturnStatementSyntax>(); //It has to be all so that we can be conclusive that all points of return a DbSet<T> bool returnsDbSet = returnStatements.All(ret => ReturnStatementTracesBackToDbSet(ret, context, clsInfo)); return returnsDbSet; } return false; }
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); } } } } } } } } }
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); } } }
static bool GetRootEntityTypeFromLinqQuery(ExpressionSyntax expr, SyntaxNodeAnalysisContext context, EFUsageContext efContext, out EFCodeFirstClassInfo clsInfo) { clsInfo = null; // from $var in ??? var memberExpr = expr as MemberAccessExpressionSyntax; var ident = expr as IdentifierNameSyntax; if (memberExpr != null) //??? = $ident.$prop { return(LinqExpressionValidator.MemberAccessIsAccessingDbContext(memberExpr, context, efContext, out clsInfo)); } else if (ident != null) { var si = context.SemanticModel.GetSymbolInfo(ident); var local = si.Symbol as ILocalSymbol; if (local != null) { var nts = local.Type as INamedTypeSymbol; if (nts != null && nts.TypeArguments.Length == 1) { var typeArg = nts.TypeArguments[0]; clsInfo = efContext.GetClassInfo(typeArg); if (clsInfo != null) { return(LinqExpressionValidator.SymbolCanBeTracedBackToDbContext(local, context, efContext, clsInfo)); } } } } return(false); }
private static void AnalyzeQueryExpression(SyntaxNodeAnalysisContext context, EFUsageContext efContext, QueryExpressionSyntax query) { //I can't believe how much easier this is compared to Extension Method syntax! Then again //query syntax does mean its own dedicated set of C# keywords, which would means its own //dedicated set of syntax node types //First item on checklist, find out our root queryable EFCodeFirstClassInfo cls; bool isConnectedToDbContext = GetRootEntityTypeFromLinqQuery(query.FromClause.Expression, context, efContext, out cls); if (cls != null) { bool treatAsWarning = !isConnectedToDbContext; var paramNodes = ContextualLinqParameter.BuildContext(query, context, efContext); var descendants = query.Body.DescendantNodes(); LinqExpressionValidator.ValidateLinqToEntitiesUsageInSyntaxNodes(descendants, cls, context, efContext, paramNodes, treatAsWarning); } }
public static Dictionary<string, ContextualLinqParameter> BuildContext(QueryExpressionSyntax query, SyntaxNodeAnalysisContext context, EFUsageContext efContext) { var cparams = new Dictionary<string, ContextualLinqParameter>(); //From x var fromExpr = query.FromClause.Identifier; //in <expr> var inExpr = query.FromClause.Expression; string name = fromExpr.ValueText; var memberExpr = inExpr as MemberAccessExpressionSyntax; if (memberExpr != null) { EFCodeFirstClassInfo cls; if (LinqExpressionValidator.MemberAccessIsAccessingDbContext(memberExpr, context, efContext, out cls)) { cparams[name] = new ContextualLinqParameter(name, cls); } } //Still not set, just set as a contextual parameter with no known type if (!cparams.ContainsKey(name)) cparams[name] = new ContextualLinqParameter(name); return cparams; }