Skip to content

Beiträge

Crystal Reports 13 Maximales Report-Verarbeitungslimit erreicht Workaround

18. Dezember 2013 • 6 Min. Lesezeit

Crystal Reports 13 Maximales Report-Verarbeitungslimit erreicht Workaround

In der Visual Studio 2012 Version von Crystal Reports 13 gibt es einen Schwellenwert, der gleichzeitige Reports, einschließlich Subreports, auf 75 Reports pro Maschine begrenzt. Das bedeutet, wenn es 5 Webanwendungen auf einem Server gibt, zählen alle geöffneten Reports aller 5 Webanwendungen zum 75-Report-Limit.

Der Fehler manifestiert sich auf verschiedene Weise und kann zu folgenden Fehlern führen: “Not enough memory for operation.” oder “The maximum report processing jobs limit configured by your system administrator has been reached”.

Das Problem ist, dass die Reports nicht ordnungsgemäß entsorgt werden und sich weiter ansammeln, bis das 75er-Limit erreicht ist. Um dieses Problem zu beheben, müssen die Reports zum frühestmöglichen Zeitpunkt entsorgt werden. Das klingt einfach, ist aber nicht so unkompliziert, wie es scheint. Je nachdem, wie die Reports generiert werden, gibt es zwei Szenarien: Erstens die Generierung von PDFs oder Excel-Tabellen und zweitens die Verwendung des Crystal Report Viewers. Jedes Szenario hat eine andere Lebensdauer, die wir bei der Entwicklung unserer Lösung berücksichtigen müssen.

Lösung

Es gibt zwei Report-Lebensdauern, die wir verwalten müssen: generierte Reports: PDF, Excel-Tabelle und der Crystal Report Viewer.

PDFs und Excel-Tabellen werden während der Anfrage generiert. Sie können beim Page Unload Event entsorgt werden. Der Crystal Report Viewer ist etwas anders. Er muss über Anfragen hinweg bestehen und wird in der Session gespeichert. Das macht die Entsorgung der Viewer-Reports etwas herausfordernd, aber nicht unmöglich.

Die Entsorgung des Viewers beim Page Unload Event funktioniert nicht. Der Viewer hat eine Paging-Funktionalität, die jede neue Seite vom Server anfordert. Um dieses Problem zu umgehen, haben wir einen Report-Referenzzähler implementiert. Jedes Mal, wenn ein Report erstellt wird, wird er in einem concurrent dictionary gespeichert. Wenn ein Report entsorgt wird, wird der Report aus dem Dictionary entfernt. Beim Öffnen eines Report-Typs prüfen wir, ob der Benutzer diesen Report bereits geöffnet hat. Falls ja, entsorgen wir einfach den bestehenden Report und öffnen einen neuen an seiner Stelle. Weitere Gelegenheiten zur Entsorgung des Reports sind bei Session End (der Benutzer meldet sich ab), bei Application End und beim Navigieren weg von der Report-Seite.

Unser internes QA-Team testete eine nicht korrigierte Version von Crystal Reports. Crystal Reports brach bei etwa 100 gleichzeitigen Verbindungen zusammen. Nach Anwendung der Korrektur führte unser QA-Team einen Lasttest gegen die Server mit 750 gleichzeitigen Verbindungen ohne Probleme durch.

Nebenbei bemerkt haben wir Latenz beim Entsorgen von Reports mit mehreren Subreports festgestellt.

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; }
}
}

Autor: Chuck Conway ist spezialisiert auf Software-Engineering und Generative KI. Verbinden Sie sich mit ihm in den sozialen Medien: X (@chuckconway) oder besuchen Sie ihn auf YouTube.

↑ Zurück nach oben

Das könnte dir auch gefallen