Simple SQL Connection Class to use for CRUD ops and transactions - c#

I have been trying to write an sql connection class for a small project,which will use simple sql commands for insert/update/select/delete ops and sometimes with transactions,is there any guidelines i could use? The class could be instantiated at any point with or without transactions...
Tried:
public class DbConnection
{
public static string srConnectionString = "blablab";
public DbConnection()
{
}
public static DataSet db_Select_Query(string strQuery)
{
DataSet dataSet = new DataSet();
try
{
using (SqlConnection connection = new SqlConnection(srConnectionString))
{
connection.Open();
SqlDataAdapter _SqlDataAdapter = new SqlDataAdapter(strQuery, connection);
DA.Fill(dataSet);
}
return dataSet;
}
catch (Exception)
{
//some error handling.
}
}
public static void db_Update_Delete_Query(string strQuery)
{
try
{
using (SqlConnection connection = new SqlConnection(srConnectionString))
{
connection.Open();
SqlCommand command = new SqlCommand(strQuery, connection);
command.ExecuteNonQuery();
}
}
catch (Exception)
{
//some error handling.
}
}
For example how can i add a parameter that opens or closes transaction as long as this class is used?For example i could be able to call db.commit or db.rollback from outside the class.
PS:tried some micro orms(petapoco for ex.) ,is there a way to run them with pure sql and get return type as datasets or datatables only?
Regards.
Edit:
public class dbconnection : IDisposable
{
public static string strConnectionString = #"Data Source=PC137\PC137_SQL2008;Initial Catalog=BARZO;Integrated Security=True";
#region IDisposable Members
public void Dispose()
{
GC.SuppressFinalize(this);
}
#endregion
private SqlTransaction transaction;
private SqlConnection connection;
private SqlCommand command = new SqlCommand();
public void db_OpenConnection(bool WithTransaction)
{
connection = new SqlConnection(strConnectionString);
connection.Open();
if (WithTransaction)
{
transaction = connection.BeginTransaction();
}
}
public void db_CloseConnection()
{
connection.Close();
connection.Dispose();
transaction.Dispose();
command.Dispose();
}
public void db_Commit()
{
transaction.Commit();
}
public void db_RollBack()
{
transaction.Rollback();
}
public DataSet db_Select_Query(string strQuery)
{
var dataSet = new DataSet();
try
{
SqlDataAdapter SqlDataAdapter = new SqlDataAdapter(strQuery, connection);
SqlDataAdapter.Fill(dataSet);
return dataSet;
}
catch (SqlException sqlError)
{
MessageBox.Show(sqlError,MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
}
public bool db_Update_Delete_Query(string strQuery)
{
try
{
command = new SqlCommand(strQuery, connection);
command.Transaction = transaction;
command.ExecuteNonQuery();
}
catch (SqlException sqlError)
{
MessageBox.Show(sqlError,MessageBoxButtons.OK, MessageBoxIcon.Stop);
return false;
}
return true;
}
}

I would make the db_Update_Delete_Query() method return your own DbCommandToken, which wraps an SqlTransaction object, and can be used to cancel the transaction or report the completion of the command.
This would look like this.
public class DbConnection
{
public static DbCommandToken db_Update_Delete_Query(string strQuery)
{
try
{
using (SqlConnection connection = new SqlConnection(strConnectionString))
{
connection.Open();
var transaction = connection.BeginTransaction();
SqlCommand command = new SqlCommand(strQuery, connection);
return new DbCommandToken(transaction, command);
}
}
catch (Exception)
{
//some error handling.
}
}
}
Note that both your command and your transaction are now owned by your DbCommandToken object, which should implement IDisposable.
If your command is a long-running command, which you may want to run asynchronously, and maybe cancel with a later request, you can add success and failure callbacks to your DbCommandToken: something along these lines.
public class DbCommandToken : IDisposable
{
private readonly SqlTransaction _transaction;
private readonly SqlCommand _command;
public DbCommandToken(SqlTransaction transaction, SqlCommand command)
{
_transaction = transaction;
_command = command;
}
public Action Success { get; set; }
public Action Failure { get; set; }
public Task<int> Execute()
{
return Task.Factory.StartNew(() => _command.ExecuteNonQuery())
.ContinueWith(t =>
{
var rowsAffected = t.Result;
if (rowsAffected >= 0)
{
_transaction.Commit();
Success();
}
...Handle other scenarios here...
return t.Result;
});
}
public void Cancel()
{
_transaction.Rollback();
}
public void Dispose()
{
_command.Dispose();
_transaction.Dispose();
}
}

Related

Alternative for wraping ADO.net

I'm using the following class to wrap my CRUD operations:
public class DAL
{
private MySqlConnection conn;
private MySqlCommand cmd = new MySqlCommand();
private MySqlDataAdapter adap = new MySqlDataAdapter();
private MySqlDataReader dr;
public MySqlDataReader Dr
{
get { return dr; }
set { dr = value; }
}
public MySqlDataAdapter Adap
{
get { return adap; }
set { adap = value; }
}
public MySqlCommand Cmd
{
get { return cmd; }
set { cmd = value; }
}
public MySqlConnection Conn
{
get { return conn; }
set { conn = value; }
}
public DAL(IConfiguration configuration)
{
this.conn = new MySqlConnection(configuration.GetConnectionString("DefaultConnection"));
}
public DAL(string constr)
{
this.conn = new MySqlConnection(constr);
}
}
Some professionals here say that my DAL Class is terrible because of this
To be honest I didn't understand it very well. But no problem.
You say it's not right to wrap Ado.net. So I don't do it anymore.
There is just one problem. What happens if I have to change my db from MySql to MSSQL?
How can I write code the way I can change my DB with changing just one file and not more.
How should I do that?
You should use interfaces.
By example instead of returning "MySqlDbConnection", you could return a "IDbConnection".
In that case the caller will only know it's a database connection. No matter if it's a MySQL or SQL Server connection.
That way the only file that knows about MySql specifically will be your DAL class.
Those interfaces already exist (IDbConnection, IDbCommand, ...) so don't need to create it yourself.
Edit :
In the following example I modified a few things :
Use interfaces (or abstract classes)
Using naming guidelines + readonly keyword
Use full name for naming things (as suggested by #pinkfloydx33 in a comment) it will help readibility. For example the name of the parameter you used for the connection string was "constr" wich, for me, was meaning "constructor" at first (without reading the following line).
What is not fixed:
The usage of the DbConnection (as you mentionned in your post, you should read again the question you linked because if it's a production code it can/will cause so issues).
Possible improvements:
Using an ORM or a library like Dapper (as suggested by #insane_developer in a comment)
public class DAL
{
private readonly IDbConnection _connection;
private DbCommand _command = new MySqlCommand();
private IDbDataAdapter = new MySqlDataAdapter();
private IDataReader _dataReader;
public IDataReader DataReader
{
get { return _dataReader; }
set { _dataReader = value; }
}
public IDbDataAdapter Adapter
{
get { return _adapter; }
set { _adapter = value; }
}
public DbCommand Cmd
{
get { return _command; }
set { _command = value; }
}
public IDbConnection Connection
{
get { return _connection; }
set { _connection = value; }
}
public DAL(IConfiguration configuration)
{
this._connection = new MySqlConnection(configuration.GetConnectionString("DefaultConnection"));
}
public DAL(string connectionString)
{
this._connection = new MySqlConnection(connectionString);
}
}
That way only this class knows about the DBMS type you're using.

Display Last Sql Record in MVC Core 3.1

i am new with MVC Core, i working on news website Project, how i can display last SQL record on my index?
please describe in details, what should i write in controller and razor page ?
thank you
public class mycontroller:BaseController
{
private readonly IRepo repo;
public mycontroller(IRepo _repo)
{
_repo=repo;
}
[httpGet]
public IActionResult<string> GetLastRecord()
{
return _repo.GetLastRecord();
}
}
public class repo
{
private readonly IDBContextFactory dBContextFactory;
public repo(IDBContextFactory _dbContextFactory)
{
_dbContextFactory=dBContextFactory;
}
public string GetLastRecord()
{
return _dbContextFactory.Select("mydb","select top 1 text from mydb order by autoincreamentedkeyusualyid desc")[0];/* this is bad way of using data table I recommend using this https://stackoverflow.com/questions/33515552/converting-datatable-to-listentity-projectdracula */;
}
}
public interface IRepo
{
public string GetLastRecord();
}
public class DBContextFactory
{
private SqlCommand BuildFactory(string dbName)
{
switch(dbName)
{
case 'mydb':
return CreateMyDB();
}
}
private SqlCommand CreateMyDB()
{
string connectionString = "your connection string";
SqlConnection connection =
new SqlConnection(connectionString));
SqlCommand command = new SqlCommand(connection);
return command.Open();
}
//Private SqlCommand GetMyOpenCommand()
public DataTable Select(string dbName,string query)
{
SqlDataAdapter dataAdapter=new SqlDataAdapter();
dataAdapter.SelectCommand=BuildFactory(dbName);
DataSet dataSet=new DataSet();
dataAdapter.Fill(dataSet);
con.Close();
}
}
public inteface IDBContextFactory
{
SqlCommand BuildFactory(string dbName);
SqlCommand CreateMyDB();
DataTable Select(string dbName,string query)

C# Unity DI container not accepting Enum with RegisterType function

see this below code
cont.RegisterType<IBBAConnection, BBAConnection>(ConType.local);
cont.RegisterType<IBBAConnection, BBAConnection>(ConType.remote);
cont.RegisterType<IBBAConnection, BBAConnection>(ConType.OrcsWeb);
cont.RegisterType<IBBAConnection, BBAConnection>(ConType.Sage);
Unity RegisterType not accepting enum but when if i pass string then no problem occur but i have to use enum. my full code as follow. so some one see my code and tell me what and where to fix in code as a result enum should be accepted.
full code
public enum ConType { local, remote, OrcsWeb, Sage, };
public interface IBBAConnection
{
IDbConnection GetConnection();
string ConType { get; set; }
}
public class BBAConnection : IBBAConnection
{
public ConType ConType { get; set; }
public IDbConnection GetConnection()
{
string _connectionString = "";
IDbConnection connection = null;
try
{
// inside if else logic we fetch connection string from ini file or from any source and inistialize connection.
if (ConType == ConType.local)
{
_connectionString = "put here local db connection";
connection = new System.Data.SqlClient.SqlConnection(_connectionString);
}
else if (ConType == ConType.remote)
{
_connectionString = "put here remote db connection";
connection = new System.Data.SqlClient.SqlConnection(_connectionString);
}
else if (ConType == ConType.OrcsWeb)
{
_connectionString = "put here website db connection";
connection = new System.Data.SqlClient.SqlConnection(_connectionString);
}
else if (ConType == ConType.Sage)
{
_connectionString = "put here sage connection";
connection = new System.Data.SqlClient.SqlConnection(_connectionString);
}
connection.Open();
}
catch (Exception ex)
{
string strErr = ex.Message;
}
return connection;
}
}
public static class Factory
{
static IUnityContainer cont = null;
public static IBBAConnection initialize(ConType oConType)
{
IBBAConnection oDbConnection = null;
cont = new UnityContainer();
cont.RegisterType<IBBAConnection, BBAConnection>(ConType.local);
cont.RegisterType<IBBAConnection, BBAConnection>(ConType.remote);
cont.RegisterType<IBBAConnection, BBAConnection>(ConType.OrcsWeb);
cont.RegisterType<IBBAConnection, BBAConnection>(ConType.Sage);
oDbConnection = cont.Resolve<IBBAConnection>(oConType);
//oDbConnection.ConType = type;
return oDbConnection;
}
}
looking for guide line that what to change as a result Enum should be accepted.
Below is what I think you should do.
It will dramatically reduce the complexity of BBAConnection because you're letting your IConnectionConfig binding determine the connection string you need.
public interface IConnectionConfig
{
string GetConnectionString();
}
public class LocalConnectionConfig : IConnectionConfig
{
public string GetConnectionString()
{
return "db connection for local";
}
}
public class BBAConnection : IBBAConnection
{
private readonly IConnectionConfig config;
public BBAConnection(IConnectionConfig config)
{
this.config = config;
}
public IDbConnection GetConnection()
{
string _connectionString = "";
IDbConnection connection = null;
try
{
connection = new System.Data.SqlClient.SqlConnection(this.config.GetConnectionString());
connection.Open();
}
catch (Exception ex)
{
string strErr = ex.Message;
}
return connection;
}
}
The registrations:
container.RegisterType<IBBAConnection, BBAConnection>();
container.RegisterType<IConnectionConfig, LocalConnectionConfig>();
Conceptually Speaking
You would normally let your build configurations define what configs you are using. You can then use that in your code to define which config you need.

TransactionScope with nested methods - MySQL

Does MYSQL support TransactionScope without passing the MySqlConnection object as a parameter?
Or does it just work with MS SQL 2008 and above?
for example:
public void Method()
{
using (var scope = new System.Transactions.TransactionScope())
{
Delete();
Insert();
Update();
scope.Complete();
}
}
public void Update()
{
using(MySqlConnection conn = new MySqlConnection())
{
// Update something in the Database
}
}
public void Insert()
{
using(MySqlConnection conn = new MySqlConnection())
{
// Insert something in the Database
}
}
public void Delete()
{
using(MySqlConnection conn = new MySqlConnection())
{
// Delete something from Database
}
}
or should I do it with the MySqlConnection conn object as a parameter?
public void Method()
{
using (var scope = new System.Transactions.TransactionScope())
{
using(MySqlConnection conn = new MySqlConnection())
{
Delete(conn);
Insert(conn);
Update(conn);
}
scope.Complete();
}
}
public void Update(MysqlConnection conn)
{
// Update something in the Database
}
public void Insert(MysqlConnection conn)
{
// Insert something in the Database
}
public void Delete(MysqlConnection conn)
{
// Delete something from Database
}
You cannot create multiple MySqlConnection objects within a single TransactionScope: bug 50773.
A solution is to upgrade to https://github.com/mysql-net/MySqlConnector which does support TransactionScope.

How to call base abstract or interface from DAL into BLL?

How can i access abstract class in BLL ? i shouldn't see GenAccessor in BLL it must be private class GenAccessor . i should access Save method over _AccessorForSQL. ok?
MY BLL cs:
public class AccessorForSQL: GenoTip.DAL._AccessorForSQL
{
public bool Save(string Name, string SurName, string Adress)
{
ListDictionary ld = new ListDictionary();
ld.Add("#Name", Name);
ld.Add("#SurName", SurName);
ld.Add("#Adress", Adress);
return **base.Save("sp_InsertCustomers", ld, CommandType.StoredProcedure);**
}
}
i can not access base.Save....????????
it is my DAL Layer:
namespace GenoTip.DAL
{
public abstract class _AccessorForSQL
{
public abstract bool Save(string sp, ListDictionary ld, CommandType cmdType);
public abstract bool Update();
public abstract bool Delete();
public abstract DataSet Select();
}
private class GenAccessor : _AccessorForSQL
{
DataSet ds;
DataTable dt;
public override bool Save(string sp, ListDictionary ld, CommandType cmdType)
{
SqlConnection con = null;
SqlCommand cmd = null;
SqlDataReader dr = null;
try
{
con = GetConnection();
cmd = new SqlCommand(sp, con);
con.Open();
cmd.CommandType = cmdType;
foreach (string ky in ld.Keys)
{
cmd.Parameters.AddWithValue(ky, ld[ky]);
}
dr = cmd.ExecuteReader();
ds = new DataSet();
dt = new DataTable();
ds.Tables.Add(dt);
ds.Load(dr, LoadOption.OverwriteChanges, dt);
}
catch (Exception exp)
{
HttpContext.Current.Trace.Warn("Error in GetCustomerByID()", exp.Message, exp);
}
finally
{
if (dr != null) dr.Close();
if (con != null) con.Close();
}
return (ds.Tables[0].Rows.Count > 0) ? true : false;
}
public override bool Update()
{
return true;
}
public override bool Delete()
{
return true;
}
public override DataSet Select()
{
DataSet dst = new DataSet();
return dst;
}
private static SqlConnection GetConnection()
{
string connStr = WebConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
SqlConnection conn = new SqlConnection(connStr);
return conn;
}
Scott Ambler: Building Object Applications That Work.
No futher comment - the whole DAL is pretty much a nice demonstration of how NOT to do it. A DAL should have NO (!) reference to the BLL at all. None. The DAL code area should not ahve ANY reference to the BLL layer at all.
In your case I strongly suggest some looks at:
BLToolkit
Nhibernate
If you want to access the save method you simply create an instance of your DAL and call the .Save method like so:
using(GenAccessor g = new GenAccessor)
{
g.save(sproc, param2, param3);
}
Your AccessorForSQL class must also be marked as abstract since you don't provide any implementation for the abstract methods in the _AccessorForSQL.
Clarification: the base keyword cannot be used here because it would indicate that you want to call the specific implementation found in the base class, which obviously doesn't exist since the method is abstract. That said, it doesn't mean you cannot call the method, you just have to drop the base keyword. And again, since you don't implement any of the abstract methods from the base class in the subclass you must mark the subclass as abstract as well. The following should work:
public abstract class AccessorForSQL: GenoTip.DAL._AccessorForSQL
{
public bool Save(string Name, string SurName, string Adress)
{
ListDictionary ld = new ListDictionary();
ld.Add("#Name", Name);
ld.Add("#SurName", SurName);
ld.Add("#Adress", Adress);
return Save("sp_InsertCustomers", ld, CommandType.StoredProcedure);
}
}
You should probably consider using a default implementation in your _AccessorForSQL class. Then you dont need the GenAccessor at all.
By the way, you shouldn't really be using anything web related in your DAL. HttpContext and WebConfigurationManager should be factored out so you can use your DAL layer in other contexts such as WinForms.
namespace GenoTip.DAL
{
public interface IAccessorForSQL
{
bool Delete();
bool Save(string sp, ListDictionary ld, CommandType cmdType);
DataSet Select();
bool Update();
}
public class _AccessorForSQL : IAccessorForSQL
{
private DataSet ds;
private DataTable dt;
public virtual bool Save(string sp, ListDictionary ld, CommandType cmdType)
{
SqlConnection con = null;
SqlCommand cmd = null;
SqlDataReader dr = null;
try
{
con = GetConnection();
cmd = new SqlCommand(sp, con);
con.Open();
cmd.CommandType = cmdType;
foreach (string ky in ld.Keys)
{
cmd.Parameters.AddWithValue(ky, ld[ky]);
}
dr = cmd.ExecuteReader();
ds = new DataSet();
dt = new DataTable();
ds.Tables.Add(dt);
ds.Load(dr, LoadOption.OverwriteChanges, dt);
}
catch (Exception exp)
{
HttpContext.Current.Trace.Warn("Error in GetCustomerByID()", exp.Message, exp);
}
finally
{
if (dr != null)
{
dr.Close();
}
if (con != null)
{
con.Close();
}
}
return (ds.Tables[0].Rows.Count > 0) ? true : false;
}
public abstract bool Update();
public abstract bool Delete();
public abstract DataSet Select();
private static SqlConnection GetConnection()
{
string connStr = WebConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
var conn = new SqlConnection("");
return conn;
}
}
}
You cannot call an abstract method because it does not have an implementation!
Perhaps you want a virtual method?
By the way this line:
return (ds.Tables[0].Rows.Count > 0) ? true : false;
is the same as this:
return (ds.Tables[0].Rows.Count > 0);

Categories