I'm having class Report and class Program, what I want to accomplish (no luck so far) is to send data from class Program and method SaveRep() to class Report method Save() and save it in this method.
I apologize if the question is badly formulated, I am really stuck at this, please help. Thanks
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Configuration;
using System.Data.SqlClient;
namespace Application
{
class Program
{
static void Main(string[] args)
{
//call method SaveRep
}
public void SaveRep(...)
{
int RepID = 1;
string Data1 = "SomeData1"
string Data2 = "SomeData2"
//This method should send the data above to method Save() in Report class
//data to this method will be provided from another method.
}
}
public class Report
{
private static int _repID;
public int RepID
{
get { return _repID; }
set { _repID = value; }
}
private static string _data1;
public string Data1
{
get { return _data1; }
set { _data1 = value; }
}
private static string __data2;
public string Data1
{
get { return _data2; }
set { _data2 = value; }
}
public void Save()
{
string strConnectionString = (#"server=(local)\sqlexpress;Integrated Security=True;database=DataBase");
SqlConnection connection = new SqlConnection(strConnectionString);
connection.Open();
// This method should save all data (RepID,Data1,Data2)
// to the DB, provided to her by SaveRep method from Program class.
// Properties are columns from a database
}
}
}
public void Save()
{
string yourConnString="Replace with Your Database ConnectionString";
using(SqlConnection connection = new SqlConnection(yourConnString))
{
string sqlStatement = "INSERT Table1(Data1) VALUES(#Data1)";
using(SqlCommand cmd = new SqlCommand(sqlStatement, connection))
{
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("#Data1",Data1);
//add more parameters as needed and update insert query
try
{
connection.Open();
cmd.ExecuteNonQuery();
}
catch(Exception ex)
{
//log error
}
}
}
}
Assuming your Data1 is the name of your Column name. Update the sqlStatement to have your relevant columns.
Now in SaveRep, you can call it like
public void SaveRep()
{
Report objReport=new Report();
objReport.Data1="Something somethinng";
objReport.Save();
}
string connectionString = ....; //adjust your connection string
string queryString = "INSERT INTO ....;"; //Adjust your query
using (SqlConnection connection = new SqlConnection(
connectionString))
{
using( SqlCommand command = new SqlCommand(
queryString, connection))
{
connection.Open();
command .ExecuteNonQuery();
}
}
One tiny thing that the previous speakers have not noticed, is that your Report class is severely broken. Why did you use static keywords on the variables? Do not do that! Well, unless you know what it does, and here I have the feeling you don't. Update your Report class to look like this:
public class Report
{
public int RepID {get;set;}
public string Data1 {get;set;}
public string Data2 {get;set;}
public void Save()
{
// what others said
}
}
'static' in your original code makes all te variables global/shared. That means that if you'd create 500 Report objects with new, they all would have the same one ID, Data1 and Data2. Any change to any single of those 500 objects would cause all the objects to change. But it's be misleading, the objects would not change. They would simply have the same data. Looking at the "ID" field, I think you'd rather want a separate objects with separate data for separate records..
Related
I have created a small ETL program with a single method that only takes a connection string for the source database and a connection string for a target database.
Basically, it has 5 steps:
Step 1. Select data from the source.
Step 2. Create a temporary table on the target.
Step 3. Load data from the source into the temporary on the target.
Step 4. Merge the data from temporary table into the actual target table.
Step 5. Drop the temporary table
This works great for a single transformation that needs to take place, but I have about 20 different ETL "jobs" that need to take place.
So instead of copying and pasting the same method verbatim 19 different times, I would like to define a base class that defines this single method one time, and then call this single method from each child class, with its own select, create, merge and drop statements.
Is this possible?
Base Class:
public class ETLBase
{
private static string Select;
private static string CreateTemp;
private static string Merge;
private static string CleanUp;
private static string DestinationTable;
public static void ExecuteJob(string sourceConnectionString, string destinationConnectionString)
{
using (OracleConnection sourceConnection = new OracleConnection(sourceConnectionString))
{
sourceConnection.Open();
OracleCommand selectCommand = new OracleCommand(Select, sourceConnection);
OracleDataReader reader = selectCommand.ExecuteReader();
using (SqlConnection destinationConnection = new SqlConnection(destinationConnectionString))
{
destinationConnection.Open();
SqlCommand createTempCommand = new SqlCommand(CreateTemp, destinationConnection);
createTempCommand.ExecuteNonQuery();
SqlCommand mergeCommand = new SqlCommand(Merge, destinationConnection);
SqlCommand dropCommand = new SqlCommand(CleanUp, destinationConnection);
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection))
{
bulkCopy.DestinationTableName = DestinationTable;
try
{
bulkCopy.WriteToServer(reader);
mergeCommand.ExecuteNonQuery();
dropCommand.ExecuteNonQuery();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
reader.Close();
}
}
}
}
}
}
Child Class:
public class ChildETL: ETLBase
{
private static string Select = #"Select THIS DataStatement";
private static string CreateTemp = #"CREATE TABLE Statement";
private static string Merge = #"Merge Table statement";
private static string CleanUp = "DROP TABLE Statement";
private static string DestinationTable = "##TempTable";
}
And then execute it something like this, but where each child class uses its own defined fields, so it uses it's own SQL statements.
public class Program
{
public static void Main(string[] args)
{
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.Build();
string schoolDBConnection = config["SchoolConnection"];
string courseDBConnection = config["CourseConnection"];
string teacherDBConnection = config["Connection"];
ChildETLA.ExecuteJob(schoolDBConnection, courseDBConnection);
ChildETLB.ExecuteJob(teacherDBConnection, courseDBConnection);
//...and so on for each child ETL class
}
}
First of all, you need to run "ExecuteJob()" polymorphically, which means behaviour of "ExeceuteJob" will be different based on source and destination connection string. You can't achieve polymorphism for static function or properties. First of all, you need to refactor it to instance method by taking out the "Static" keywords. Also, the behaviour of the base class is deciding by child classes so that nobody should be able to create an object of base class to make it an abstract class. Look at your code you have two behaviours one for school and another one for the teacher. So you have to create two different child classes which inherit the abstract base class. It is the responsibility of object creation to compose object with source and destination connection string so pass it to the constructor and set it while creating the object itself. Please find the refactored code,
public abstract class ETLBase
{
private readonly string sourceConnectionString;
private readonly string destinationConnectionString;
protected virtual string Select { get; set; } = #"Select THIS DataStatement";
protected virtual string CreateTemp { get; set; } = #"CREATE TABLE Statement";
protected virtual string Merge { get; set; } = #"Merge Table statement";
protected virtual string CleanUp { get; set; } = "DROP TABLE Statement";
protected virtual string DestinationTable { get; set; } = "##TempTable";
protected ETLBase(string sourceConnectionString, string destinationConnectionString)
{
this.sourceConnectionString = sourceConnectionString;
this.destinationConnectionString = destinationConnectionString;
}
public void ExecuteJob()
{
using (OracleConnection sourceConnection = new OracleConnection(sourceConnectionString))
{
sourceConnection.Open();
OracleCommand selectCommand = new OracleCommand(Select, sourceConnection);
OracleDataReader reader = selectCommand.ExecuteReader();
using (SqlConnection destinationConnection = new SqlConnection(destinationConnectionString))
{
destinationConnection.Open();
SqlCommand createTempCommand = new SqlCommand(CreateTemp, destinationConnection);
createTempCommand.ExecuteNonQuery();
SqlCommand mergeCommand = new SqlCommand(Merge, destinationConnection);
SqlCommand dropCommand = new SqlCommand(CleanUp, destinationConnection);
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection))
{
bulkCopy.DestinationTableName = DestinationTable;
try
{
bulkCopy.WriteToServer(reader);
mergeCommand.ExecuteNonQuery();
dropCommand.ExecuteNonQuery();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
reader.Close();
}
}
}
}
}
And the child classes,
public class ChildETLSchool : ETLBase
{
public ChildETLSchool(string sourceConnectionString, string destinationConnectionString)
: base(sourceConnectionString, destinationConnectionString)
{
//Change values of below lines only if you want to override the values
//Select = #"Select THIS DataStatement";
//CreateTemp = #"CREATE TABLE Statement";
//Merge = #"Merge Table statement";
//CleanUp = "DROP TABLE Statement";
//DestinationTable = "##TempTable";
}
}
public class ChildETLTeacher : ETLBase
{
public ChildETLTeacher(string sourceConnectionString, string destinationConnectionString)
: base(sourceConnectionString, destinationConnectionString)
{
//Change values of below lines only if you want to override the values
//Select = #"Select THIS DataStatement";
//CreateTemp = #"CREATE TABLE Statement";
//Merge = #"Merge Table statement";
//CleanUp = "DROP TABLE Statement";
//DestinationTable = "##TempTable";
}
}
And the object creation in main function,
static void Main(string[] args)
{
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.Build();
string schoolDBConnection = config["SchoolConnection"];
string courseDBConnection = config["CourseConnection"];
string teacherDBConnection = config["Connection"];
var childETLSchool = new ChildETLSchool(schoolDBConnection, courseDBConnection);
var childETLTeacher = new ChildETLTeacher(teacherDBConnection, courseDBConnection);
childETLSchool.ExecuteJob();
childETLTeacher.ExecuteJob();
}
Small brief of what i need and what i currently have
I connect to a database and get my data from it and i get ( Name , LongNumber) and basically i have an event (action) that fires when the event occurs (this action gives me a LongNumber aswell).
I need to compare the LongNumbers between the one i got from the event (action) and the one from my database, and if they are similar i take the name and use it.
for example Hello, Alex ( alex is taken from the database)
Issue
I get Index out of range, and using my logic all texts will change what i'm trying to achieve is to change the text to the name of the person that got the Long number the same as the longNumber from the event
Code: Gettings Data from the database
using UnityEngine;
using System.Collections;
using MySql.Data.MySqlClient;
using System;
using System.Linq;
using System.Collections.Generic;
public class MySqlTestScript : MonoBehaviour
{
private static MySqlTestScript _instnace;
public static MySqlTestScript sharedInstance()
{
return _instnace;
}
string row = "";
public string host = "*****";
public string database = "*******";
public string usrename= "*******";
public string password = "******";
public List<Data> userData = new List<Data>();
Data data;
void Awake()
{
_instnace = this;
}
// Use this for initialization
public void Start()
{
GetDataFromDatabase();
}
public string GetDataFromDatabase()
{
string myConnectionString = "Server="+host+";Database="+database+";Uid="+usrename+ ";Pwd="+password+";";
MySqlConnection connection = new MySqlConnection(myConnectionString);
MySqlCommand command = connection.CreateCommand();
command.CommandText = "SELECT * FROM Users";
MySqlDataReader Reader;
try
{
connection.Open();
Reader = command.ExecuteReader();
while (Reader.Read())
{
for (int i = 0; i < Reader.FieldCount; i++)
{
//rfid_tags.Add (Reader.GetString("UserName"));
//rfid_tags.Add(Reader.GetString("RFID_Tag"));
data = new Data(Reader.GetString("UserName"), Reader.GetString("RFID_Tag"));
userData.Add(data);
// ws.DomainUrl = reader.GetString("DomainUrl");
// rfid_tags.Add(Reader.GetValue(i).ToString() + ",");
// row += Reader.GetValue(i).ToString() + ", ";
}
Debug.Log(row);
}
}
catch (Exception x)
{
Debug.Log(x.Message);
return x.Message;
}
connection.Close();
return row;
}
}
public class Data {
public string username { get; set; }
public string rfid { get; set; }
public Data(string _name, string _rfid)
{
username = _name;
rfid = _rfid;
}
public void SetUserName(string _userName) { username = _userName; }
public void SetRFID(string _rfid) { rfid = _rfid; }
}
Code for Comparing the values from the event(action) and showing the text
void OnTagsReported(ImpinjReader sender, TagReport report)
{
Debug.Log("OnTagsReported");
// This event handler is called asynchronously
// when tag reports are available.
// Loop through each tag in the report
// and print the data.
foreach (Tag tag in report)
{
Debug.Log(tag.Epc);
// Debug.Log(MySqlTestScript.sharedInstance().rfid_tags[0]);
Debug.Log("STEP ONE");
for (int i = 0; i < MySqlTestScript.sharedInstance().userData.Count; i++)
{
Debug.Log("STEP TWO");
if (tag.Epc.ToString().Trim() == MySqlTestScript.sharedInstance().userData[i].rfid)
{
Debug.Log("STEP THREE");
// TODO References the Name
Loom.QueueOnMainThread(() => {
namesTxt[i].text = MySqlTestScript.sharedInstance().userData[i].username;
});
}
}
As you can see in the Event script it's so unflexible and it gives index out of range if my database has less than 6 rows. I need to make it more friendly and generic
Any help would be appreciated and i hope my question is clear Thank you :)!
This should make more sense:
Database:
using UnityEngine;
using System.Collections;
using MySql.Data.MySqlClient;
using System;
using System.Linq;
using System.Collections.Generic;
public class MySqlTestScript : MonoBehaviour
{
private static MySqlTestScript _instnace;
public static MySqlTestScript sharedInstance()
{
return _instnace;
}
string row = "";
public string host = "*****";
public string database = "*******";
public string usrename= "*******";
public string password = "******";
public List<AppUser> users = new List<AppUser>();
void Awake()
{
_instnace = this;
}
// Use this for initialization
public void Start()
{
GetDataFromDatabase();
}
public string GetDataFromDatabase()
{
string myConnectionString = "Server=" + host + ";Database=" + database + ";Uid=" + usrename + ";Pwd=" + password + ";";
MySqlConnection connection = new MySqlConnection(myConnectionString);
MySqlCommand command = connection.CreateCommand();
command.CommandText = "SELECT * FROM users";
MySqlDataReader Reader;
try
{
connection.Open();
Reader = command.ExecuteReader();
while (Reader.Read())
{
users.Add(new AppUser() {
username = Reader.GetString("UserName").Trim(),
rfid = Reader.GetString("longNumbers ").Trim()
});
}
}
catch (Exception x)
{
Debug.Log(x.Message);
return x.Message;
}
connection.Close();
return row;
}
}
Data object:
public Class AppUser
{
public string rfid { get; set; }
public string username { get; set; }
}
Event/data comparison:
void OnTagsReported(ImpinjReader sender, TagReport report)
{
Debug.Log("OnTagsReported");
// This event handler is called asynchronously
// when tag reports are available.
// Loop through each tag in the report
// and print the data.
foreach (Tag tag in report)
{
Debug.Log(tag.Epc);
List<AppUser> appUsers = MySqlTestScript.sharedInstance().users;
int numUsers = appUsers.Count;
Debug.Log(numUsers);
for (int i = 0; i < numUsers; i++)
{
if (tag.Epc.ToString().Trim() == appUsers[i].rfid)
{
// TODO References the Name
Loom.QueueOnMainThread(() => {
if (i < namesTxt.Count) namesTxt[i].Text = appUsers[i].username; //assumes textnames is a "List" of textboxes and is already populated with instances of text1, text2 text3 etc. The if is just to guard against there being more rows in the DB than textboxes
});
}
}
}
}
One way to get around this is to wrap each of your cases in another if statement to check if the database has that many rows
if (MySqlTestScript.sharedInstance().longNumbers.Length > 1) {
if (tag.Epc.ToString().Trim() == MySqlTestScript.sharedInstance().longNumbers[0].ToString()) {
if(MySqlTestScript.sharedInstance().userNames[0] != null)
txt1.text = "Hello "+ MySqlTestScript.sharedInstance().userNames[0];
}
}
}
else {
txt1.text = "";
}
This will avoid your index out of range exception. In each wrapping case you will need to increment the comparison to be 1, 2, 3, etc.
It is not the prettiest solution but will avoid the exception. Another option would be to wrap in a try-catch block. Again not the prettiest but would accomplish the task.
Good day!
Using visual studio 2012, I have created a Student class with get and set codes, and i need to complete the StudentDAO class to create insert coding that will use to store data to database student table. this action is perform by a windows form button click event.
what i need to create a button click code and then insert into database code,
//Student.cs class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SRSJason
{
class Student
{
private string S_Student_id;
private string S_Full_name;
private DateTime S_Dob;
private string S_Address;
private int S_Contact;
private string S_Username;
private string S_Password;
public Student() //Default constructor
{
}
public Student(string Student_id, string Full_name, DateTime Dob, string Address, int Contact, string Username, string Password) //Overloadign
{
S_Student_id = Student_id;
S_Full_name = Full_name;
S_Dob = Dob;
S_Address = Address;
S_Contact = Contact;
S_Username = Username;
S_Password = Password;
}
public void setID(string Student_id)
{
S_Student_id = Student_id;
}
public string getID()
{
return S_Student_id;
}
public void setName(string Full_name)
{
S_Full_name = Full_name;
}
public string getName()
{
return S_Full_name;
}
public void setDob(DateTime Dob)
{
S_Dob = Dob;
}
public DateTime getDob()
{
return S_Dob;
}
public void setAddress(string Address)
{
S_Address = Address;
}
public string getAddress()
{
return S_Address;
}
public void setContact(int Contact)
{
S_Contact = Contact;
}
public int getContact()
{
return S_Contact;
}
public void setUsername(string Username)
{
S_Username = Username;
}
public string getUsername()
{
return S_Username;
}
public void setPassword(string Password)
{
S_Password = Password;
}
public string getPassword()
{
return S_Password;
}
}
}`
//StudentDAO class (please help me to complete this code)
`class StudentDAO
{
static string constring = "Data Source=JAZE;Initial Catalog=srsjason;Integrated Security=True";
SqlConnection m_con = new SqlConnection(constring);
}`
//button click from the form (please help me to complete this code as well)
private void submitstudent(object sender, EventArgs e)
{
}
please help me to complete this coding
First of all when you create private properties u cant access them in your forms, you have to create a method instead inside the same class then use it in your form. second you should know about the ORM - Object Relational Mapping that you are using.
Here I'll list them:
ADO.NET
LINQ to SQL
ADO.NET Entity Framework
When you picked one of those. next step would be learning about how they work and whats the syntax.
However knowing that you kinda showed a syntax of ADO.NET Here is an Example how you can insert the data in your data base using ADO.NET. If you want to add data directly from code-behind without a method. so basically on click event of your button.
private void btn_Click(object sender, EventArgs e)
{
try
{
//create object of Connection Class..................
SqlConnection con = new SqlConnection();
// Set Connection String property of Connection object..................
con.ConnectionString = "Data Source=KUSH-PC;Initial Catalog=test;Integrated Security=True";
// Open Connection..................
con.Open();
//Create object of Command Class................
SqlCommand cmd = new SqlCommand();
//set Connection Property of Command object.............
cmd.Connection = con;
//Set Command type of command object
//1.StoredProcedure
//2.TableDirect
//3.Text (By Default)
cmd.CommandType = CommandType.Text;
//Set Command text Property of command object.........
cmd.CommandText = "Insert into Registration (Username, password) values ('#user','#pass')";
//Assign values as `parameter`. It avoids `SQL Injection`
cmd.Parameters.AddWithValue("user", TextBox1.text);
cmd.Parameters.AddWithValue("pass", TextBox2.text);
//Execute command by calling following method................
//1.ExecuteNonQuery()
//This is used for insert,delete,update command...........
//2.ExecuteScalar()
//This returns a single value .........(used only for select command)
//3.ExecuteReader()
//Return one or more than one record.
cmd.ExecuteNonQuery();
con.Close();
MessageBox.Show("Data Saved");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
con.Close();
}
}
Be sure that you've included your ConnectionString in your config file.
According to MSDN documentation a LINQ query is not executed before iterated over in a foreach loop.
But when I try the following:
namespace MCSD487_AdoConnection
{
class Program
{
static void Main(string[] args)
{
DataSet dataSet = new DataSet();
dataSet.Locale = CultureInfo.InvariantCulture;
FillDataSet(dataSet);
DataTable folders = dataSet.Tables["Folder"];
IEnumerable<DataRow> folderQuery = folders.AsEnumerable();
IEnumerable<DataRow> aFolders = folderQuery.Where(f => f.Field<string>("Name")[0].ToString().ToLower() == "a");
// this is where I thought the SQL execution whould happen
foreach (DataRow row in aFolders)
{
Console.WriteLine("{0} was created on {1}", row.Field<string>("Name"), row.Field<DateTime>("DateTime"));
}
Console.ReadLine();
}
internal static void FillDataSet(DataSet dataSet)
{
try
{
string connectionString = ConfigurationManager.ConnectionStrings["conn"].ConnectionString;
SqlDataAdapter dataAdapter = new SqlDataAdapter("SELECT DateTime, Name FROM Folder", connectionString);
// Add table mappings.
dataAdapter.TableMappings.Add("Table", "Folder");
dataAdapter.Fill(dataSet);
// Fill the DataSet.
// This it where the actual SQL executes
dataAdapter.Fill(dataSet);
}
catch (SqlException ex)
{
Console.WriteLine("SQL exception occurred: " + ex.Message);
}
}
}
}
and I look at my SQL Server Profiler, I can see that the actual SQL call is performed when I call the dataAdapter.Fill(dataSet) in the FillDataSet method and not when iterating through rows.
My question is: How do I make LINQ perform the SQL execution on only the names starting with 'a' (without specifying that in the SQL commandText in the FillDataSet method)?
EDIT 2013-07-07 23:44:
I ended with the following solution, based on Evan Harpers answer:
using System;
using System.Data.SqlClient;
using System.Linq;
using System.Data.Linq;
using System.Data.Linq.Mapping;
namespace MCSD487_AdoConnection
{
[Table(Name = "Folder")]
public class Folder
{
private int _Id;
[Column(IsPrimaryKey = true, Storage = "_Id")]
public int Id
{
get { return _Id; }
set { _Id = value; }
}
private DateTime _DateTime;
[Column(Storage = "_DateTime")]
public DateTime DateTime
{
get { return _DateTime; }
set { _DateTime = value; }
}
private string _Name;
[Column(Storage = "_Name")]
public string Name
{
get { return _Name; }
set { _Name = value; }
}
}
class Program
{
static void Main(string[] args)
{
DataContext db = new DataContext(new SqlConnection(#"Data Source=OLF\OLF;Initial Catalog=DirStructure;Integrated Security=True"));
Table<Folder> folders = db.GetTable<Folder>();
IQueryable<Folder> folderQuery = from folder in folders
where folder.Name[0].ToString().ToLower() == "a"
select folder;
foreach (Folder folder in folderQuery)
{
Console.WriteLine("{0} was created on {1}", folder.Name, folder.DateTime);
}
Console.ReadLine();
}
}
}
Thanks for guiding me in the right direction.
This is exactly the expected behavior. You are using ADO.NET to get the data into a DataTable, and then querying against the DataTable – not the underlying database – in line with ADO.NET's disconnected architecture.
If you want to see your LINQ queries turned into optimized database calls just-in-time, use something like LINQ to SQL.
With DataSet you can'T do it. The only way is to fill the DataSet/Table adapter with the limited results (the starts with stuff have to be written in SQL).
I'd like to implement N-tier architecture in my WinForms applications to separate (just logically - in one project) business logic from data access, however I have some doubts about using transacion in BLL. All tutorials I've found in the Internet either are very simple implementations of that architecture (without transactions), or are too complex for my needs. Trying to find my own way, I've come to the point, where I don't know the best way to handle transactions of in BLL layer.
I'll try to use some simple example to illustrate the problem (all classes are in separate files):
//DTO - Data Transfer Objects
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
public class SomeOtherItem
{
public int Id { get; set; }
public string Name { get; set; }
}
//DAL - Data Access layer
public class ItemDAL
{
public ItemDAL()
{
}
public void Add(Item item)
{
using (NpgsqlConnection conn = new NpgsqlConnection(connString))
{
conn.Open();
using (NpgsqlCommand cmd = new NpgsqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = #"INSERT INTO tbl_items (name)
VALUES (#name)";
cmd.Parameters.AddWithValue("#name", item.Name);
cmd.ExecuteNonQuery();
}
}
}
}
public class SomeOtherItemDAL
{
public SomeOtherItemDAL()
{
}
public void Add(SomeOtherItem someOtherItem)
{
using (NpgsqlConnection conn = new NpgsqlConnection(connString))
{
conn.Open();
using (NpgsqlCommand cmd = new NpgsqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = #"INSERT INTO tbl_some_other_items (name)
VALUES (#name)";
cmd.Parameters.AddWithValue("#name", someOtherItem.Name);
cmd.ExecuteNonQuery();
}
}
}
}
//BLL - Business Logic Layer
public class SomeBLL
{
public SomeBLL()
{
}
public void Add(Item item, SomeOtherItem someOtherItem)
{
ItemDAL itemDAL = new ItemDAL();
SomeOtherItemDAL someOtherItemDAL = new SomeOtherItemDAL();
// *** this must be done in one transaction ***
itemDAL.Add(item);
someOtherItemDAL.Add(someOtherItem);
}
}
Now, the problem is that if I want to use Transacion, I cannot use:
using (NpgsqlConnection conn = new NpgsqlConnection(connString))
in DAL. To use NpgsqlTransacion object I must somehow keep connection opened and visible in both DAL classes.
I've tried use TransacionScope object for that, but from from some reasons it's not working with PostgreSQL and the driver I'm using (INSERTS are done just after executed and there is no transaction rollback when exception within TransacionScope occures).
What I've come into is to make additional Singleton class to keep connection alive and manage transactions:
public class DB
{
private static DB instance;
private const string connString = #"Server=localhost;Port=5432;Database=db_test;User Id=usr_test;Password=pass";
private NpgsqlConnection conn;
private DB()
{
conn = new NpgsqlConnection(connString);
}
public static DB Instance
{
get
{
if (instance == null)
{
instance = new DB();
}
return instance;
}
}
#region --- connection ---
public NpgsqlConnection GetOpenConnection()
{
OpenConnection();
return conn;
}
private void OpenConnection()
{
if (conn.State == ConnectionState.Closed || conn.State == ConnectionState.Broken)
conn.Open();
}
public void CloseConnection()
{
if (conn != null && !inTransaction)
{
conn.Close();
}
}
#endregion
#region --- transaction ---
private NpgsqlTransaction trans;
private bool inTransaction;
public bool InTransaction { get { return inTransaction; } }
public void TransactionStart()
{
OpenConnection();
trans = conn.BeginTransaction();
inTransaction = true;
}
public void TransactionCommit()
{
if (inTransaction)
{
try
{
trans.Commit();
trans.Dispose();
}
finally
{
inTransaction = false;
CloseConnection();
}
}
}
public void TransactionRollback()
{
if (inTransaction)
{
try
{
trans.Rollback();
trans.Dispose();
}
finally
{
inTransaction = false;
CloseConnection();
}
}
}
#endregion
}
and rebuild both DAL Add methods to access connection like that:
//DAL - Data Access layer
public class ItemDAL
{
public ItemDAL()
{
}
public void Add(Item item)
{
using (NpgsqlCommand cmd = new NpgsqlCommand())
{
cmd.Connection = DB.Instance.GetOpenConnection();
cmd.CommandText = #"INSERT INTO tbl_items (name)
VALUES (#name)";
cmd.Parameters.AddWithValue("#name", item.Name);
cmd.ExecuteNonQuery();
}
if (!DB.Instance.InTransaction)
DB.Instance.CloseConnection();
}
}
public class SomeOtherItemDAL
{
public SomeOtherItemDAL()
{
}
public void Add(SomeOtherItem someOtherItem)
{
using (NpgsqlCommand cmd = new NpgsqlCommand())
{
cmd.Connection = DB.Instance.GetOpenConnection();
cmd.CommandText = #"INSERT INTO tbl_some_other_items (name)
VALUES (#name)";
cmd.Parameters.AddWithValue("#name", someOtherItem.Name);
cmd.ExecuteNonQuery();
}
if (!DB.Instance.InTransaction)
DB.Instance.CloseConnection();
}
}
Note that I'd like to follow the rule to "close database connection as soon as possible", so when Add method is called without transaction scope, I'd like it to close the connection.
So, the final questions are:
1. What do you think about it, is there a better way to handle that issue, any suggestions?
2. Should I dispose a connection in DB.CloseConnecion()? I surely do when using using (NpgsqlConnection conn = ...) { ... } pattern, but as Singleton is alive as long as application, does it make sense? Connection is returned to ConnectionPool after Close(), isn't it? Or maybe I should also dispose a Singleton object (together with connection), after each using?
3. It's not directly connected question, but if I use DTO objects (just properties, no methods) and have also some BusinessObjects (BO) with the same properties, but also with additional methods (validations, calculations, operations etc.), can it be inherited from DTO? Or maybe I can use full BusinessObject to transfer it between layers, and get rid off DTO?
EDIT: TransacionScope
As requested, I add some code from my tries with TransactionScope. Simply WinForm application, no Exceptions handling. As a result, there is an Exception window when I throw it, but in database I see records with values test1 ans test2. Both when debbuging in VS and executing application from .exe
using Npgsql;
using System.Transactions;
//...
private void button1_Click(object sender, EventArgs e)
{
using (System.Transactions.TransactionScope scope = new TransactionScope())
{
AddValue("test1");
AddValue("test2");
throw new Exception("bam!");
AddValue("test3");
scope.Complete();
}
}
private void AddValue(string value)
{
string connString = "Server=localhost;Port=5432;Database=db_test;User Id=usr_test;Password=pass";
using (NpgsqlConnection conn = new NpgsqlConnection(connString))
{
conn.Open();
using (NpgsqlCommand cmd = new NpgsqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = #"INSERT INTO tbl_test (name)
VALUES (#name)";
cmd.Parameters.AddWithValue("#name", value);
cmd.ExecuteNonQuery();
}
}
}
I've never used NpgSql, but reading the documentation of NpgSql it appears they have some support of TransactionScope() if you add "enlist=true" in your connection string.
I'm looking at the "System.Transactions Support" section of the below NpgSql documentation:
http://npgsql.projects.postgresql.org/docs/manual/UserManual.html
Assuming TransactionScope() did work, then you can do simething like this...
using (var scope = new System.Transactions.TransactionScope())
{
ItemDAL itemDAL = new ItemDAL();
SomeOtherItemDAL someOtherItemDAL = new SomeOtherItemDAL();
// *** this must be done in one transaction ***
itemDAL.Add(item);
someOtherItemDAL.Add(someOtherItem);
scope.Complete()
}
What you did is brave, but not scalable. I'm not familiar with PGSQL, but this problem is the exact reason why the TransactionScope API was designed.
Can you show your code using the TransactionScope api? Make sure that you are not calling scope.Complete(); if an error occurs in one of the methods. Be careful to not "eat" the exceptions inside the methods because in that case the flow will continue just like if nothing happened.
More reading about TransactionScope here:
http://msdn.microsoft.com/en-us/library/ms172152.aspx
Update 1
Thanks for sharing your code that uses the TransactionScope class. The code looks perfectly correct to me. According to this (http://npgsql.projects.postgresql.org/docs/manual/UserManual.html) document (the same quoted by ChrisNeil52), Enlist=true should be included in the connection string for transactions to work.
You might be dealing with a buggy API. Good luck with this.
I know this sounds peculiar, but something I would try would be to use a different NpgsqlCommand constructor. new NpgsqlCommand("sql query", connection), instead of creating the command and assigning it the connection. They should be equivalent. but who know...