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.
Related
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)
I have a class library that contains hundreds of static methods and variable as follows…
public class General
{
public static string Con { get; set; }
public static string Func1()
{
using (SqlConnection con = new SqlConnection(Con))
{
// My stuff here
}
}
public static string Func2()
{
using (SqlConnection con = new SqlConnection(Con))
{
// My stuff here
}
}
public static string Funcn()
{
using (SqlConnection con = new SqlConnection(Con))
{
// My stuff here
}
}
}
I referenced this class library to my ASP.Net Webform app and assign connectionstring from Global.asax’s Application_Start event
protected void Application_Start(object sender, EventArgs e)
{
General.Con = ConfigurationManager.ConnectionStrings["Con"].ConnectionString;
}
Everything works fine.
Now that I wanted to change database connectionstring on per-LoggedIn-user basis.
What I have tried:
I stored all user’s connectionstring info at one table in common database and mapped them to respective userIDs.
On Login button click, I could save connectionstring to session.
Session["Con"] = ds.Tables[0].Rows[0][0].ToString();
In class library I used
Public static string Con = System.Web.HttpContext.Current.Session["Con"].ToString();
I get error : Object reference not set to an instance of an object.
If I remove static, all my variable and methods gives an error: An object reference is required for the non-static
If I use new keyword here, error is “Cannot be accessed with an instance reference; qualify it with a type name instead”.
Then I used
public static string Con()
{
string Con = "";
HttpContext httpContext = HttpContext.Current;
if (httpContext.ApplicationInstance.Session.Count > 0)
{
if (httpContext.ApplicationInstance.Session["Con"] != null)
Consd = httpContext.ApplicationInstance.Session["Con"].ToString();
}
return Con;
}
It gives me error: Cannot convert from method group to string
Please help me out…
Your class is static, which means there is a single instance, yet you want to change the connection on a user basis.
That is obviously not going to work. I would personally recommend you allow multiple instances of your class (non-static) and initialize each instance with the connection string as a parameter:
public class General
{
private string _connectionString;
public General(string connectionString)
{
_connectionString = connectionString;
}
}
You can then instantiate it once per session if you want, or simply instantiate it every time you need to use it. I would not recommend adding dependencies into the class (e.g. where the connection string comes to).
var data = new General(ds.Tables[0].Rows[0][0].ToString());
var result = data.Func1();
If for some reason, you need to keep the class static, you would need to either provide the connection string to the function so it can use it, or embed the logic to pull out the connection string from the session into the method:
public static string Func1(string connectionString)
{
using (SqlConnection con = new SqlConnection(connectionString))
{
// My stuff here
}
}
OR
public static string Func1()
{
using (SqlConnection con = new SqlConnection(ds.Tables[0].Rows[0][0].ToString()))
{
// My stuff here
}
}
I would recommend passing the connection string as a parameter if you decide to go this route.
One last option if you wish to keep your class static would be to use a Configurator object that provides the connection string, in order to abstract the location:
public interface IConfigurator
{
string GetConnectionString();
}
public class SessionConfigurator : IConfigurator
{
public string GetConnectionString()
{
var connectionString = Session["Con"].ToString();
return connectionString;
}
}
public static class General
{
public IConfigurator Configurator { get; set; }
public static string Func1()
{
using (SqlConnection con = new SqlConnection(Configurator.GetConnectionString()))
{
// My stuff here
}
}
}
Then upon application startup:
General.Configurator = new SessionConfigurator();
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();
}
}
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);
My class has the following core:
class SmartDbConnection
{
private readonly IDbConnection Connection;
public SmartDbConnection(string ConnectionString)
{
if(ConnectionString.Contains("MultipleActiveResultSets=true"))
{
Connection = new SqlConnection(ConnectionString);
}
}
}
I don't want it to have "SqlConnection" hardcoded. So I thought in making it a Generic class (accepting IDbConnection classes). But I don't know how to do it. Anyone can help?
First - I've added IDisposable to this, as I believe it is important.
Second, note that providers are an alternative here:
class SmartDbConnection
{
private DbConnection Connection;
public SmartDbConnection(string provider, string connectionString)
{
Connection = DbProviderFactories.GetFactory(provider)
.CreateConnection();
Connection.ConnectionString = connectionString;
}
public void Dispose() {
if (Connection != null)
{
Connection.Dispose();
Connection = null;
}
}
}
If you must go generic, how about:
class SmartDbConnection<T> : IDisposable where T : class,
IDbConnection, new()
{
private T Connection;
public SmartDbConnection(string connectionString)
{
T t = new T();
t.ConnectionString = connectionString;
// etc
}
public void Dispose() {
if (Connection != null)
{
Connection.Dispose();
Connection = null;
}
}
}
Why don't you accept IDbConnection instead of connectionstring to your ctor?
Maybe...
class SmartDbConnection<T> where T : IDbConnection, new()
{
private readonly IDbConnection Connection;
public SmartDbConnection(string connectionString)
{
if (connectionString.Contains("MultipleActiveResultSets=true"))
{
Connection = new T();
Connection.ConnectionString = connectionString;
}
}
}
EDIT: But what kaanbardak suggests can be even better...
If you don't want to specify SqlConnection there, where would you specify it - and how would you know to use it only if the connection string contains "MultipleActiveResultSets=true"?
I suspect at some level you want a connection factory - either a Func<string, IDbConnection> you can pass in or set somewhere, or possibly just a class:
public static class ConnectionFactory
{
public static IDbConnection CreateConnection(string connectionString)
{
// Hard-code stuff here
}
}
Of course, they're just two sides of the same coin - ConnectionFactory is just a static implementation of the Func<string, IDbConnection>.
class SmartDbConnection<T> where T: IDbConnection , new()
{
private readonly T Connection;
public SmartDbConnection(string ConnectionString)
{
if (ConnectionString.Contains("MultipleActiveResultSets=true"))
{
Connection = new T();
Connection.ConnectionString = ConnectionString;
}
}
}