Beiträge
Crystal Reports 13 Maximale Berichtverarbeitungslimit erreicht Workaround
18. Dezember 2013 • 6 min Lesezeit
In der Visual Studio 2012-Version von Crystal Reports 13 gibt es einen Schwellenwert, der gleichzeitige Berichte, einschließlich Unterberichte, auf 75 Berichte pro Computer drosselt. Dies bedeutet, dass wenn 5 Webanwendungen auf einem bestimmten Server vorhanden sind, alle geöffneten Berichte über alle 5 Webanwendungen hinweg zum Limit von 75 Berichten zählen.
Der Fehler manifestiert sich auf verschiedene Weise und kann zu den folgenden Fehlern führen: „Nicht genug Speicher für Operation.” oder „Das von Ihrem Systemadministrator konfigurierte maximale Berichtverarbeitungslimit wurde erreicht”.
Das Problem besteht darin, dass die Berichte nicht freigegeben werden und sich weiterhin ansammeln, bis das Limit von 75 erreicht ist. Um dieses Problem zu beheben, müssen die Berichte so früh wie möglich freigegeben werden. Das klingt einfach, ist aber nicht so unkompliziert, wie es scheint. Je nachdem, wie die Berichte generiert werden, gibt es zwei Szenarien: Das erste ist die Generierung von PDFs oder Excel-Tabellen und das zweite ist die Verwendung des Crystal Report Viewers. Jedes Szenario hat eine andere Lebensdauer, die wir bei der Erstellung unserer Lösung berücksichtigen müssen.
Lösung
Es gibt zwei Berichtlebensdauern, die wir verwalten müssen: generierte Berichte: PDF, Excel-Tabelle und der Crystal Report Viewer.
PDFs und Excel-Tabellen werden während der Anfrage generiert. Sie können beim Seitenladevorgang freigegeben werden. Der Crystal Report Viewer ist etwas anders. Er muss sich über Anfragen erstrecken und wird in der Sitzung gespeichert. Dies macht das Freigeben der Viewer-Berichte etwas schwierig, aber nicht unmöglich.
Das Freigeben des Viewers beim Seitenladevorgang funktioniert nicht. Der Viewer verfügt über Paginierungsfunktionen, die jede neue Seite vom Server anfordern. Um dieses Problem zu umgehen, haben wir einen Berichtreferenzzähler implementiert. Jedes Mal, wenn ein Bericht erstellt wird, wird er in einem gleichzeitigen Wörterbuch gespeichert. Wenn ein Bericht freigegeben wird, wird der Bericht aus dem Wörterbuch entfernt. Beim Öffnen eines Berichttyps überprüfen wir, dass der Benutzer diesen Bericht nicht bereits geöffnet hat. Wenn dies der Fall ist, geben wir einfach den vorhandenen Bericht frei und öffnen einen neuen an seiner Stelle. Weitere Gelegenheiten zum Freigeben des Berichts sind beim Sitzungsende (der Benutzer meldet sich ab), beim Anwendungsende und beim Navigieren weg von der Berichtsseite.
Unser internes QA-Team testete eine nicht korrigierte Version von Crystal Reports. Crystal Reports fiel bei etwa 100 gleichzeitigen Verbindungen zusammen. Nach Anwendung der Korrektur führte unser QA-Team einen Lasttest auf den Servern mit 750 gleichzeitigen Verbindungen ohne Probleme durch.
Nebenbei bemerkt: Wir haben Latenz beim Freigeben von Berichten mit mehreren Unterberichten 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 ein KI-Ingenieur mit fast 30 Jahren Erfahrung in der Softwareentwicklung. Er entwickelt praktische KI-Systeme – Content-Pipelines, Infrastruktur-Agenten und Tools, die echte Probleme lösen – und teilt seine Erkenntnisse unterwegs. Verbinden Sie sich mit ihm in den sozialen Medien: X (@chuckconway) oder besuchen Sie ihn auf YouTube und auf SubStack.