Skip to content

Commit

Permalink
initial code to teach the codebase async
Browse files Browse the repository at this point in the history
Introduce async operations and refactor for readability

Refactored multiple files to introduce asynchronous operations using
async/await to improve performance and responsiveness, especially for
I/O-bound tasks like database connections and file operations.

Key changes:
- Added System.Threading.Tasks namespace to several files.
- Refactored methods in DataSourceDefn.cs, DataSourcesDefn.cs, Query.cs,
  ReportDefn.cs, ReportItem.cs, ReportLink.cs, Subreport.cs, ProcessReport.cs,
  Report.cs, MainWindow.cs, and ReportViewer.cs to support async operations.
- Improved error handling and readability by introducing helper methods.
- Updated event handlers and methods in ReportViewer.cs to be asynchronous.
- Replaced Hashtable with ConcurrentDictionary in Report.cs for thread safety.
- Corrected indentation in various files for consistency.
  • Loading branch information
majorsilence committed Jan 3, 2025
1 parent 5bcdad3 commit 64f7141
Show file tree
Hide file tree
Showing 12 changed files with 341 additions and 234 deletions.
2 changes: 1 addition & 1 deletion RdlDesign/PropertyCtl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ internal void ResetSelection(DesignXmlDraw d, DesignCtl dc)
if (_Draw.SelectedCount == 0)
{
this.pgSelected.SelectedObject = new PropertyReport(_Draw, dc);
cbReportItems.SelectedItem = REPORT;
cbReportItems.SelectedItem = REPORT;
}
else if (SingleReportItemType())
{
Expand Down
142 changes: 101 additions & 41 deletions RdlEngine/Definition/DataSourceDefn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ the website www.fyiReporting.com.
using System.Data.Odbc;
using System.IO;
using RdlEngine.Resources;
using System.Threading.Tasks;
using System.Data.Common;

namespace fyiReporting.RDL
{
Expand Down Expand Up @@ -129,8 +131,8 @@ internal bool AreSameDataSource(DataSourceDefn dsd)
}

internal bool ConnectDataSource(Report rpt)
{
IDbConnection cn = GetConnection(rpt);
{
IDbConnection cn = GetConnection(rpt);
if (cn != null)
{
return true;
Expand All @@ -147,46 +149,104 @@ internal bool ConnectDataSource(Report rpt)
return false;
}

bool rc = false;
try
{
cn = RdlEngineConfig.GetConnection(_ConnectionProperties.DataProvider,
_ConnectionProperties.Connectstring(rpt));
if (cn != null)
{
cn.Open();
rc = true;
}
}
catch(Exception e)
{
string err = string.Format("DataSource '{0}'.\r\n{1}", _Name,
e.InnerException == null? e.Message: e.InnerException.Message);
if (rpt == null)
OwnerReport.rl.LogError(4, err); // error occurred during parse phase
else
rpt.rl.LogError(4, err);
if (cn != null)
{
cn.Close();
cn = null;
}
}
bool rc = false;
try
{
cn = RdlEngineConfig.GetConnection(_ConnectionProperties.DataProvider,
_ConnectionProperties.Connectstring(rpt));
if (cn != null)
{
cn.Open();
rc = true;
}
}
catch (Exception e)
{
cn = HandleConnectionError(rpt, cn, e);
}

if (cn != null)
SetSysConnection(rpt, cn);
else
{
string err = string.Format("Unable to connect to datasource '{0}'.", _Name.Nm);
if (rpt == null)
OwnerReport.rl.LogError(4, err); // error occurred during parse phase
else
rpt.rl.LogError(4, err);
}
return rc;
}
ConnectDataSourceLogInfoAndCache(rpt, cn);
return rc;
}

async internal Task<bool> ConnectDataSourceAsync(Report rpt)
{
IDbConnection cn = GetConnection(rpt);
if (cn != null)
{
return true;
}

if (_DataSourceReference != null)
{
ConnectDataSourceReference(rpt); // this will create a _ConnectionProperties
}

if (_ConnectionProperties == null ||
_ConnectionProperties.ConnectstringValue == null)
{
return false;
}

bool rc = false;
try
{
cn = RdlEngineConfig.GetConnection(_ConnectionProperties.DataProvider,
_ConnectionProperties.Connectstring(rpt));
if (cn != null)
{
if (cn is DbConnection dbConnection)
{
await dbConnection.OpenAsync();
}
else
{
cn.Open();
}
rc = true;
}
}
catch (Exception e)
{
cn = HandleConnectionError(rpt, cn, e);
}

void ConnectDataSourceReference(Report rpt)
ConnectDataSourceLogInfoAndCache(rpt, cn);
return rc;
}

private void ConnectDataSourceLogInfoAndCache(Report rpt, IDbConnection cn)
{
if (cn != null)
SetSysConnection(rpt, cn);
else
{
string err = string.Format("Unable to connect to datasource '{0}'.", _Name.Nm);
if (rpt == null)
OwnerReport.rl.LogError(4, err); // error occurred during parse phase
else
rpt.rl.LogError(4, err);
}
}

private IDbConnection HandleConnectionError(Report rpt, IDbConnection cn, Exception e)
{
string err = string.Format("DataSource '{0}'.\r\n{1}", _Name,
e.InnerException == null ? e.Message : e.InnerException.Message);
if (rpt == null)
OwnerReport.rl.LogError(4, err); // error occurred during parse phase
else
rpt.rl.LogError(4, err);
if (cn != null)
{
cn.Close();
cn = null;
}

return cn;
}

void ConnectDataSourceReference(Report rpt)
{
if (_ConnectionProperties != null)
return;
Expand Down Expand Up @@ -218,7 +278,7 @@ void ConnectDataSourceReference(Report rpt)
XmlNode xNodeLoop = xDoc.FirstChild;

_ConnectionProperties = new ConnectionProperties(OwnerReport, this, xNodeLoop);
_ConnectionProperties.FinalPass();
_ConnectionProperties.FinalPass();
}
catch (Exception e)
{
Expand Down
5 changes: 3 additions & 2 deletions RdlEngine/Definition/DataSourcesDefn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ the website www.fyiReporting.com.
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Threading.Tasks;
using System.Xml;


Expand Down Expand Up @@ -85,7 +86,7 @@ override internal void FinalPass()
return;
}

internal bool ConnectDataSources(Report rpt)
async internal Task<bool> ConnectDataSources(Report rpt)
{
// Handle any parent connections if any (ie we're in a subreport and want to use parent report connections
if (rpt.ParentConnections != null && rpt.ParentConnections.Items != null)
Expand All @@ -105,7 +106,7 @@ internal bool ConnectDataSources(Report rpt)

foreach (DataSourceDefn ds in _Items.Values)
{
ds.ConnectDataSource(rpt);
await ds.ConnectDataSourceAsync(rpt);
}
return true;
}
Expand Down
148 changes: 81 additions & 67 deletions RdlEngine/Definition/Query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ the website www.fyiReporting.com.
using System.Globalization;
using System.Reflection;
using RdlEngine.Resources;
using System.Threading.Tasks;
using System.Data.Common;

namespace fyiReporting.RDL
{
Expand Down Expand Up @@ -103,82 +105,94 @@ internal Query(ReportDefn r, ReportLink p, XmlNode xNode) : base(r, p)
}
}

