Skip to content

投稿

Crystal Reports 13 最大レポート処理制限到達時の回避策

2013年12月18日 • 8 分で読める

Crystal Reports 13 最大レポート処理制限到達時の回避策

Visual Studio 2012版のCrystal Reports 13には、同時実行レポート(サブレポートを含む)を1台のマシンあたり75レポートに制限するしきい値があります。つまり、特定のサーバー上に5つのウェブアプリケーションがある場合、5つのウェブアプリケーション全体で開かれたすべてのレポートが75レポート制限にカウントされます。

このエラーはさまざまな形で現れ、「操作に十分なメモリがありません。」または「システム管理者が設定した最大レポート処理ジョブ制限に達しました」というエラーが発生する可能性があります。

問題は、レポートが破棄されず、75の制限に達するまで蓄積し続けることです。この問題を解決するには、レポートをできるだけ早い段階で破棄する必要があります。これは簡単に聞こえますが、見た目ほど単純ではありません。レポートの生成方法によって2つのシナリオがあります。1つ目はPDFまたはExcelスプレッドシートの生成で、2つ目はCrystal Report Viewerの使用です。各シナリオには異なるライフタイムがあり、ソリューションを作成する際にこれを考慮する必要があります。

ソリューション

管理する必要があるレポートのライフタイムは2つあります。生成されたレポート(PDF、Excelスプレッドシート)とCrystal Report Viewerです。

PDFとExcelスプレッドシートはリクエスト中に生成されます。これらはPage Unloadイベントで破棄できます。Crystal Report Viewerは少し異なります。リクエスト間にまたがる必要があり、セッションに保存されます。これにより、Viewerレポートの破棄は少し難しくなりますが、不可能ではありません。

Page UnloadイベントでViewerを破棄することはできません。Viewerにはページング機能があり、サーバーから新しいページごとにリクエストします。この問題を回避するために、レポート参照カウンターを実装しました。レポートが作成されるたびに、同時実行ディクショナリに保存されます。レポートが破棄されると、レポートはディクショナリから削除されます。レポートタイプを開く際に、ユーザーがこのレポートをすでに開いていないかどうかを確認します。開いている場合は、既存のレポートを単に破棄して、その場所に新しいレポートを開きます。レポートを破棄する他の機会は、セッション終了時(ユーザーがサインアウト)、アプリケーション終了時、およびレポートページから移動する際です。

内部QAチームは修正されていないバージョンのCrystal Reportsをテストしました。Crystal Reportsは約100の同時接続でダウンしました。修正を適用した後、QAチームは750の同時接続でサーバーに対して負荷テストを実行し、問題は発生しませんでした。

余談ですが、複数のサブレポートを持つレポートを破棄する際にレイテンシが発生することが確認されています。

