public GetClassInfo ( ITypeSymbol typeArg ) : |
||
typeArg | ITypeSymbol | |
return |
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); } } } } }
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); }
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); }
/// <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); } }
/// <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 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); } } } } } } } } }
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; }
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 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 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 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); } } }
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; }