পোস্ট
Crystal Reports 13 সর্বোচ্চ রিপোর্ট প্রসেসিং সীমা পৌঁছানোর সমাধান
১৮ ডিসেম্বর, ২০১৩ • 6 মিনিট পড়া

Visual Studio 2012 সংস্করণের Crystal Reports 13-এ একটি থ্রেশহোল্ড রয়েছে যা একযোগে রিপোর্টগুলিকে থ্রটল করে, এতে সাবরিপোর্টও অন্তর্ভুক্ত, একটি মেশিনে 75টি রিপোর্ট পর্যন্ত। এর মানে হল যদি একটি নির্দিষ্ট সার্ভারে 5টি ওয়েব অ্যাপ্লিকেশন থাকে তাহলে সব 5টি ওয়েব অ্যাপ্লিকেশনের সমস্ত খোলা রিপোর্ট 75 রিপোর্ট সীমার দিকে গণনা করা হয়।
ত্রুটিটি বিভিন্ন উপায়ে প্রকাশ পায় এবং নিম্নলিখিত ত্রুটিগুলির কারণ হতে পারে “অপারেশনের জন্য পর্যাপ্ত মেমরি নেই।” অথবা “আপনার সিস্টেম অ্যাডমিনিস্ট্রেটর দ্বারা কনফিগার করা সর্বোচ্চ রিপোর্ট প্রসেসিং জবস সীমা পৌঁছে গেছে”।
সমস্যা হল রিপোর্টগুলি dispose করা হয় না এবং সেগুলি 75 সীমা পৌঁছানো পর্যন্ত জমা হতে থাকে। এই সমস্যা সমাধানের জন্য, রিপোর্টগুলিকে যত তাড়াতাড়ি সম্ভব dispose করতে হবে। এটি সহজ শোনায়, কিন্তু এটি যতটা সহজ মনে হয় ততটা সরল নয়। রিপোর্টগুলি কীভাবে তৈরি করা হয় তার উপর নির্ভর করে দুটি পরিস্থিতি রয়েছে: প্রথমটি হল PDF বা Excel স্প্রেডশিট তৈরি করা এবং দ্বিতীয়টি হল Crystal Report Viewer ব্যবহার করা। প্রতিটি পরিস্থিতির একটি ভিন্ন জীবনকাল রয়েছে, যা আমাদের সমাধান তৈরি করার সময় বিবেচনা করতে হবে।
সমাধান
আমাদের দুটি রিপোর্ট জীবনকাল পরিচালনা করতে হবে: তৈরি করা রিপোর্ট: PDF, Excel স্প্রেডশিট এবং Crystal Report viewer।
PDF এবং Excel স্প্রেডশিটগুলি অনুরোধের সময় তৈরি হয়। এগুলি Page Unload ইভেন্টে dispose করা যেতে পারে। Crystal Report viewer একটু ভিন্ন। এটি অনুরোধগুলি জুড়ে বিস্তৃত হতে হবে এবং সেশনে সংরক্ষিত হয়। এটি viewer রিপোর্টগুলি dispose করাকে একটু চ্যালেঞ্জিং করে তোলে, কিন্তু অসম্ভব নয়।
Page Unload ইভেন্টে Viewer dispose করা কাজ করবে না। Viewer-এর পেজিং কার্যকারিতা রয়েছে যা সার্ভার থেকে প্রতিটি নতুন পৃষ্ঠার অনুরোধ করে। এই সমস্যার সমাধানের জন্য আমরা একটি রিপোর্ট রেফারেন্স কাউন্টার বাস্তবায়ন করেছি। প্রতিবার একটি রিপোর্ট তৈরি হলে, এটি একটি concurrent dictionary-তে সংরক্ষিত হয়। যখন একটি রিপোর্ট dispose করা হয় তখন রিপোর্টটি dictionary থেকে সরানো হয়। একটি ধরনের রিপোর্ট খোলার সময় আমরা পরীক্ষা করি যে ব্যবহারকারীর কাছে ইতিমধ্যে এই রিপোর্ট খোলা আছে কিনা, যদি থাকে, আমরা কেবল বিদ্যমান রিপোর্টটি dispose করি এবং এর জায়গায় একটি নতুন খুলি। রিপোর্ট dispose করার অন্যান্য সুযোগ হল Session End (ব্যবহারকারী সাইন আউট করে), Application End এবং রিপোর্ট পৃষ্ঠা থেকে দূরে নেভিগেট করার সময়।
আমাদের অভ্যন্তরীণ QA টিম Crystal Reports-এর একটি অ-ফিক্স সংস্করণ পরীক্ষা করেছে। Crystal Reports প্রায় 100 concurrent connection-এ ব্যর্থ হয়েছে। ফিক্স প্রয়োগ করার পর আমাদের QA টিম কোনো সমস্যা ছাড়াই 750 concurrent connection-এ সার্ভারগুলির বিরুদ্ধে লোড চালিয়েছে।
একটি পার্শ্ব নোটে, আমরা একাধিক সাব রিপোর্ট সহ রিপোর্ট dispose করার সময় বিলম্বের সম্মুখীন হয়েছি।
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; }
}
}
লেখক: চাক কনওয়ে সফটওয়্যার ইঞ্জিনিয়ারিং এবং জেনারেটিভ এআই-তে বিশেষজ্ঞ। তার সাথে সোশ্যাল মিডিয়ায় যোগাযোগ করুন: X (@chuckconway) অথবা তাকে YouTube-এ দেখুন।