如何使用.NET编译器API检查IDataReader是否已关闭。

3
我正在尝试编写一个代码分析器,用于检查是否有任何未关闭的IDataReaders。
我已经看过这个问题,但它没有解释如何实现。我也试图阅读GitHub链接中的文档,但英语太复杂了,我不知道如何找到所有类型为IDataReader的实例,并验证在该类型的任何变量超出作用域之前是否调用了close()方法。
我尝试在Visual Studio中创建具有代码修复的Analyzer项目,我尝试在我的类的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

总的来说,所展示的代码并不理想,在我看来编写分析器强制执行它是错误的解决方法。

有一种非常简单的方法可以进行此类操作,它主要涉及忘记使用Close ,而是使用它是 IDisposable - 这是这种情况的预期API。然后就变得简单得多了-如此简单,以至于a:您不需要特殊的分析器来处理它,b:现有的针对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的所有细节被其内部处理。


据我的经验,使用using语句并不能真正关闭数据读取器(我使用的是与Oracle数据库通信的Microsoft Enterprise Library)。我的Web应用程序源代码有几千个方法,从DAL层返回IDataReader,重构一切对我来说不是一个选项,即使using语句突然开始工作也是如此。 - Vishweshwar Kapse
3
@VishweshwarKapse,恕我直言,我不认为你是正确的。这里是DbDataReader,实际上所有读取器都是从它派生而来 - 请注意Dispose()调用了Close() - 实际上这就是 Dispose() 做的全部。而且这里是OracleDataReader - 它并不改变Dispose() - Marc Gravell
1
@VishweshwarKapse 如果您正在使用EntLib,而EntLib正在做一些有趣的事情:坦率地说 - 错误在于使用EntLib;如果我没记错,这个代码库已经被放弃了约十年。 - Marc Gravell
@VishweshwarKapse 我已经检查了EntLib源代码;有一个包装器(DataReaderWrapper),但是 Dispose() 仍然会执行 if(!innerReader.IsClosed) { innerReader.Dispose(); } - 所以它最终仍然会*调用 Dispose(),这将调用 Close()*。 - Marc Gravell
亲爱的@Marc,我真的希望我的代码分析器能够工作,因为我已经有一个非常庞大的代码库(一个超过10年的大型产品),重构整个代码对我来说不是一个选项,有没有办法让代码分析器工作呢..? - Vishweshwar Kapse
1
@VishweshwarKapse 是的,一般来说分析器是有效的;但是对于它们可能存在的问题,超出了我的专业范围,无法发表评论。 - Marc Gravell

0
以下代码是一种非经过实战检验的方法,您可以遵循。
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;
}

如果源代码包含“var reader = GetDataReader(db)”,则源代码中不会出现“IDataReader”,有没有什么方法可以缓解这个问题? - Vishweshwar Kapse
GetDataReader会在同一个类中吗? - JohanP
不,它将在另一个类中。GetDataReader方法名称也不是固定的,我们可以有各种方法名称从用于数据库查询执行的类返回IDataReader类型,另一个类使用此数据访问类调用GetDataReader类型的方法。 - Vishweshwar Kapse
另一个需要注意的重要点是,我们可能会有类似这样的代码:IDataReader dr = null dr = DAL.GetOrderDetailsDataReader(db, 其他一些参数) whilel(dr.Read()) { 处理数据 } dr = DAL.GetSomeThingElseDataReader(db, 其他一些参数) dr.close();上述代码是违规的,因为在第一个dr超出范围之前没有关闭close()方法。 - Vishweshwar Kapse

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接