We have a legacy C# Windows Service application (.Net Framework 4.8) that performs some statistical analysis which usually takes hours to complete as the underlying database has millions of rows of historical data.
It works fine if there is no underlying network interruption. However, we recently started using a database which is only accessible over the VPN. Now if there is any VPN connection issue, the analysis stops.
Is there any way to recover from these transient faults silently and gracefully and continue the work?
For the SqlClient I have found a sample code at https://learn.microsoft.com/en-us/sql/connect/ado-net/step-4-connect-resiliently-sql-ado-net?view=sql-server-ver15
The sample code from the link is shown below (just in case if the link dies).
using System; // C#
using CG = System.Collections.Generic;
using QC = Microsoft.Data.SqlClient;
using TD = System.Threading;
namespace RetryAdo2
{
public class Program
{
static public int Main(string[] args)
{
bool succeeded = false;
int totalNumberOfTimesToTry = 4;
int retryIntervalSeconds = 10;
for (int tries = 1;
tries <= totalNumberOfTimesToTry;
tries++)
{
try
{
if (tries > 1)
{
Console.WriteLine
("Transient error encountered. Will begin attempt number {0} of {1} max...",
tries, totalNumberOfTimesToTry
);
TD.Thread.Sleep(1000 * retryIntervalSeconds);
retryIntervalSeconds = Convert.ToInt32
(retryIntervalSeconds * 1.5);
}
AccessDatabase();
succeeded = true;
break;
}
catch (QC.SqlException sqlExc)
{
if (TransientErrorNumbers.Contains
(sqlExc.Number) == true)
{
Console.WriteLine("{0}: transient occurred.", sqlExc.Number);
continue;
}
else
{
Console.WriteLine(sqlExc);
succeeded = false;
break;
}
}
catch (TestSqlException sqlExc)
{
if (TransientErrorNumbers.Contains
(sqlExc.Number) == true)
{
Console.WriteLine("{0}: transient occurred. (TESTING.)", sqlExc.Number);
continue;
}
else
{
Console.WriteLine(sqlExc);
succeeded = false;
break;
}
}
catch (Exception Exc)
{
Console.WriteLine(Exc);
succeeded = false;
break;
}
}
if (succeeded == true)
{
return 0;
}
else
{
Console.WriteLine("ERROR: Unable to access the database!");
return 1;
}
}
/// <summary>
/// Connects to the database, reads,
/// prints results to the console.
/// </summary>
static public void AccessDatabase()
{
//throw new TestSqlException(4060); //(7654321); // Uncomment for testing.
using (var sqlConnection = new QC.SqlConnection
(GetSqlConnectionString()))
{
using (var dbCommand = sqlConnection.CreateCommand())
{
dbCommand.CommandText = #"
SELECT TOP 3
ob.name,
CAST(ob.object_id as nvarchar(32)) as [object_id]
FROM sys.objects as ob
WHERE ob.type='IT'
ORDER BY ob.name;";
sqlConnection.Open();
var dataReader = dbCommand.ExecuteReader();
while (dataReader.Read())
{
Console.WriteLine("{0}\t{1}",
dataReader.GetString(0),
dataReader.GetString(1));
}
}
}
}
/// <summary>
/// You must edit the four 'my' string values.
/// </summary>
/// <returns>An ADO.NET connection string.</returns>
static private string GetSqlConnectionString()
{
// Prepare the connection string to Azure SQL Database.
var sqlConnectionSB = new QC.SqlConnectionStringBuilder();
// Change these values to your values.
sqlConnectionSB.DataSource = "tcp:myazuresqldbserver.database.windows.net,1433"; //["Server"]
sqlConnectionSB.InitialCatalog = "MyDatabase"; //["Database"]
sqlConnectionSB.UserID = "MyLogin"; // "#yourservername" as suffix sometimes.
sqlConnectionSB.Password = "MyPassword";
sqlConnectionSB.IntegratedSecurity = false;
// Adjust these values if you like. (ADO.NET 4.5.1 or later.)
sqlConnectionSB.ConnectRetryCount = 3;
sqlConnectionSB.ConnectRetryInterval = 10; // Seconds.
// Leave these values as they are.
sqlConnectionSB.IntegratedSecurity = false;
sqlConnectionSB.Encrypt = true;
sqlConnectionSB.ConnectTimeout = 30;
return sqlConnectionSB.ToString();
}
static public CG.List<int> TransientErrorNumbers =
new CG.List<int> { 4060, 40197, 40501, 40613,
49918, 49919, 49920, 11001 };
}
/// <summary>
/// For testing retry logic, you can have method
/// AccessDatabase start by throwing a new
/// TestSqlException with a Number that does
/// or does not match a transient error number
/// present in TransientErrorNumbers.
/// </summary>
internal class TestSqlException : ApplicationException
{
internal TestSqlException(int testErrorNumber)
{ this.Number = testErrorNumber; }
internal int Number
{ get; set; }
}
}
However, I couldn't find any helpful material for the OracleClient. Any ideas, please?
Related
I have this test code that listens for messages forever. If it gets one it prints it out. What I want to do is avoid having to construct a MQMessage object prior to each get(). How do i reuse a MQMessage for multiple calls to get()?
using System;
using IBM.WMQ;
namespace WMQ {
class Program {
static void Main(string[] args) {
string QueueManagerName = "A1PATA00";
string channelName = "ECACHE";
string connectionName = "A1PATA00.WORLDSPAN.COM(1414)";
var queueManager = new MQQueueManager(QueueManagerName, channelName, connectionName);
MQQueue get = queueManager.AccessQueue("SPLASH.ECAC.2", MQC.MQOO_INPUT_AS_Q_DEF | MQC.MQGMO_FAIL_IF_QUIESCING);
var gmo = new MQGetMessageOptions();
gmo.Options = MQC.MQGMO_FAIL_IF_QUIESCING | MQC.MQGMO_WAIT;
gmo.WaitInterval = 10000000;// wait time
// var queueMessage = new MQMessage(); <--- i want to do this new once!
while (true) {
var queueMessage = new MQMessage(); // <-- only works if I do this every time i do a get
get.Get(queueMessage, gmo);
var strReturn = queueMessage.ReadString(queueMessage.MessageLength);
Console.WriteLine(strReturn);
}
}
}
}
When I see questions like this, I shake my head. You do not
understand object oriented concepts in the .NET VM (framework) and
garbage collection.
Also, why don't you write pure C# code?
Finally, in a world where security is very important, your code does
not support either SSL/TLS and/or UserID & Password authentication.
(1) Please notice where I am defining the MQMessage object (very important).
There is basically no difference in memory usage or speed between the following 2 code snippets:
(A)
MQMessage msg = null;
while (true)
{
msg = new MQMessage();
get.Get(msg, gmo);
Console.WriteLine(msg.ReadString(msg.MessageLength));
}
(B)
MQMessage msg = new MQMessage();
while (true)
{
get.Get(msg, gmo);
Console.WriteLine(msg.ReadString(msg.MessageLength));
msg.ClearMessage();
msg.MessageId = MQC.MQMI_NONE;
msg.CorrelationId = MQC.MQCI_NONE;
}
I prefer (A) because it is cleaner and easier to read.
(2) When you use 'var', you are forcing the .NET framework to guess at what you are doing. Do pure object oriented programming.
i.e.
MQMessage msg = new MQMessage();
(3) Explicitly setting the channel name and connection name in the MQQueueManager does not allow setting of MQ security information. Also, do NOT use the MQEnvironment class as it is NOT thread safe. It is far better to put all the information in a Hashtable and pass the Hashtable to the MQQueueManager class. Here is a MQ .NET managed-mode example using a Hashtable for MQ connection information:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using IBM.WMQ;
namespace MQTest02
{
class MQTest02
{
private Hashtable inParms = null;
private Hashtable qMgrProp = null;
private System.String qManager;
private System.String outputQName;
/*
* The constructor
*/
public MQTest02()
: base()
{
}
/// <summary> Make sure the required parameters are present.</summary>
/// <returns> true/false
/// </returns>
private bool allParamsPresent()
{
bool b = inParms.ContainsKey("-h") && inParms.ContainsKey("-p") &&
inParms.ContainsKey("-c") && inParms.ContainsKey("-m") &&
inParms.ContainsKey("-q");
if (b)
{
try
{
System.Int32.Parse((System.String)inParms["-p"]);
}
catch (System.FormatException e)
{
b = false;
}
}
return b;
}
/// <summary> Extract the command-line parameters and initialize the MQ variables.</summary>
/// <param name="args">
/// </param>
/// <throws> IllegalArgumentException </throws>
private void init(System.String[] args)
{
inParms = Hashtable.Synchronized(new Hashtable());
if (args.Length > 0 && (args.Length % 2) == 0)
{
for (int i = 0; i < args.Length; i += 2)
{
inParms[args[i]] = args[i + 1];
}
}
else
{
throw new System.ArgumentException();
}
if (allParamsPresent())
{
qManager = ((System.String)inParms["-m"]);
outputQName = ((System.String)inParms["-q"]);
qMgrProp = new Hashtable();
qMgrProp.Add(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_MANAGED);
qMgrProp.Add(MQC.HOST_NAME_PROPERTY, ((System.String)inParms["-h"]));
qMgrProp.Add(MQC.CHANNEL_PROPERTY, ((System.String)inParms["-c"]));
try
{
qMgrProp.Add(MQC.PORT_PROPERTY, System.Int32.Parse((System.String)inParms["-p"]));
}
catch (System.FormatException e)
{
qMgrProp.Add(MQC.PORT_PROPERTY, 1414);
}
if (inParms.ContainsKey("-u"))
qMgrProp.Add(MQC.USER_ID_PROPERTY, ((System.String)inParms["-u"]));
if (inParms.ContainsKey("-x"))
qMgrProp.Add(MQC.PASSWORD_PROPERTY, ((System.String)inParms["-x"]));
if (inParms.ContainsKey("-s"))
qMgrProp.Add(MQC.SECURITY_EXIT_PROPERTY, ((System.String)inParms["-s"]));
System.Console.Out.WriteLine("MQTest02:");
Console.WriteLine(" QMgrName ='{0}'", qManager);
Console.WriteLine(" Output QName ='{0}'", outputQName);
System.Console.Out.WriteLine("QMgr Property values:");
foreach (DictionaryEntry de in qMgrProp)
{
Console.WriteLine(" {0} = '{1}'", de.Key, de.Value);
}
}
else
{
throw new System.ArgumentException();
}
}
/// <summary> Connect, open queue, read a message, close queue and disconnect.
///
/// </summary>
/// <throws> MQException </throws>
private void testReceive()
{
MQQueueManager qMgr = null;
MQQueue queue = null;
int openOptions = MQC.MQOO_INPUT_AS_Q_DEF + MQC.MQOO_FAIL_IF_QUIESCING;
MQGetMessageOptions gmo = new MQGetMessageOptions();
MQMessage receiveMsg = null;
try
{
qMgr = new MQQueueManager(qManager, qMgrProp);
System.Console.Out.WriteLine("MQTest02 successfully connected to " + qManager);
queue = qMgr.AccessQueue(outputQName, openOptions, null, null, null); // no alternate user id
System.Console.Out.WriteLine("MQTest02 successfully opened " + outputQName);
receiveMsg = new MQMessage();
queue.Get(receiveMsg, gmo);
System.Console.Out.WriteLine("Message Data>>>" + receiveMsg.ReadString(receiveMsg.MessageLength));
}
catch (MQException mqex)
{
System.Console.Out.WriteLine("MQTest02 cc=" + mqex.CompletionCode + " : rc=" + mqex.ReasonCode);
}
catch (System.IO.IOException ioex)
{
System.Console.Out.WriteLine("MQTest02 ioex=" + ioex);
}
finally
{
try
{
queue.Close();
System.Console.Out.WriteLine("MQTest02 closed: " + outputQName);
}
catch (MQException mqex)
{
System.Console.Out.WriteLine("MQTest02 cc=" + mqex.CompletionCode + " : rc=" + mqex.ReasonCode);
}
try
{
qMgr.Disconnect();
System.Console.Out.WriteLine("MQTest02 disconnected from " + qManager);
}
catch (MQException mqex)
{
System.Console.Out.WriteLine("MQTest02 cc=" + mqex.CompletionCode + " : rc=" + mqex.ReasonCode);
}
}
}
/// <summary> main line</summary>
/// <param name="args">
/// </param>
// [STAThread]
public static void Main(System.String[] args)
{
MQTest02 mqt = new MQTest02();
try
{
mqt.init(args);
mqt.testReceive();
}
catch (System.ArgumentException e)
{
System.Console.Out.WriteLine("Usage: MQTest02 -h host -p port -c channel -m QueueManagerName -q QueueName [-u userID] [-x passwd] [-s securityExit]");
System.Environment.Exit(1);
}
catch (MQException e)
{
System.Console.Out.WriteLine(e);
System.Environment.Exit(1);
}
System.Environment.Exit(0);
}
}
}
To run MQTest02 using your information, it would be:
MQTest02.exe -h A1PATA00.WORLDSPAN.COM -p 1414 -m A1PATA00 -c ECACHE -q SPLASH.ECAC.2
In the IBM MQ Knowledge center page "ClearMessage method" it documents the following:
This method clears the data buffer portion of the MQMessage object.
Any Message Data in the data buffer is lost, because MessageLength,
DataLength, and DataOffset are all set to zero.
The Message Descriptor (MQMD) portion is unaffected; an application
might need to modify some of the MQMD fields before reusing the
MQMessage object. To set the MQMD fields back use New to replace the
object with a new instance.
In the IBM MQ Knowledge center page "MQMessage .NET class" it documents the following:
public byte[] MessageId {get; set;}
For an MQQueue.Get call, this field specifies the message identifier
of the message to be retrieved. Normally, the queue manager returns
the first message with a message identifier and correlation identifier
that match the message descriptor fields. Allow any message identifier
to match using the special value MQC.MQMI_NONE.
public byte[] CorrelationId {get; set;}
For an MQQueue.Get call, the correlation identifier of the message to
be retrieved. The queue manager returns the first message with a
message identifier and a correlation identifier that match the message
descriptor fields. The default value, MQC.MQCI_NONE, helps any
correlation identifier to match.
Try this:
var queueMessage = new MQMessage(); <--- i want to do this new once!
while (true) {
//var queueMessage = new MQMessage(); // <-- only works if I do this every time i do a get
queueMessage.ClearMessage();
queueMessage.MessageId = MQC.MQMI_NONE;
queueMessage.CorrelationId = MQC.MQCI_NONE;
get.Get(queueMessage, gmo);
I am looking to loop through all SAP GuiComponents in c# but am struggling to get all the children of children of a gui session.
This is what I have so far (initially passing session.ActiveWindow.Children to nodes):
private void IterateFindIDByNAme(GuiComponentCollection nodes, string searchstring)
{
if (foundID == "")
{
foreach (GuiComponent node in (GuiComponentCollection)nodes)
{
var comp = node;
if (comp.Name.Contains(searchstring))
foundID = comp.Id;
else
{
try
{
FindIDByNAme((GuiComponentCollection)node, searchstring);
}
catch { }
}
}
}
}
Its able to get all the child elements of session.ActiveWindow but when trying to cast all the children to GuiComponentCollections its falling over.
This would be irrelevant if I could use the FindByName function but for some reason it is not working on the SAP screen i am currently working on (it does work on others, not sure why).
The ID of the field is:
wnd[0]/usr/subBCA_SUB_HEADER:SAPLBANK_BDT_CTRL:0100/subSUBS_DETAIL:SAPLBUSS:0028/ssubGENSUB:SAPLBUSS:4038/subA01P02:SAPLBCA_DYN_CN_CNMANO:0002/ctxtBCA_DYN_CONTRACT_ORGENTRY-ORGUNIT
and the function I am trying is:
((GuiTextField)session.FindByName("BCA_DYN_CONTRACT_ORGENTRY-ORGUNIT", "GuiTextField")).Text = "Test";
The findbyid works fine, but not findbyname?
I know this is 2 questions, but kind of related.
ctxtBCA_DYN_CONTRACT_ORGENTRY-ORGUNIT
The type of the control is a GuiCTextField not GuiTextField:
session.FindByName("BCA_DYN_CONTRACT_ORGENTRY-ORGUNIT", "GuiCTextField")
Sample Code :
public DateTime? GetModificationDate(int employeeID)
{
var session = SapHelper.GetActiveSession();
Console.WriteLine("Recherche de la measure [A5 ou A6] avec un motif 90...");
var window = session.BeginTransaction("PA20", "Afficher données de base personnel");
window.FindByName<GuiCTextField>("RP50G-PERNR").Text = employeeID.ToString();
window.FindByName<GuiCTextField>("RP50G-CHOIC").Text = "Mesures (0000)";
window.FindByName<GuiCTextField>("RP50G-SUBTY").Text = null;
window.FindByName<GuiButton>("btn[20]").Press(); // list view
if (window.Text == "Afficher données de base personnel")
{
Console.WriteLine(">> " + window.FindByName<GuiStatusbar>("sbar").Text);
return null;
}
/* Index Type Title Tooltip
0 GuiTextField Début Date de début
1 GuiTextField Fin Date de fin
2 GuiCTextField Mes. Catégorie de mesure
3 GuiTextField Dés. cat. mesure Dés. cat. mesure
4 GuiCTextField MotMe Motif mesure
5 GuiTextField Dés. motif mesure Dés. motif mesure
6 GuiCTextField Client Statut propre client
7 GuiCTextField Activité Statut d'activité
8 GuiCTextField Paiement Statut paiement part */
var result = window.FindByName<GuiTableControl>("MP000000TC3000").AsEnumerable()
.Select(x => new
{
Start = x.GetText(0),
Category = x.GetCText(2),
CategoryText = x.GetText(3),
Reason = x.GetCText(4),
ReasonText = x.GetText(5),
})
.Where(x => (x.Category == "A5" || x.Category == "AG") && x.Reason == "90")
.FirstOrDefault();
if (result == null)
{
Console.WriteLine(">> aucune measure [A5 ou A6] avec un motif 90 trouvée");
return null;
}
else
{
Console.WriteLine(">> {0}:[{1}]{2} [{3}]{4}",
result.Start, result.Category, result.Category, result.Reason, result.ReasonText);
return DateTime.ParseExact(result.Start, "yyyy/MM/dd", CultureInfo.InvariantCulture);
}
}
Xy.Sap :
#region namespace Xy.Sap
namespace Xy.Sap
{
using System.Reflection;
using sapfewse;
using saprotwr.net;
using COMException = System.Runtime.InteropServices.COMException;
public static class SapHelper
{
public static GuiSession GetActiveSession()
{
var rot = new CSapROTWrapper().GetROTEntry("SAPGUI");
if (rot == null)
throw SapException.NotOpened();
var app = (GuiApplication)rot.GetType().InvokeMember("GetScriptingEngine", BindingFlags.InvokeMethod, null, rot, null);
var connectedSession = app.Connections.Cast<GuiConnection>()
.SelectMany(x => x.Children.Cast<GuiSession>())
.Where(x => !string.IsNullOrEmpty(x.Info.User))
.FirstOrDefault();
if (connectedSession == null)
throw SapException.NotOpened();
return connectedSession;
}
}
public class SapException : Exception
{
public SapException(string message) : base(message) { }
public static SapException NotOpened()
{
return new SapException("Veuillez lancer le SAP et de connecter avec votre identité");
}
}
public static class SapExtensions
{
#region GuiSession
/// <summary>
/// Shortcut for PA20 query
/// </summary>
/// <param name="session"></param>
/// <param name="employeeID"></param>
/// <param name="it">Infotype ID</param>
/// <param name="sty">Subtype ID</param>
/// <param name="asListView"></param>
/// <returns></returns>
public static GuiFrameWindow QueryPA20(this GuiSession session, int employeeID, string it, string sty = null, bool asListView = false)
{
var window = session.BeginTransaction("PA20", "Afficher données de base personnel");
window.FindByName<GuiCTextField>("RP50G-PERNR").Text = employeeID.ToString();
window.FindByName<GuiCTextField>("RP50G-CHOIC").Text = it;
window.FindByName<GuiCTextField>("RP50G-SUBTY").Text = sty;
window.FindByName<GuiButton>(asListView ? "btn[20]" : "btn[7]").Press();
if (window.Text == "Afficher données de base personnel")
{
var exception = new InvalidOperationException(string.Format("Failed to access to personal information of {0}", employeeID));
exception.Data["Employee ID"] = employeeID;
exception.Data["Infotype"] = it;
exception.Data["Subtype"] = sty;
exception.Data["View"] = asListView ? "ListView[Mont]" : "RecordView[Glasses]";
exception.Data["Status Message"] = window.FindByName<GuiStatusbar>("sbar").Text;
throw exception;
}
return window;
}
/// <summary>
/// Shortcut for PA30 query
/// </summary>
/// <param name="session"></param>
/// <param name="employeeID"></param>
/// <param name="it">Infotype ID</param>
/// <param name="sty">Subtype ID</param>
/// <param name="asListView"></param>
/// <returns></returns>
public static GuiFrameWindow QueryPA30(this GuiSession session, int employeeID, string it, string sty = null)
{
var window = session.BeginTransaction("PA30", "Gérer données de base HR");
window.FindByName<GuiCTextField>("RP50G-PERNR").Text = employeeID.ToString();
window.FindByName<GuiCTextField>("RP50G-CHOIC").Text = it;
window.FindByName<GuiCTextField>("RP50G-SUBTY").Text = sty;
window.FindByName<GuiButton>("btn[6]").Press();
if (window.Text == "Gérer données de base HR")
{
var exception = new InvalidOperationException(string.Format("Failed to access to personal information of {0}", employeeID));
exception.Data["Employee ID"] = employeeID;
exception.Data["Infotype"] = it;
exception.Data["Subtype"] = sty;
exception.Data["Status Message"] = window.FindByName<GuiStatusbar>("sbar").Text;
throw exception;
}
return window;
}
/// <summary>
/// Start a new transaction and return the active window
/// </summary>
public static GuiFrameWindow BeginTransaction(this GuiSession session, string transactionID, string expectedTitle)
{
return session.BeginTransaction(transactionID,
x => x.Text == expectedTitle,
x =>
{
var exception = new InvalidOperationException(string.Format("Failed to open transaction : {0}", transactionID));
exception.Data["Transaction ID"] = transactionID;
exception.Data["Expected Title"] = expectedTitle;
exception.Data["Current Title"] = x.Text;
exception.Data["Status Message"] = x.FindByName<GuiStatusbar>("sbar").Text;
return exception;
});
}
public static GuiFrameWindow BeginTransaction(this GuiSession session, string transactionID, Predicate<GuiFrameWindow> validation, Func<GuiFrameWindow, string> errorFormatter)
{
return session.BeginTransactionImpl(transactionID, validation, x => new Exception(errorFormatter(x)));
}
public static GuiFrameWindow BeginTransaction(this GuiSession session, string transactionID, Predicate<GuiFrameWindow> validation, Func<GuiFrameWindow, Exception> errorBuilder)
{
return session.BeginTransactionImpl(transactionID, validation, errorBuilder);
}
private static GuiFrameWindow BeginTransactionImpl(this GuiSession session, string transactionID, Predicate<GuiFrameWindow> validation, Func<GuiFrameWindow, Exception> errorBuilder)
{
// force current transaction to end, preventing any blocking(eg: model dialog)
session.EndTransaction();
session.StartTransaction(transactionID);
var window = session.ActiveWindow;
if (!validation(window))
throw errorBuilder(window);
return window;
}
#endregion
#region GuiFrameWindow
public static TSapControl FindByName<TSapControl>(this GuiFrameWindow window, string name)
{
try
{
return (TSapControl)window.FindByName(name, typeof(TSapControl).Name);
}
catch (COMException e)
{
var writer = new StringWriter();
writer.WriteLine("The control could not be found by name and type.");
writer.WriteLine("Name : " + name);
writer.WriteLine("Type : " + typeof(TSapControl).Name);
throw new Exception(writer.ToString(), e);
}
}
#endregion
#region GuiTableControl
/// <summary>Note: Do not iterate through this ienumerable more than once</summary>
public static IEnumerable<GuiTableRow> AsEnumerable(this GuiTableControl table)
{
var container = table.Parent as dynamic;
string name = table.Name, type = table.Type;
int rowCount = table.VerticalScrollbar.Maximum;
Func<GuiTableControl> getTable = () => container.FindByName(name, type) as GuiTableControl;
for (int i = 0; i <= rowCount; i++)
{
getTable().VerticalScrollbar.Position = i;
yield return getTable().Rows.Item(0) as GuiTableRow;
}
}
public static TSapControl GetCell<TSapControl>(this GuiTableRow row, int column)
{
return (TSapControl)row.Item(column);
}
public static string GetCText(this GuiTableRow row, int column)
{
return row.GetCell<GuiCTextField>(column).Text;
}
public static string GetText(this GuiTableRow row, int column)
{
return row.GetCell<GuiTextField>(column).Text;
}
#endregion
}
}
#endregion
EDIT: The link to the LINQPad script mentioned in comment below no longer works. I have re-uploaded it here.
This script helps you to browse SAP GUI:
List control type, name, id, content
List properties and methods
Highlight control
Generate selector : .FindByName<GuiTableControl>("MP000000TC3000")
In case anyone wants to know how to get child elements of any gui component (this will simply write all ids to the console):
private void LoopAllElements(GuiComponentCollection nodes)
{
foreach (GuiComponent node in (GuiComponentCollection)nodes)
{
Console.WriteLine(node.Id);
if (node.ContainerType)
{
var children = ((node as dynamic).Children as GuiComponentCollection);
LoopAllElements(children);
}
}
}
This will loop through all child elements of any GUIComponentCollection you pass to it such as
LoopAllElements(session.Children)
There is probably a better linq way of doing this but this should get you started.
Please also check out this gist created by Xiaoy312 https://gist.github.com/Xiaoy312/a1424bd34acae92554105b27b0c80971
this has a lot of useful functions to debug your current sap session which i have found extremely useful.
You can either view the code as it has a number of functions you can use such as improved findbyid and findbyname or run it in linqpad https://www.linqpad.net/Download.aspx which will allow you to interogate the current SAP session.
If I understood correctly we have to manually start SAP Logon to make line below working:
var rot = new CSapROTWrapper().GetROTEntry("SAPGUI");
Am I right?
If so maybe someone can explain me why at the line:
var app = (GuiApplication)rot.GetType().InvokeMember("GetScriptingEngine", BindingFlags.InvokeMethod, null, rot, null);
I am getting exception:
An exception of type 'System.Runtime.InteropServices.COMException'
occurred in System.Dynamic.dll but was not handled in user code
Additional information: Error loading type library/DLL. (Exception
from HRESULT: 0x80029C4A (TYPE_E_CANTLOADLIBRARY))
I've already tried to change target platform from Any CPU to x86 and x64 and didn't have success in resolving this problem.
But when I run SAPGUI using following script:
var SapGuiApp = new GuiApplication();
string development = "Development ERP System";
var connection = SapGuiApp.OpenConnection(development);
instead of running SAP Logon manually described above problem is not reproducing.
But in this case I have other problem: GUI is different and for second option GUI looks corrupted - some of GUI controls are not displayed (please see screenshot). Please give me advice what can be wrong? Thank you in advance
UPDATE: I found solution that solves issue described below. You have to use object instead of var while you are trying to get session connection:
CSapROTWrapper sapROTWrapper = new CSapROTWrapper();
object rot = sapROTWrapper.GetROTEntry("SAPGUI");
object engine = rot.GetType().InvokeMember("GetScriptingEngine", System.Reflection.BindingFlags.InvokeMethod, null, rot, null);
GuiConnection connection = (engine as GuiApplication).OpenConnection(connectionName);
GuiSession session = connection.Children.ElementAt(0) as GuiSession;
I have a simple program that grab data from the database and stored it in a DataSet through DataAdapter.Fill().
The problem I am facing is that the program's memory size keep increasing. Using process explorer, I monitor the virtual size of the program. The processes are run in a standalne build(not in unity editor).
2mins after the process is launch, the program sits at 824,444K virtual size, but leaving the program running for 30 mins, the program's virtual size increased to 1,722,340K.
(can't upload screenshot) https://drive.google.com/open?id=0B0DwzunTEqfKcDhHcXRmV2twUEE
The program only consist of 2 simple script: A helper class SQLController which manage the loading from database to dataset and a monobehavior script DataProducer that poll the database at regular interval for "update" using SQLController object.
SQLController:
using UnityEngine;
using System.Collections;
using System.Data;
using System.Data.Sql;
using System.Data.SqlClient;
using UnityEngine.UI;
using System.Threading;
public class SQLController {
string connectionString;
string dataSource, catalog, uid, pwd;
DataSet dat_set;
public SQLController(string dataSource, string catalog, string uid, string pwd)
{
this.dataSource = dataSource;
this.catalog = catalog;
this.uid = uid;
this.pwd = pwd;
}
/// <summary>
/// Open a connectio to the database and query it for data with the statement input
/// </summary>
/// <param name="statement"> The query statement to be used to query the server</param>
/// <returns></returns>
public DataSet Load(string statement, string name)
{
connectionString = string.Format("data source={0};initial catalog={1};uid={2};pwd={3}", dataSource, catalog, uid, pwd);
using (SqlConnection dbcon = new SqlConnection(connectionString))
using (SqlDataAdapter dataAdapter = new SqlDataAdapter(statement, dbcon))
{
dat_set = new System.Data.DataSet();
dbcon.Open();
dataAdapter.Fill(dat_set, name);
}
return dat_set;
}
}
DataProducer
using UnityEngine;
using System.Collections;
using System.Threading;
using System.Data;
using UnityEngine.UI;
using SimpleJSON;
public class DataProducer : MonoBehaviour {
public GameObject textObj;
private string[] _configData;
private string _outputText;
private SQLController _sqlController;
private bool _toggle = true;
private bool _updating = false;
private DataSet _dataSetCache;
private Thread _dataGrabThread;
// Use this for initialization
void Start () {
_configData = new string[5];
if (LoadFromConfigFile())
{
StartCoroutine(LoadFromDB());
}
}
// Update is called once per frame
void Update () {
textObj.GetComponent<Text>().text = _outputText;
}
public void OnDisable()
{
// stop any running thread
if (null != _dataGrabThread && _dataGrabThread.IsAlive)
{
_dataGrabThread.Abort();
}
}
IEnumerator LoadFromDB()
{
while (true)
{
if (_updating)
{
Debug.Log("Data System Poll DataBase ignored");
}
else
{
_updating = true;
_dataGrabThread = new Thread(Load);
_dataGrabThread.IsBackground = true;
_dataGrabThread.Start();
}
yield return new WaitForSeconds(10f);
}
}
void Load()
{
string statement;
if (_toggle)
{
_toggle = !_toggle;
statement = "SELECT TOP 100000 [AIIDX],[LASTATTACKDATE],[LASTRECEIVEDDATE],[DEVICEID],[INSTANCES],[ATTACKTYPE],[SEVERITY],[STATUS] FROM AI (NOLOCK)";
}
else
{
_toggle = !_toggle;
statement = "SELECT TOP 100000 [AIIDX],[LASTATTACKDATE],[LASTRECEIVEDDATE],[DEVICEID],[SEVERITY],[STATUS] FROM AI (NOLOCK)";
}
_sqlController = new SQLController(_configData[0], _configData[1], _configData[2], _configData[3]);
_outputText = "Loading";
_dataSetCache = _sqlController.Load(statement, "TestObject");
PrintDataSet();
_updating = false;
}
/// <summary>
/// Convert datatable into string and print it out through a text object
/// </summary>
void PrintDataSet()
{
if (null == _dataSetCache)
{
return;
}
DataTable dt = _dataSetCache.Tables["TestObject"];
if (null == dt)
{
return;
}
System.Text.StringBuilder builder = new System.Text.StringBuilder();
for (int i = 0; i < 20; ++i)
{
builder.AppendFormat("{0,-5}", (i + 1) + ".");
//comp.text += string.Format("{0,-5}", (i + 1) + ".");
DataRow dr = dt.Rows[i];
for (int j = 0; j < dt.Columns.Count; ++j)
{
builder.AppendFormat("{0, -30}", dr[dt.Columns[j]].ToString());
}
builder.Append("\n");
}
_outputText = builder.ToString();
builder = null;
}
bool LoadFromConfigFile()
{
string line;
using (System.IO.StreamReader file = new System.IO.StreamReader("./config.txt"))
{
if (file == null)
{
return false;
}
int index = 0;
while ((line = file.ReadLine()) != null)
{
if (index > _configData.Length)
{
Debug.LogError("Invalid Config file");
return false;
}
_configData[index++] = line;
}
//if the config file does not consist of 5 data
if (index < _configData.Length)
{
Debug.LogError("Invalid Config file");
return false;
}
return true;
}
}
}
I am not very sure what exactly causes the memory leak but when I changed the loading from threaded
_dataGrabThread = new Thread(Load);
to running in the main thread,
Load()
the process virtual size, though still increment, but at a slower rate. From 828,312K at 2 min to 1,083,908K at 40mins.
I am trying to write a well-documented code that will be also easy to debug if some exception is handled. Therefore my methods throw exceptions and they're gathered in the log that I can browse. However, it is not really suitable to write complex messages when throwing a new exception. I would like to write a simple utility that would gather:
name of the current class
name of the current method
parameters of the method
Can this be achieved in some easy way? Also, is there a better way to make such logs of exceptions? Perhaps this issue is being solved in a different way, so please share your experience with me :)
An exception already contains this information in its StackTrace.
To get access to it, simply use the public getter:
catch(Exception ex)
{
string trace = ex.StackTrace;
}
If you want to log it, most loggers have an overload which takes an exception. The message and stack trace will be logged.
For example, with log4net, you can use the method void Error(object message, Exception t);:
catch(Exception ex)
{
logger.Error("More details", ex);
}
hi to get the name of the method with signatures the fix is
//get the stack trace
StackTrace stackTrace = new StackTrace();
//the count may vary
MethodBase CallingMethod = stackTrace.GetFrame(2).GetMethod();
//Method called by
string _signatures = MethodInfoExtensions.GetSignature((MethodInfo)CallingMethod));
and the class
public static class MethodInfoExtensions
{
/// <summary>
/// Return the method signature as a string.
/// </summary>
/// <param name="method">The Method</param>
/// <param name="callable">Return as an callable string(public void a(string b) would return a(b))</param>
/// <returns>Method signature</returns>
public static string GetSignature(this MethodInfo method, bool callable = false)
{
var firstParam = true;
var sigBuilder = new StringBuilder();
if (callable == false)
{
if (method.IsPublic)
sigBuilder.Append("public ");
else if (method.IsPrivate)
sigBuilder.Append("private ");
else if (method.IsAssembly)
sigBuilder.Append("internal ");
if (method.IsFamily)
sigBuilder.Append("protected ");
if (method.IsStatic)
sigBuilder.Append("static ");
sigBuilder.Append(TypeName(method.ReturnType));
sigBuilder.Append(' ');
}
sigBuilder.Append(method.Name);
// Add method generics
if (method.IsGenericMethod)
{
sigBuilder.Append("<");
foreach (var g in method.GetGenericArguments())
{
if (firstParam)
firstParam = false;
else
sigBuilder.Append(", ");
sigBuilder.Append(TypeName(g));
}
sigBuilder.Append(">");
}
sigBuilder.Append("(");
firstParam = true;
var secondParam = false;
foreach (var param in method.GetParameters())
{
if (firstParam)
{
firstParam = false;
if (method.IsDefined(typeof(System.Runtime.CompilerServices.ExtensionAttribute), false))
{
if (callable)
{
secondParam = true;
continue;
}
sigBuilder.Append("this ");
}
}
else if (secondParam == true)
secondParam = false;
else
sigBuilder.Append(", ");
if (param.ParameterType.IsByRef)
sigBuilder.Append("ref ");
else if (param.IsOut)
sigBuilder.Append("out ");
if (!callable)
{
sigBuilder.Append(TypeName(param.ParameterType));
sigBuilder.Append(' ');
}
sigBuilder.Append(param.Name);
}
sigBuilder.Append(")");
return sigBuilder.ToString();
}
/// <summary>
/// Get full type name with full namespace names
/// </summary>
/// <param name="type">Type. May be generic or nullable</param>
/// <returns>Full type name, fully qualified namespaces</returns>
public static string TypeName(Type type)
{
var nullableType = Nullable.GetUnderlyingType(type);
if (nullableType != null)
return nullableType.Name + "?";
if (!type.IsGenericType)
switch (type.Name)
{
case "String": return "string";
case "Int32": return "int";
case "Decimal": return "decimal";
case "Object": return "object";
case "Void": return "void";
default:
{
return string.IsNullOrWhiteSpace(type.FullName) ? type.Name : type.FullName;
}
}
var sb = new StringBuilder(type.Name.Substring(0,
type.Name.IndexOf('`'))
);
sb.Append('<');
var first = true;
foreach (var t in type.GetGenericArguments())
{
if (!first)
sb.Append(',');
sb.Append(TypeName(t));
first = false;
}
sb.Append('>');
return sb.ToString();
}
}
I am having an issue creating my (mostly) generic data access wrapper
Code
/// <summary>
/// Get the results of a stronly-typed IList Object
/// </summary>
/// <typeparam name="T">Strongly-Typed class of objects that should be returned</typeparam>
/// <param name="_DBType">The type of database to use</param>
/// <param name="_Qry">The query to run</param>
/// <param name="_QryType">The Query Type to run</param>
/// <param name="_ParamNames">The Parameters' names to pass to the query, if any</param>
/// <param name="_ParamVals">The Parameters' values to pass to the query, if any</param>
/// <param name="_ParamDTs">The Parameters' data types to pass to the query, if any</param>
/// <param name="_ShouldCache">Should we cache the response</param>
/// <param name="_CacheID">Cache item name</param>
/// <returns>Strongly Typed ilist of objects</returns>
public static IList<T> GetResults<T>(Enumerators.DatabaseTypes _DBType,
string _Qry,
CommandType _QryType,
string[] _ParamNames = null,
object[] _ParamVals = null,
Enumerators.DataTypes[] _ParamDTs = null,
bool _ShouldCache = false,
string _CacheID = "") where T : new()
{
// Create a reference to a potential already cached IList
IList<T> _CachedItem = _Cache.Get<IList<T>>(_CacheID);
// If we're already cached, there's no need to fire up the data access objects, so return the cached item instead
if (_CachedItem != null && _ShouldCache)
{
return _CachedItem;
}
else
{
// Fire up our data access object
switch (_DBType)
{
case Enumerators.DatabaseTypes.SqlServer:
SqlServer.Access db = new SqlServer.Access();
break;
case Enumerators.DatabaseTypes.SqlCE:
SqlCE.Access db = new SqlCE.Access();
break;
case Enumerators.DatabaseTypes.MySql:
MySql.Access db = new MySql.Access();
break;
case Enumerators.DatabaseTypes.OLE:
Ole.Access db = new Ole.Access();
break;
case Enumerators.DatabaseTypes.ODBC:
Odbc.Access db = new Odbc.Access();
break;
default:
db = null;
}
using (db)
{
try
{
// create a new ilist reference of our strongly typed class
IList<T> _Query = default(IList<T>);
// set the query type
db.QueryType = _QryType;
// set the query text
db.Query = _Qry;
// make sure we've got some parameters, if we do the set them to our db access object
if (_ParamNames != null)
{
// set the parameter names
db.ParameterNames = _ParamNames;
// set the parameter values
db.ParameterValues = _ParamVals;
// set the parameter data types
db.ParameterDataTypes = _ParamDTs;
}
// start using our db access :) Fire off the GetResults method and return back a SqlDataReader to work on
using (DbDataReader r = db.GetResults())
{
// make sure the data reader actually exists and contains some results
if (r != null)
{
// map the data reader to our strongly type(s)
_Query = Map<T>(r);
}
r.Close();
}
// check if we should cache the results
if (_ShouldCache)
{
// if so, set the query object to the cache
_Cache.Set<IList<T>>(_Query, _CacheID);
}
// return our strongly typed list
return _Query;
}
catch (DbException dEx)
{
// Catch an exception if any, an write it out to our logging mechanism, in addition to adding it our returnable message property
_Msg += "Wrapper.GetResults Exception: " + dEx.Message + db.Message;
ErrorReporting.WriteEm.WriteItem(dEx, "o7th.Class.Library.Data.Wrapper.GetResults", _Msg);
// make sure this method returns a default List
return default(IList<T>);
}
catch (Exception ex)
{
// Catch an exception if any, an write it out to our logging mechanism, in addition to adding it our returnable message property
_Msg += "Wrapper.GetResults Exception: " + ex.Message + db.Message;
ErrorReporting.WriteEm.WriteItem(ex, "o7th.Class.Library.Data.Wrapper.GetResults", _Msg);
// make sure this method returns a default List
return default(IList<T>);
}
}
}
}
Access Class - SqlServer
using System;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
namespace o7th.Class.Library.Data.SqlServer
{
internal class Access : IDisposable
{
#region "Properties"
// Set the type of query we are running
private CommandType _QT;
internal CommandType QueryType { set { _QT = value; } }
// Set the actual query text to run
private string _Qry;
internal string Query { set { _Qry = value; } }
// Set the parameter names if there are any
private string[] _PNs;
internal string[] ParameterNames { set { _PNs = value; } }
// Set the parameter values if there are any
private object[] _PVs;
internal object[] ParameterValues { set { _PVs = value; } }
// Set the actual Sql Data Types if there are any
private DataTypes[] _DTs;
internal DataTypes[] ParameterDataTypes { set { _DTs = value; } }
// Check to see if there are any parameters passed
private bool AreParams() {
// Check to see if the values and names are null first
if (_PVs != null && _PNs != null) {
try {
Type _t_pv = _PVs.GetType();
Type _t_pn = _PNs.GetType();
if (_t_pv.IsArray && _t_pn.IsArray) {
return (_PVs.Length > 0 && _PNs.Length > 0) ? true : false;
} else {
return false;
}
} catch {
// yes I meant to do this, we really don't need to get the exception here
return false;
}
} else {
return false;
}
}
// Get a return message if any
private string _Msg;
internal string Message { get { return _Msg; } }
// Set the official Sql Reader object
private SqlDataReader _Rdr;
// Set the official Sql Connection object
private SqlConnection _Conn;
// Set the official Sql Command object
private SqlCommand _Cmd;
// Hack for seeing if we're disposed already
private bool disposedValue;
#endregion
// Constructor
internal Access() {
Invoke();
}
// Official Constructor. We can thread these 2 becuase they are not being used yet, and it makes it slightly more efficient
internal void Invoke() {
try {
Parallel.Invoke(() => {
_Conn = new SqlConnection(AssemblyProperties.GetConnectionString());
}, () =>
{
_Cmd = new SqlCommand();
});
}
catch (SqlException dEx)
{
// Catch an exception if any, an write it out to our logging mechanism, in addition to adding it our returnable message property
_Msg = "Access.Invoke Exception: " + dEx.Message;
ErrorReporting.WriteEm.WriteItem(dEx, "o7th.Class.Library.Data.SqlServer.Access.Invoke", _Msg);
}
catch (Exception ex)
{
_Msg = "Access.Invoke Exception: " + ex.Message;
ErrorReporting.WriteEm.WriteItem(ex, "o7th.Class.Library.Data.SqlServer.Access.Invoke", _Msg);
}
}
/// <summary>
/// Return a SqlDataReader based on the properties passed to this class
/// </summary>
/// <returns></returns>
internal SqlDataReader GetResults()
{
try {
// check for parameters
if (AreParams()) {
PrepareParams(_Cmd);
}
// set our connection
_Cmd.Connection = _Conn;
// set the type of query to run
_Cmd.CommandType = _QT;
// set the actual query to run
_Cmd.CommandText = _Qry;
// open the connection
_Cmd.Connection.Open();
// prepare the command with any parameters that may have gotten added
_Cmd.Prepare();
// Execute the SqlDataReader, and set the connection to close once returned
_Rdr = _Cmd.ExecuteReader(CommandBehavior.CloseConnection);
// clear out any parameters
_Cmd.Parameters.Clear();
// return our reader object
return (!_Rdr.HasRows) ? null : _Rdr;
}
catch (SqlException SqlEx)
{
_Msg += "Acccess.GetResults SqlException: " + SqlEx.Message;
ErrorReporting.WriteEm.WriteItem(SqlEx, "o7th.Class.Library.Data.SqlServer.Access.GetResults", _Msg);
return null;
}
catch (Exception ex) {
_Msg += "Acccess.GetResults Exception: " + ex.Message;
ErrorReporting.WriteEm.WriteItem(ex, "o7th.Class.Library.Data.SqlServer.Access.GetResults", _Msg);
return null;
}
}
/// <summary>
/// Execute a non-return query, and return the success
/// </summary>
/// <returns></returns>
internal bool Execute() {
try {
// check for parameters
if (AreParams()) {
PrepareParams(_Cmd);
}
// set our connection
_Cmd.Connection = _Conn;
// set the type of query to run
_Cmd.CommandType = _QT;
// set the actual query to run
_Cmd.CommandText = _Qry;
// open the connection
_Cmd.Connection.Open();
// prepare the command with any parameters that may have gotten added
_Cmd.Prepare();
// execute the non-returnable query against the database
_Cmd.ExecuteNonQuery();
// clear out any parameters
_Cmd.Parameters.Clear();
// executed successfully (otherwise would have thrown an exception)
return true;
}
catch (SqlException SqlEx)
{
_Msg += "Access.Execute SqlException: " + SqlEx.Message;
ErrorReporting.WriteEm.WriteItem(SqlEx, "o7th.Class.Library.Data.SqlServer.Access.Execute", _Msg);
return false;
}
catch (Exception ex) {
_Msg += "Access.Execute Exception: " + ex.Message;
ErrorReporting.WriteEm.WriteItem(ex, "o7th.Class.Library.Data.SqlServer.Access.Execute", _Msg);
return false;
}
}
/// <summary>
/// Execute a query with a return value. Used in Selecting the ID of the last inserted record.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="_DefVal"></param>
/// <returns></returns>
internal T ExecuteWithReturn<T>(T _DefVal) {
try {
T _Ret;
// check for parameters
if (AreParams()) {
PrepareParams(_Cmd);
}
// set our connection
_Cmd.Connection = _Conn;
// set the type of query to run
_Cmd.CommandType = _QT;
// set the actual query to run
_Cmd.CommandText = _Qry;
// open the connection
_Cmd.Connection.Open();
// prepare the command with any parameters that may have gotten added
_Cmd.Prepare();
T _T = (T)_Cmd.ExecuteScalar();
_Ret = (_T is DBNull) ? default(T) : _T;
// clear out _T
_T = default(T);
// clear out any parameters
_Cmd.Parameters.Clear();
// return the single return value from the query run
return _Ret;
}
catch (SqlException SqlEx)
{
_Msg += "Access.ExecuteWithReturn SqlException: " + SqlEx.Message;
ErrorReporting.WriteEm.WriteItem(SqlEx, "o7th.Class.Library.Data.SqlServer.Access.ExecuteWithReturn", _Msg);
return default(T);
} catch (Exception ex) {
_Msg += "Access.ExecuteWithReturn Exception: " + ex.Message;
ErrorReporting.WriteEm.WriteItem(ex, "o7th.Class.Library.Data.SqlServer.Access.ExecuteWithReturn", _Msg);
return default(T);
}
}
/// <summary>
/// Prepare our parameters, adding them and forcing a valid data length
/// </summary>
/// <param name="objCmd"></param>
protected void PrepareParams(SqlCommand objCmd) {
try {
// set our initial Data Size
int _DataSize = 0;
// get the number of Parameter Values passed in
int _PCt = _PVs.GetUpperBound(0);
// begin array check
Type _t_dt = _DTs.GetType();
// start looping over our parameters
for (int i = 0; i <= _PCt; ++i) {
// make sure that the data types are actually an array
if (_t_dt.IsArray) {
// select which datatype, and force the official size
switch ((int)_DTs[i]) {
case 0:
case 33:
case 6:
case 9:
case 13:
case 19:
_DataSize = 8;
break;
case 1:
case 3:
case 7:
case 10:
case 12:
case 21:
case 22:
case 23:
case 25:
_DataSize = _PVs[i].ToString().Length;
break;
case 2:
case 20:
_DataSize = 1;
break;
case 5:
_DataSize = 17;
break;
case 8:
case 17:
case 15:
_DataSize = 4;
break;
case 14:
_DataSize = 16;
break;
case 31:
_DataSize = 3;
break;
case 32:
_DataSize = 5;
break;
case 16:
_DataSize = 2;
break;
}
// add our parameter to the command object
objCmd.Parameters.Add(_PNs[i], (SqlDbType)_DTs[i], _DataSize).Value = _PVs[i];
} else {
// if the datatypes were not set, try to add them generically
objCmd.Parameters.AddWithValue(_PNs[i], _PVs[i]);
}
}
// clean up
_PNs = null;_PVs = null;_DTs = null;
} catch (Exception ex) {
_Msg += "Access.PrepareParams Exception: " + ex.Message;
ErrorReporting.WriteEm.WriteItem(ex, "o7th.Class.Library.Data.SqlServer.Access.PrepareParams", _Msg);
}
}
#region "Dispose Support"
protected virtual void Dispose(bool disposing)
{
if (!disposedValue && disposing) {
try
{
_Qry = string.Empty;
_Rdr.Close();
_Rdr.Dispose();
_Cmd.Connection.Close();
_Cmd.Dispose();
if (_Conn.State == ConnectionState.Open)
{
SqlConnection.ClearAllPools();
_Conn.Close();
_Conn.Dispose();
}
_Msg = null;
}
catch(Exception ex) {
ErrorReporting.WriteEm.WriteItem(ex, "o7th.Class.Library.Data.SqlServer.Access.Dispose", "");
}
}
disposedValue = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}
The issue I am having is, because I need the switch statement to select which database Access method should be used, it is throwing The name 'db' does not exist in the current context errors all over the place.
I've also attempted to var db; prior to the switch statement with the Implicitly-typed local variables error.
I will not know which type of database will be used until it is time to actually use this, so how can I make sure this works?
IDbAccess db;
switch (_DBType)
{
case Enumerators.DatabaseTypes.SqlServer:
SqlServer.Access db = new SqlServer.Access();
break;
case Enumerators.DatabaseTypes.SqlCE:
SqlCE.Access db = new SqlCE.Access();
break;
case Enumerators.DatabaseTypes.MySql:
MySql.Access db = new MySql.Access();
break;
case Enumerators.DatabaseTypes.OLE:
Ole.Access db = new Ole.Access();
break;
case Enumerators.DatabaseTypes.ODBC:
Odbc.Access db = new Odbc.Access();
break;
default:
db = null;
}
is producing Cannot implicitly convert type 'SqlCE.Access to SqlServer.Access' errors down the line, as well as A local variable 'db' is already defined in this scope and A local variable named 'db' cannot be declared in this scope because it would give a different meaning to 'db' which is already used in a parent of current scope to denote something else
All of those various database providers should have some sort of base class they all inherit from or an interface that they all implement. That base class/interface should have all of the operations that you use later on in the method. Create a variable of that base type/interface before the switch and then assign that variable a value in each case.