public static class ReportFactory
{
static readonly ConcurrentDictionary<string, ConcurrentDictionary<string, UserReport>> _sessions = new ConcurrentDictionary<string, ConcurrentDictionary<string, UserReport>>();

/// <summary>
/// Creates the report dispose on unload.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="page">The page.</param>
/// <returns>``0.</returns>
public static T CreateReportDisposeOnUnload<T>(this Page page) where T : IDisposable, new()
{
    var report = new T();
    DisposeOnUnload(page, report);
    return report;
}

/// <summary>
/// Disposes on page unload.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="page">The page.</param>
/// <param name="instance">The instance.</param>
private static void DisposeOnUnload<T>(this Page page, T instance) where T : IDisposable
{
    page.Unload += (s, o) =>
    {
        if (instance != null)
        {
            CloseAndDispose(instance);
        }
    };
}

/// <summary>
/// Unloads the report when user navigates away from report.
/// </summary>
/// <param name="page">The page.</param>
public static void UnloadReportWhenUserNavigatesAwayFromPage(this Page page)
{
    var sessionId = page.Session.SessionID;
    var pageName = Path.GetFileName(page.Request.Url.AbsolutePath);

    var contains = _sessions.ContainsKey(sessionId);

    if (contains)
    {
        var reports = _sessions[sessionId];
        var report = reports.Where(r => r.Value.PageName != pageName).ToList();

        foreach (var userReport in report)
        {
            UserReport instance;

            bool removed = reports.TryRemove(userReport.Key, out instance);

            if (removed)
            {
                CloseAndDispose(instance.Report);
            }
        }
    }
}

/// <summary>
/// Gets the report.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>ReportClass.</returns>
public static T CreateReportForCrystalReportViewer<T>(this Page page) where T : IDisposable, new()
{
    var report = CreateReport<T>(page);
    return report;
}

/// <summary>
/// Creates the report.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="page">The page.</param>
/// <returns>``0.</returns>
private static T CreateReport<T>(Page page) where T : IDisposable, new()
{
    MoreThan70ReportsFoundRemoveOldestReport();

    var sessionId = page.Session.SessionID;

    bool containsKey = _sessions.ContainsKey(sessionId);
    var reportKey = typeof(T).FullName;
    var newReport = GetUserReport<T>(page);

    if (containsKey)
    {
        //Get user by session id
        var reports = _sessions[sessionId];

        //check for the report, remove it and dispose it if it exists in the collection
        RemoveReportWhenMatchingTypeFound<T>(reports);

        //add the report to the collection

        reports.TryAdd(reportKey, newReport);

        //add the reports to the user key in the concurrent dictionary
        _sessions[sessionId] = reports;
    }
    else //key does not exist in the collection
    {
        var newDictionary = new ConcurrentDictionary<string, UserReport>();
        newDictionary.TryAdd(reportKey, newReport);

        _sessions[sessionId] = newDictionary;

    }

    return (T)newReport.Report;
}

/// <summary>
/// Ifs the more than 70 reports remove the oldest report.
/// </summary>
private static void MoreThan70ReportsFoundRemoveOldestReport()
{
    var reports = _sessions.SelectMany(r => r.Value).ToList();

    if (reports.Count() > 70)
    {
        //order the reports with the oldest on top.
        var sorted = reports.OrderByDescending(r => r.Value.TimeAdded);

        //remove the oldest
        var first = sorted.FirstOrDefault();
        var key = first.Key;
        var sessionKey = first.Value.SessionId;

        if (first.Value != null)
        {
            //close and depose of the first report
            CloseAndDispose(first.Value.Report);

            var dictionary = _sessions[sessionKey];
            var containsKey = dictionary.ContainsKey(key);

            if (containsKey)
            {
                //remove the disposed report from the collection
                UserReport report;
                dictionary.TryRemove(key, out report);
            }
        }

    }
}

/// <summary>
/// Removes the report if there is a report with a match type.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="reports">The reports.</param>
private static void RemoveReportWhenMatchingTypeFound<T>(ConcurrentDictionary<string, UserReport> reports) where T : IDisposable, new()
{
    var key = typeof(T).FullName;
    var containsKey = reports.ContainsKey(key);

    if (containsKey)
    {
        UserReport instance;

        bool removed = reports.TryRemove(key, out instance);

        if (removed)
        {
            CloseAndDispose(instance.Report);
        }

    }
}

/// <summary>
/// Removes the reports for session.
/// </summary>
/// <param name="sessionId">The session identifier.</param>
public static void RemoveReportsForSession(string sessionId)
{
    var containsKey = _sessions.ContainsKey(sessionId);

    if (containsKey)
    {
        ConcurrentDictionary<string, UserReport> session;

        var removed = _sessions.TryRemove(sessionId, out session);

        if (removed)
        {
            foreach (var report in session.Where(r => r.Value.Report != null))
            {
                CloseAndDispose(report.Value.Report);
            }
        }
    }
}

/// <summary>
/// Closes the and dispose.
/// </summary>
/// <param name="report">The report.</param>
private static void CloseAndDispose(IDisposable report)
{
    report.Dispose();
}

/// <summary>
/// Gets the user report.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>UserReport.</returns>
private static UserReport GetUserReport<T>(Page page) where T : IDisposable, new()
{
    string onlyPageName = Path.GetFileName(page.Request.Url.AbsolutePath);

    var report = new T();
    var userReport = new UserReport { PageName = onlyPageName, TimeAdded = DateTime.UtcNow, Report = report, SessionId = page.Session.SessionID };

    return userReport;
}

/// <summary>
/// Removes all reports.
/// </summary>
public static void RemoveAllReports()
{
    foreach (var session in _sessions)
    {
        foreach (var report in session.Value)
        {
            if (report.Value.Report != null)
            {
                CloseAndDispose(report.Value.Report);
            }
        }

        //remove all the disposed reports
        session.Value.Clear();
    }

    //empty the collection
    _sessions.Clear();
}

private class UserReport
{
    /// <summary>
    /// Gets or sets the time added.
    /// </summary>
    /// <value>The time added.</value>
    public DateTime TimeAdded { get; set; }

    /// <summary>
    /// Gets or sets the report.
    /// </summary>
    /// <value>The report.</value>
    public IDisposable Report { get; set; }

    /// <summary>
    /// Gets or sets the session identifier.
    /// </summary>
    /// <value>The session identifier.</value>
    public string SessionId { get; set; }

    /// <summary>
    /// Gets or sets the name of the page.
    /// </summary>
    /// <value>The name of the page.</value>
    public string PageName { get; set; }
}
}

Author: Chuck Conway is an AI Engineer with nearly 30 years of software engineering experience. He builds practical AI systems—content pipelines, infrastructure agents, and tools that solve real problems—and shares what he’s learning along the way. Connect with him on social media: X (@chuckconway) or visit him on YouTube and on SubStack.

著者: Chuck Conwayは、ソフトウェアエンジニアリングの経験が30年近くあるAIエンジニアです。彼は実用的なAIシステム(コンテンツパイプライン、インフラストラクチャエージェント、実際の問題を解決するツール)を構築し、学んだことを共有しています。ソーシャルメディアで彼とつながってください: X (@chuckconway) または YouTubeSubStack で彼を訪問してください。

↑ トップに戻る

こちらもおすすめ