// Handle parsing of function in final pass
override internal void FinalPass()
{
if (_CommandText != null)
_CommandText.FinalPass();
if (_QueryParameters != null)
_QueryParameters.FinalPass();

// verify the data source
DataSourceDefn ds=null;
if (OwnerReport.DataSourcesDefn != null &&
OwnerReport.DataSourcesDefn.Items != null)
{
ds = OwnerReport.DataSourcesDefn[_DataSourceName];
}
if (ds == null)
{
OwnerReport.rl.LogError(8, "Query references unknown data source '" + _DataSourceName + "'");
return;
}
_DataSourceDefn = ds;
internal override void FinalPass()
{
// HACK:
Task.Run(async ()=> await FinalPassAsync()).GetAwaiter().GetResult();
}

IDbConnection cnSQL = ds.SqlConnect(null);
if (cnSQL == null || _CommandText == null)
return;
// Handle parsing of function in final pass
internal override async Task FinalPassAsync()
{
if (_CommandText != null)
_CommandText.FinalPass();
if (_QueryParameters != null)
_QueryParameters.FinalPass();

// verify the data source
DataSourceDefn ds = null;
if (OwnerReport.DataSourcesDefn != null &&
OwnerReport.DataSourcesDefn.Items != null)
{
ds = OwnerReport.DataSourcesDefn[_DataSourceName];
}
if (ds == null)
{
OwnerReport.rl.LogError(8, "Query references unknown data source '" + _DataSourceName + "'");
return;
}
_DataSourceDefn = ds;

// Treat this as a SQL statement
String sql = _CommandText.EvaluateString(null, null);
IDbCommand cmSQL=null;
IDataReader dr=null;
try
{
cmSQL = cnSQL.CreateCommand();
cmSQL.CommandText = AddParametersAsLiterals(null, cnSQL, sql, false);
IDbConnection cnSQL = ds.SqlConnect(null);
if (cnSQL == null || _CommandText == null)
return;

// Treat this as a SQL statement
String sql = _CommandText.EvaluateString(null, null);
IDbCommand cmSQL = null;
IDataReader dr = null;
try
{
cmSQL = cnSQL.CreateCommand();
cmSQL.CommandText = AddParametersAsLiterals(null, cnSQL, sql, false);
if (this._QueryCommandType == QueryCommandTypeEnum.StoredProcedure)
cmSQL.CommandType = CommandType.StoredProcedure;

AddParameters(null, cnSQL, cmSQL, false);
dr = cmSQL.ExecuteReader(CommandBehavior.SchemaOnly);
if (dr.FieldCount < 10)
_Columns = new ListDictionary(); // Hashtable is overkill for small lists
else
_Columns = new Hashtable(dr.FieldCount);

for (int i=0; i < dr.FieldCount; i++)
{
QueryColumn qc = new QueryColumn(i, dr.GetName(i), Type.GetTypeCode(dr.GetFieldType(i)) );

try { _Columns.Add(qc.colName, qc); }
catch // name has already been added to list:
{ // According to the RDL spec SQL names are matched by Name not by relative
// position: this seems wrong to me and causes this problem; but
// user can fix by using "as" keyword to name columns in Select
// e.g. Select col as "col1", col as "col2" from tableA
OwnerReport.rl.LogError(8, String.Format("Column '{0}' is not uniquely defined within the SQL Select columns.", qc.colName));
}
}
}
catch (Exception e)
{
AddParameters(null, cnSQL, cmSQL, false);
if (cmSQL is DbCommand dbCommand)
{
dr = await dbCommand.ExecuteReaderAsync(CommandBehavior.SchemaOnly).ConfigureAwait(false);
}
else
{
dr = cmSQL.ExecuteReader(CommandBehavior.SchemaOnly);
}

if (dr.FieldCount < 10)
_Columns = new ListDictionary(); // Hashtable is overkill for small lists
else
_Columns = new Hashtable(dr.FieldCount);

for (int i = 0; i < dr.FieldCount; i++)
{
QueryColumn qc = new QueryColumn(i, dr.GetName(i), Type.GetTypeCode(dr.GetFieldType(i)));

try { _Columns.Add(qc.colName, qc); }
catch // name has already been added to list:
{ // According to the RDL spec SQL names are matched by Name not by relative
// position: this seems wrong to me and causes this problem; but
// user can fix by using "as" keyword to name columns in Select
// e.g. Select col as "col1", col as "col2" from tableA
OwnerReport.rl.LogError(8, String.Format("Column '{0}' is not uniquely defined within the SQL Select columns.", qc.colName));
}
}
}
catch (Exception e)
{
// Issue #35 - Kept the logging
OwnerReport.rl.LogError(4, "SQL Exception during report compilation: " + e.Message + "\r\nSQL: " + sql);
throw;
}
finally
{
if (cmSQL != null)
{
cmSQL.Dispose();
if (dr != null)
dr.Close();
}
}

return;
}
finally
{
if (cmSQL != null)
{
cmSQL.Dispose();
if (dr != null)
dr.Close();
}
}
}

internal bool GetData(Report rpt, Fields flds, Filters f)
{
Expand Down
Loading

0 comments on commit 64f7141

Please sign in to comment.