Как проверить, закрыт ли IDataReader, используя API компилятора. net - PullRequest
3 голосов
/ 17 июня 2020

Я пытаюсь написать анализатор кода, который будет проверять, есть ли какие-нибудь IDataReaders, которые не закрыты.

Я рассмотрел этот вопрос , но он не объясняет, как это может готово, я также попытался прочитать документацию по ссылке github Используемый здесь язык Engli sh слишком сложен, и я не понял, как я смогу найти все экземпляры типа IDataReader и убедитесь, что на нем вызывается метод close () до того, как какая-либо переменная указанного типа выйдет из области видимости.

Я попытался создать проект типа Analyzer с исправлением кода в Visual Studio, я попытался зарегистрируйте контекст операции в методе Initialize моего класса (который расширен от типа DiagnosticAnalyzer следующим образом:

 [DiagnosticAnalyzer(LanguageNames.CSharp)]
public class DataReaderAnalyzerAnalyzer : DiagnosticAnalyzer
{
    public const string DiagnosticId = "DataReaderAnalyzer";

    private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources));
    private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
    private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources));
    private const string Category = "DBConnectionCheck";

    private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description);

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }

    public override void Initialize(AnalysisContext context)
    {

        context.RegisterOperationAction((operationContext) => 
        {
            ((Microsoft.CodeAnalysis.CSharp.Syntax.AssignmentExpressionSyntax)((Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionStatementSyntax)operationContext.Operation.Syntax).Expression).Left
        }
           , OperationKind.ExpressionStatement);
    }
}

Я хочу найти все ссылки на вхождение переменной, содержащей тип IDataReader, убедитесь, что в этой переменной вызывается метод close, прежде чем она будет потеряна за пределами области видимости.

Пример моего кода, который я хотел бы проанализировать, выглядит следующим образом.

 class Program
{
    static void Main(string[] args)
    {
        IDataReader reader = null;
        try
        {

            Database db = DatabaseFactory.CreateDatabase("ApplicationConnection");



            reader = GetDataReader(db);
            while (reader.Read())
            {
                   //Do somethig with the data here
            }
            reader.Close();
        }
        catch (Exception)
        {

            throw;
        }
        finally
        {
            if (reader != null && !reader.IsClosed)
            {
                reader.Close();
            }
        }
    }
public static IDataReader GetDataReader(Database db)
    {
        DbCommand dbcmd = db.GetSqlStringCommand("some select statement to get data from oracle data base");
        var reader = db.ExecuteReader(dbcmd);
        return reader;
    }
}

Ответы [ 2 ]

4 голосов
/ 17 июня 2020

В конечном итоге, показанный код не очень хорош , и, IMO, было бы неправильным решением написать анализатор для его принудительного применения.

Есть очень простой способ сделать это стиль работы, и это в основном включает в себя забывание о Close и использование того факта, что это IDisposable - это API, предназначенный для такого рода сценариев. Тогда это становится намного, намного проще - настолько проще, что а: для этого не нужен специальный анализатор, и б: существующие анализаторы, которые работают против IDisposable, вероятно, сделают эту работу за вас.

using var reader = GetDataReader(db);
while (reader.Read())
{
    //Do somethig with the data here
}

с нет try / catch / finally и т. Д .; компилятор добавит все необходимое для правильного выполнения этой операции, просто через using. Обратите внимание, что для старых компиляторов это должно быть:

using (var reader = GetDataReader(db))
{
    while (reader.Read())
    {
        //Do somethig with the data here
    }
}

В качестве побочного примечания: я настоятельно рекомендую не бороться с ADO. NET API - это не полезный способ провести время; такие инструменты, как Dapper, делают самые обычные для вас вещи , поэтому вам не нужно писать этот код - и он знает все угловые случаи, которых следует избегать.

Типичным использованием Dapper может быть:

string region = ...
var users = connection.Query<User>(
    "some * from Users where Region = @region",
    new { region } // parameters
).AsList();

с библиотекой, имеющей дело со всеми ADO. NET внутренними деталями.

0 голосов
/ 23 июня 2020

Приведенный ниже код - это не боевой подход, которому вы можете следовать.

analysisContext.RegisterCompilationStartAction(compilationContext =>
{
    var variables = new HashSet<string>();
    var tree = compilationContext.Compilation.SyntaxTrees.First();

    //iterate over all childnodes starting from root
    foreach (var node in tree.GetRoot().ChildNodes())
    {
        var flat = Flatten(node).ToList();
        //find all variable declarations
        var varDecls = flat.OfType<VariableDeclarationSyntax>();
        foreach (var decl in varDecls)
        {
            if (!(decl.Type is IdentifierNameSyntax id)) continue;
            if (!id.Identifier.Text.Equals("IDataReader")) continue;
            //if you are declaring an IDataReader, go store the var name in set
            foreach (var reader in decl.Variables)
            {
                variables.Add(reader.Identifier.Text);
            }
        }
        //find all method calls i.e. reader.Read() etc
        var invokes = flat.OfType<InvocationExpressionSyntax>();
        foreach (var invoke in invokes)
        {
            var memberAccess = invoke.Expression as MemberAccessExpressionSyntax;
            var ident = memberAccess.Expression as IdentifierNameSyntax;
            if(!variables.Contains(ident.Identifier.Text)) continue;
            var name = memberAccess.Name as IdentifierNameSyntax;
            //if we find any Close() method on reader, remove from var set
            if (name.Identifier.Text.Equals("Close"))
            {
                variables.Remove(ident.Identifier.Text);
            }
        }
    }
    // if we have any variables left in set it means Close() was never called
    if (variables.Count != 0)
    {
        //this is where you can report
        //var diagnostic = Diagnostic.Create(Rule, location, value);

        //context.ReportDiagnostic(diagnostic);
    }
 
});

public static IEnumerable<SyntaxNode> Flatten(SyntaxNode node)
{
    yield return node;
    var childNodes = node.ChildNodes();
    foreach (var child in childNodes)
        foreach (var descendant in Flatten(child))
            yield return descendant;
}
...