I have a common database class for my application and in that class i have a function
public MySqlDataReader getRecord(string query)
{
MySqlDataReader reader;
using (var connection = new MySqlConnection(connectionString))
{
connection.Open();
using (var cmd = new MySqlCommand(query, connection))
{
reader = cmd.ExecuteReader();
return reader;
}
}
return null;
}
and on my code behind pages I use
String sql = "SELECT * FROM `table`";
MySqlDataReader dr = objDB.getRecord(sql);
if (dr.Read())
{
// some code goes hear
}
and I am having error as Invalid attempt to Read when reader is closed.
I know access the reader after the database connection is closed is not possible
bot I am looking for a work around in which I need not to change in the codebehind
EDIT: I WILL LIKE THE SOLUTION IN WHICH THE READER IS ASSIGNED TO OTHER OBJECT (SIMILAR TO READER ) AND THEN RETURN THAT OBJECT so i need not to change in all the application pages
You can load the results of your query to memory, then close the connection and still return an IDataReader that works as expected. Note that this costs memory.
public IDataReader getRecord(string query)
{
MySqlDataReader reader;
using (var connection = new MySqlConnection(connectionString))
{
connection.Open();
using (var cmd = new MySqlCommand(query, connection))
{
reader = cmd.ExecuteReader();
var dt = new DataTable();
dt.Load( reader );
return dt.CreateDataReader();
}
}
return null;
}
In the callers:
String sql = "SELECT * FROM `table`";
var dr = objDB.getRecord(sql); // or DataTableReader dr = ...
if (dr.Read())
{
// some code goes here
}
When the scope of your call to using (var connection = new MySqlConnection(connectionString))
ends, the connection will be closed.
However, you are still returning a reader based on that connection. Once you try to use it in your caller method, you will get the error as closed connection can't be used.
Besides, your method is called GetRecord but it returns a reader.
One of the options is to do this:
public void processQuery(string query, Action<MySqlDataReader> fn)
{
using (var connection = new MySqlConnection(connectionString))
{
connection.Open();
using (var cmd = new MySqlCommand(query, connection))
{
using (var reader = cmd.ExecuteReader())
{
fn(reader);
}
}
}
}
// caller
String sql = "SELECT * FROM `table`";
objDB.procesQuery(sql, dr => {
if (dr.Read())
{
// some code goes here
}
});
Your idea of creating an object 'similar to reader', so you don't have to change the caller, would not work: the returned object would need to contain both reader and an open connection, so that you can use the reader. This means you would have to close the connection in the caller. In best case, the caller would need to be modified as follows:
String sql = "SELECT * FROM `table`";
using (MyWrapper wr = objDB.getRecord(sql))
{
if (wr.Reader.Read())
{
// some code goes here
}
}
You will not save that much work, but one missing using statement in the caller will result in your app not working after some time due to a connection leak.
What you want is possible, but it is not a nice solution, because you have to wrap all the functions of the MySqlDataReader class and forward it to the real MySqlDataReader.
See the ConnectedMySqlDataReader class (hint: it does not implement all functions of MySqlDataReader, if you really want to use it, you have to do it yourself) and how it would fit in your solution:
public ConnectedMySqlDataReader GetRecord(string query)
{
return new ConnectedMySqlDataReader(connectionString, query);
}
// ...
var sql = "SELECT * FROM `table`";
using(var dr = objDB.GetRecord(sql))
{
if (dr.Read())
{
// some code goes hear
}
}
i have not tested this class, it is just for demonstration!
using System;
using System.Collections;
using System.Data;
using System.Data.Common;
using MySql.Data.MySqlClient;
namespace MySqlTest
{
public class ConnectedMySqlDataReader : DbDataReader
{
private readonly MySqlConnection _connection;
private readonly Lazy<MySqlDataReader> _reader;
private MySqlCommand _command;
public ConnectedMySqlDataReader(MySqlConnection connection, string query)
{
if(connection == null)
throw new ArgumentNullException("connection");
_connection = connection;
_reader = new Lazy<MySqlDataReader>(() =>
{
_connection.Open();
_command = new MySqlCommand(query, _connection);
return _command.ExecuteReader();
});
}
public ConnectedMySqlDataReader(string connectionString, string query)
: this(new MySqlConnection(connectionString), query) { }
private MySqlDataReader Reader
{
get { return _reader.Value; }
}
public override void Close()
{
if (_reader.IsValueCreated)
_reader.Value.Close();
if(_command != null)
_command.Dispose();
_connection.Dispose();
}
public override DataTable GetSchemaTable()
{
return this.Reader.GetSchemaTable();
}
public override bool NextResult()
{
return this.Reader.NextResult();
}
public override bool Read()
{
return this.Reader.Read();
}
public override int Depth
{
get { return this.Reader.Depth; }
}
public override bool IsClosed
{
get { return this.Reader.IsClosed; }
}
public override int RecordsAffected
{
get { return this.Reader.RecordsAffected; }
}
public override bool GetBoolean(int ordinal)
{
return this.Reader.GetBoolean(ordinal);
}
public override byte GetByte(int ordinal)
{
return this.Reader.GetByte(ordinal);
}
public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
{
return this.Reader.GetBytes(ordinal, dataOffset, buffer, bufferOffset, length);
}
public override char GetChar(int ordinal)
{
return this.Reader.GetChar(ordinal);
}
public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
{
return this.Reader.GetChars(ordinal, dataOffset, buffer, bufferOffset, length);
}
public override Guid GetGuid(int ordinal)
{
return this.Reader.GetGuid(ordinal);
}
public override short GetInt16(int ordinal)
{
return this.Reader.GetInt16(ordinal);
}
public override int GetInt32(int ordinal)
{
return this.Reader.GetInt32(ordinal);
}
public override long GetInt64(int ordinal)
{
return this.Reader.GetInt64(ordinal);
}
public override DateTime GetDateTime(int ordinal)
{
return this.Reader.GetDateTime(ordinal);
}
public override string GetString(int ordinal)
{
return this.Reader.GetString(ordinal);
}
public override object GetValue(int ordinal)
{
return this.Reader.GetValue(ordinal);
}
public override int GetValues(object[] values)
{
return this.Reader.GetValues(values);
}
public override bool IsDBNull(int ordinal)
{
return this.Reader.IsDBNull(ordinal);
}
public override int FieldCount
{
get { return this.Reader.FieldCount; }
}
public override object this[int ordinal]
{
get { return this.Reader[ordinal]; }
}
public override object this[string name]
{
get { return this.Reader[name]; }
}
public override bool HasRows
{
get { return this.Reader.HasRows; }
}
public override decimal GetDecimal(int ordinal)
{
return this.Reader.GetDecimal(ordinal);
}
public override double GetDouble(int ordinal)
{
return this.Reader.GetDouble(ordinal);
}
public override float GetFloat(int ordinal)
{
return this.Reader.GetFloat(ordinal);
}
public override string GetName(int ordinal)
{
return this.Reader.GetName(ordinal);
}
public override int GetOrdinal(string name)
{
return this.Reader.GetOrdinal(name);
}
public override string GetDataTypeName(int ordinal)
{
return this.Reader.GetDataTypeName(ordinal);
}
public override Type GetFieldType(int ordinal)
{
return this.Reader.GetFieldType(ordinal);
}
public override IEnumerator GetEnumerator()
{
return this.Reader.GetEnumerator();
}
}
}
PS: this is what sealed in c# context means, and yes, MySqlDataReader is sealed.
Related
See my repository code
public abstract class AdoRepository<T> where T : class
{
private static SqlConnection _connection;
public AdoRepository(string connectionString)
{
_connection = new SqlConnection(connectionString);
}
public virtual T PopulateRecord(SqlDataReader reader)
{
return null;
}
public virtual void GetDataCount(int count)
{
}
protected IEnumerable<T> GetRecords(SqlCommand command)
{
var list = new List<T>();
command.Connection = _connection;
_connection.Open();
try
{
var reader = command.ExecuteReader();
try
{
while (reader.Read())
{
list.Add(PopulateRecord(reader));
}
reader.NextResult();
if (reader.HasRows)
{
while (reader.Read())
{
GetDataCount(Convert.ToInt32(reader["Count"].ToString()));
}
}
}
finally
{
// Always call Close when done reading.
reader.Close();
}
}
catch(Exception ex)
{
string Msg = ex.Message;
}
finally
{
_connection.Close();
}
return list;
}
protected T GetRecord(SqlCommand command)
{
T record = null;
command.Connection = _connection;
_connection.Open();
try
{
var reader = command.ExecuteReader();
try
{
while (reader.Read())
{
record = PopulateRecord(reader);
break;
}
}
finally
{
// Always call Close when done reading.
reader.Close();
}
}
finally
{
_connection.Close();
}
return record;
}
protected IEnumerable<T> ExecuteStoredProc(SqlCommand command)
{
var list = new List<T>();
command.Connection = _connection;
command.CommandType = CommandType.StoredProcedure;
_connection.Open();
try
{
var reader = command.ExecuteReader();
try
{
while (reader.Read())
{
var record = PopulateRecord(reader);
if (record != null) list.Add(record);
}
}
finally
{
// Always call Close when done reading.
reader.Close();
}
}
finally
{
_connection.Close();
}
return list;
}
}
public class StudentRepository : AdoRepository<Student>
{
public int DataCounter { get; set; }
public StudentRepository(string connectionString)
: base(connectionString)
{
}
public IEnumerable<Student> GetAll()
{
// DBAs across the country are having strokes
// over this next command!
using (var command = new SqlCommand("SELECT ID, FirstName,LastName,IsActive,StateName,CityName FROM vwListStudents"))
{
return GetRecords(command);
}
}
public Student GetById(string id)
{
// PARAMETERIZED QUERIES!
using (var command = new SqlCommand("SELECT ID, FirstName,LastName,IsActive,StateName,CityName FROM vwListStudents WHERE Id = #id"))
{
command.Parameters.Add(new ObjectParameter("id", id));
return GetRecord(command);
}
}
public IEnumerable<Student> GetStudents(int StartIndex, int EndIndex, string sortCol, string sortOrder)
{
string strSQL = "SELECT * FROM vwListStudents WHERE ID >=" + StartIndex + " AND ID <=" + EndIndex;
strSQL += " ORDER BY " + sortCol + " " + sortOrder;
strSQL += ";SELECT COUNT(*) AS Count FROM vwListStudents";
var command = new SqlCommand(strSQL);
return GetRecords(command);
}
public override Student PopulateRecord(SqlDataReader reader)
{
return new Student
{
ID = Convert.ToInt32(reader["ID"].ToString()),
FirstName = reader["FirstName"].ToString(),
LastName = reader["LastName"].ToString(),
IsActive = Convert.ToBoolean(reader["IsActive"]),
StateName = reader["StateName"].ToString(),
CityName = reader["CityName"].ToString()
};
}
public override void GetDataCount(int count)
{
DataCounter = count;
}
}
this way unit of work code added
public class UnitOfWorkFactory
{
public static IUnitOfWork Create()
{
var connection = new SqlConnection(ConfigurationManager.ConnectionStrings("MyDb").ConnectionString);
connection.Open();
return new AdoNetUnitOfWork(connection, true);
}
}
public class AdoNetUnitOfWork : IUnitOfWork
{
public AdoNetUnitOfWork(IDbConnection connection, bool ownsConnection)
{
_connection = connection;
_ownsConnection=ownsConnection;
_transaction = connection.BeginTransaction();
}
public IDbCommand CreateCommand()
{
var command = _connection.CreateCommand();
command.Transaction = _transaction;
return command;
}
public void SaveChanges()
{
if (_transaction == null)
throw new InvalidOperationException("Transaction have already been commited. Check your transaction handling.");
_transaction.Commit();
_transaction = null;
}
public void Dispose()
{
if (_transaction != null)
{
_transaction.Rollback();
_transaction = null;
}
if (_connection != null && _ownsConnection)
{
_connection.Close();
_connection = null;
}
}
}
using (var uow = UnitOfWorkFactory.Create())
{
var repos = new UserRepository(uow);
uow.SaveChanges();
}
public class UserRepository
{
private AdoNetUnitOfWork _unitOfWork;
public UserRepository(IUnitOfWork uow)
{
if (uow == null)
throw new ArgumentNullException("uow");
_unitOfWork = uow as AdoNetUnitOfWork;
if (_unitOfWork == null)
throw new NotSupportedException("Ohh my, change that UnitOfWorkFactory, will you?");
}
public User Get(Guid id)
{
using (var cmd = _unitOfWork.CreateCommand())
{
cmd.CommandText = "SELECT * FROM Users WHERE Id = #id");
cmd.AddParameter("id", id);
// uses an extension method which I will demonstrate in a
// blog post in a couple of days
return cmd.FirstOrDefault<User>();
}
}
}
but i want to add unit of work code in my repository class. may be in AdoRepository class or StudentRepository class as a result i do not have to write the below code again and again.
using (var uow = UnitOfWorkFactory.Create())
{
var repos = new UserRepository(uow);
uow.SaveChanges();
}
so when i will be working with any repository like UserRepository then i have to repeat again and again this lines of code UnitOfWorkFactory.Create() which i do not want rather i am looking for a way to embed unit of work code in some where centralize as a result i could reduce the repeated code.
so looking for idea and suggestion. if possible give me modified version of code.
thanks
I will be straightforward about this:
A UoW that contains repositories is an anti pattern at least if DDD is involved. If it isn't, then you're dealing with a very simple app so make you life easier and use a ORM which implements what you want.
As a thumb rule, a repository may contain a UoW as an implementation detail and you should be certain that you really need a repository. A repository inside a UoW is a very specific case which is valid ONLY with CRUD apps and you'll just be reinventing a very small part of an ORM.
Using ado.net directly is wasting time (and money). Either use an ORM or a data mapper aka micro-ORM. With the latter option the UoW is the Db transaction.
You don't gain anything significant using Ado.Net,the performance gain is so small it's not even funny (I know that because I've benchmarked it). The only thing you'll get is longer devel time and human errors.
Actually, if I take a better look at your code, you're basically building a micro ORM, without knowing it. It's still not a repository though, because the abstraction doesn't have business semantics and it's too tightly coupled to an implementation detail.
I am currently implementing a small software that could read data from different DataBases. The followin is the code:
interface Fetch
{
Dictionary<string, DbDataReader> GetData();
}
abstract class Conn : Fetch
{
abstract public void Connect();
abstract public Dictionary<string, SqlDataReader> GetData();
}
class SqlConn : Conn
{
public override void Connect()
{
_connection = new SqlConnection(_connectionString);
try
{
_connection.Open();
}
catch (SqlException dbe)
{
throw dbe;
}
}
public override Dictionary<string, SqlDataReader> GetData()
{
using (_connection)
{
Dictionary<string, SqlDataReader> dataDictionary = new Dictionary<string, SqlDataReader>();
_xmlDoc.Load("Queries.xml");
XPathNavigator navigator = _xmlDoc.CreateNavigator();
XPathNodeIterator iterator = navigator.Select("//query");
while (iterator.MoveNext())
{
_command = new SqlCommand(iterator.Current.ToString());
_command.Connection = _connection;
_command.CommandText = iterator.Current.ToString();
SqlDataReader reader = _command.ExecuteReader() as SqlDataReader;
dataDictionary.Add(iterator.Current.GetAttribute("name", ""), reader);
}
return dataDictionary;
}
}
}
class OraConn : Conn
{
public override void Connect()
{
_connection = new OracleConnection(_connectionString);
}
public override Dictionary<string, OracleDataReader> GetData()
{
using (_connection)
{
Dictionary<string, OracleDataReader> dataDictionary = new Dictionary<string, OracleDataReader>();
_xmlDoc.Load("Queries.xml");
XPathNavigator navigator = _xmlDoc.CreateNavigator();
XPathNodeIterator iterator = navigator.Select("//query");
while (iterator.MoveNext())
{
_command = new OracleCommand(iterator.Current.ToString());
_command.Connection = _connection;
_command.CommandText = iterator.Current.ToString();
OracleDataReader reader = _command.ExecuteReader() as OracleDataReader;
dataDictionary.Add(iterator.Current.GetAttribute("name", ""), reader);
}
return dataDictionary;
}
}
}
But My problem is the return type, SQLDataReader and OraDataReader in the derived class.
This causes a compiler error that states
‘Error 2 'DashBoard.Connection.OraConn.GetData()': return type must be 'System.Collections.Generic.Dictionary' to match overridden member 'DashBoard.Connection.Conn.GetData()
’.
How can I solve this problem? Or is there other ways to implement this funtion?
Thank you!
The method signature has to stay the same, but as long as your sub-classes inherit from DbDataReader you can return other DbDataReader types such as SqlDataReader and it will compile.
for example:
abstract class DbDataReader
{
// ...
}
class SqlDataReader : DbDataReader
{
}
// ...
class SqlConn : Conn
{
public override Dictionary<string, DbDataReader> GetData()
{
return new Dictionary<string, DbDataReader>
{
{ "Key", new SqlDataReader() }
}
}
}
You can try this way:
public interface IValue<T> { T GetValue(); }
public class SomeClass : IValue<DbDataReader>, IValue<SqlDataReader>
{
DbDataReader IValue<DbDataReader>.GetValue() { return objDbDataReader; }
SqlDataReader IValue<SqlDataReader>.GetValue() { return objSqlDataReader; }
}
I am trying to use AppFabric for fasten my image retrieval from SQL Database. I created my provider file and load it to my cache. However, I am struggling right now.
How can I call get function from cache using my provider file or do I need to use my provider file while retrieving data?
When I call .Get(key.Key), do I need to see the data coming from my database?
My Read method from provider is as follows, is this correct?
public override DataCacheItem Read(DataCacheItemKey key)
{
try
{
Object retrievedValue = null;
DataCacheItem cacheItem;
retrievedValue = *Running SQL Query Retrieving Image from DB*;//Is that a correct approach?
if (retrievedValue == null)
cacheItem = null;
else
cacheItem = DataCacheItemFactory.GetCacheItem(key, cacheName, retrievedValue, null);
return cacheItem;
}
catch
{
return null;
}
}
Example:>
using Microsoft.ApplicationServer.Caching;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient;
namespace SampleProvider
{
public class Provider : DataCacheStoreProvider
{
private readonly String dataCacheName;
private readonly Dictionary<string, string> config;
public Provider(string cacheName, Dictionary<string, string> config)
{
this.dataCacheName = cacheName; //Store the cache name for future use
this.config = config;
}
public override DataCacheItem Read(DataCacheItemKey key)
{
Object retrievedValue = null;
DataCacheItem cacheItem;
retrievedValue = ReadFromDatabase(key.Key); //Your implemented method that searches in the backend store based
if (retrievedValue == null)
cacheItem = null;
else
cacheItem = DataCacheItemFactory.GetCacheItem(key, dataCacheName, retrievedValue, null);
return cacheItem;
}
public override void Read(System.Collections.ObjectModel.ReadOnlyCollection<DataCacheItemKey> keys, IDictionary<DataCacheItemKey, DataCacheItem> items)
{
foreach (var key in keys)
{
items[key] = Read(key);
}
}
public override void Delete(System.Collections.ObjectModel.Collection<DataCacheItemKey> keys) { }
public override void Delete(DataCacheItemKey key) { }
protected override void Dispose(bool disposing) { }
public override void Write(IDictionary<DataCacheItemKey, DataCacheItem> items) { }
public override void Write(DataCacheItem item) { }
private string ReadFromDatabase(string key)
{
string value = string.Empty;
object retrievedValue = null;
using (SqlConnection connection = new SqlConnection(config["DBConnection"]))
{
SqlCommand cmd = new SqlCommand();
cmd.CommandText = string.Format("select Value from KeyValueStore where [Key] = '{0}'", key);
cmd.Connection = connection;
connection.Open();
retrievedValue = cmd.ExecuteScalar();
if (retrievedValue != null)
{
value = retrievedValue.ToString();
}
}
return value;
}
}
}
I am a newbie to 3 tier architecture and below is my DAL code
public static int Insert(string firstname, string lastname, DateTime dob, string gender,string email, string password)
{
// bool flag = false;
SqlParameter pid;
SqlParameter result;
SqlConnection con = Generic.DBConnection.OpenConnection();
try
{
SqlCommand cmd1 = new SqlCommand("Insertreg", con);
cmd1.CommandType = CommandType.StoredProcedure;
cmd1.Parameters.AddWithValue("#FirstName", firstname);
cmd1.Parameters.AddWithValue("#LastName", lastname);
cmd1.Parameters.AddWithValue("#Dob", dob);
cmd1.Parameters.AddWithValue("#Gender", gender);
cmd1.Parameters.AddWithValue("#EmailId", email);
cmd1.Parameters.AddWithValue("#Password", password);
result = cmd1.Parameters.Add("#result", System.Data.SqlDbType.Int);
result.Direction = System.Data.ParameterDirection.Output;
pid = cmd1.Parameters.Add("#id", System.Data.SqlDbType.Int);
pid.Direction = System.Data.ParameterDirection.Output;
return cmd1.ExecuteNonQuery();
con.Close();
}
catch (Exception ex)
{
throw ex;
}
}
this in BAL
public int insert(string firstname,string lastname,DateTime dob,string gender,string email,string password)
{
ProfileMasterDAL dal=new ProfileMasterDAL();
try
{
return ProfileMasterDAL.Insert(firstname, lastname, dob, gender,email, password);
}
catch (Exception ex)
{
throw ex;
}
finally
{
dal = null;
}
}
I my UI
ProfileMasterBLL pmBLL = new ProfileMasterBLL();
pmBLL.insert(firstname, lastname, dob, gender, mobile, country, state, email, password);
Is this the correct way to code in 3 tier??I mean how to call methods from DAL to BAL and into UI?If not suggest me some good way.Thanks.
Normally I do the following:
Define a Business Layer (BL, you call it BAL). This contains the definitions of you business entities. It also defines interfaces to retrieve/save/delete data for whatever patterns you use (repository, context, etc).
Define a Data Access Layer (DAL). This contains the actual implementation for the retrieve/save/delete interfaces.
Define a UI layer. This contains UI elements (forms, controls, models, controllers, etc), which can use the BL to load data.
The references are the following:
The BL doesn't know the DAL or the UI.
The DAL knows the BL. The DAL does not know the UI.
THe UI knows the BL. The UI does not know the DAL.
The big question for you probably is, how does the BL retrieve/save/delete data when it doesn't know the DAL, and therefore cannot create an instance of a class in the DAL. Well, this is where a little Dependency Injection comes in handy. All you have to wire up is the injection of the DAL-class to the BL-interface.
Hope this makes sense. I use it as my standard 3-tier implementation, and it works absolutely without problems. Specifically, I use Entity Framework with POCO for entities, and the DI I use is a custom one, but any of the ones out there will do.
UPDATE
The BL does not know the DAL.
The BL defines an interface (lets call it IRepository) which it can use to do what it needs to do.
The DAL defines a class (Repository) which implements the interface IRepository. So the actual implementation of the repository is in the DAL.
Obviously the BL cannot create an instance of the repository directly. This is where dependency injection comes in, this allows the developer to create an instance of a class where it normally cannot be done. A simple crude version of this, is to use reflection.
I hope this makes more sense.
It might help you to see some actual code. I suggest you download NetTiers, run it against your db schema and see the outputted code for the implementation details.
using System;
using System.Data;
using System.Configuration;
using System.Collections.Generic;
using System.Text;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data.OleDb;
using System.Data.Odbc;
using System.IO;
using System.ComponentModel;
namespace dal
{
/// <summary>
/// Summary description for Data Access Layer
/// </summary>
public class DataAccess
{
public string strConnectionString;
private DbConnection objConnection;
private DbCommand objCommand;
private DbProviderFactory objFactory = null;
private bool boolHandleErrors=false;
private string strLastError;
private bool boolLogError=false;
private string strLogFile;
public DataAccess()
{
//strConnectionString = ;
strConnectionString = objCommon.GetConnectionString;
objFactory = OleDbFactory.Instance;
objConnection = objFactory.CreateConnection();
objCommand = objFactory.CreateCommand();
objConnection.ConnectionString = strConnectionString;
objCommand.Connection = objConnection;
}
public bool HandleErrors
{
get
{
return boolHandleErrors;
}
set
{
boolHandleErrors = value;
}
}
public string LastError
{
get
{
return strLastError;
}
}
public bool LogErrors
{
get
- {
return boolLogError;
}
set
{
boolLogError = value;
}
}
public string LogFile
{
get
{
return strLogFile;
}
set
{
strLogFile = value;
}
}
public int AddParameter(string name, object value)
{
DbParameter p = objFactory.CreateParameter();
p.ParameterName = name;
p.Value = value;
return objCommand.Parameters.Add(p);
}
public int AddParameter(string name, object value, ParameterDirection direction)
{
DbParameter p = objFactory.CreateParameter();
p.ParameterName = name;
p.Value = value;
p.Direction = direction;
return objCommand.Parameters.Add(p);
}
public int AddParameter(string name, object value, DbType type)
{
DbParameter p = objFactory.CreateParameter();
p.ParameterName = name;
p.Value = value;
p.DbType = type;
return objCommand.Parameters.Add(p);
}
public int AddParameter(DbParameter parameter)
{
return objCommand.Parameters.Add(parameter);
}
public DbCommand Command
{
get
{
return objCommand;
}
}
public void BeginTransaction()
{
try
{
if (objConnection.State == System.Data.ConnectionState.Closed)
{
objConnection.Open();
}
objCommand.Transaction = objConnection.BeginTransaction();
}
catch (Exception Ex)
{
HandleExceptions(Ex);
}
}
public void CommitTransaction()
{
objCommand.Transaction.Commit();
objConnection.Close();
}
public void RollbackTransaction()
{
objCommand.Transaction.Rollback();
objConnection.Close();
}
public int ExecuteNonQuery(string query)
{
return ExecuteNonQuery(query, CommandType.Text, ConnectionState.CloseOnExit);
}
public int ExecuteNonQuery(string query, CommandType commandtype)
{
return ExecuteNonQuery(query, commandtype, ConnectionState.CloseOnExit);
}
public int ExecuteNonQuery(string query, ConnectionState connectionstate)
{
return ExecuteNonQuery(query, CommandType.Text, connectionstate);
}
public int ExecuteNonQuery(string query, CommandType commandtype, ConnectionState connectionstate)
{
objCommand.CommandText = query;
objCommand.CommandType = commandtype;
int i = -1;
try
{
if (objConnection.State == System.Data.ConnectionState.Closed)
{
objConnection.Open();
}
i = objCommand.ExecuteNonQuery();
}
catch (Exception ex)
{
HandleExceptions(ex);
}
finally
{
objCommand.Parameters.Clear();
if (connectionstate == ConnectionState.CloseOnExit)
{
objConnection.Close();
}
}
return i;
}
public object ExecuteScalar(string query)
{
return ExecuteScalar(query, CommandType.Text, ConnectionState.CloseOnExit);
}
public object ExecuteScalar(string query, CommandType commandtype)
{
return ExecuteScalar(query, commandtype, ConnectionState.CloseOnExit);
}
public object ExecuteScalar(string query, ConnectionState connectionstate)
{
return ExecuteScalar(query, CommandType.Text, connectionstate);
}
public object ExecuteScalar(string query, CommandType commandtype, ConnectionState connectionstate)
{
objCommand.CommandText = query;
objCommand.CommandType = commandtype;
object o = null;
try
{
if (objConnection.State == System.Data.ConnectionState.Closed)
{
objConnection.Open();
}
o = objCommand.ExecuteScalar();
}
catch (Exception ex)
{
HandleExceptions(ex);
}
finally
{
objCommand.Parameters.Clear();
if (connectionstate == ConnectionState.CloseOnExit)
{
objConnection.Close();
}
}
return o;
}
public DbDataReader ExecuteReader(string query)
{
return ExecuteReader(query, CommandType.Text, ConnectionState.CloseOnExit);
}
public DbDataReader ExecuteReader(string query, CommandType commandtype)
{
return ExecuteReader(query, commandtype, ConnectionState.CloseOnExit);
}
public DbDataReader ExecuteReader(string query, ConnectionState connectionstate)
{
return ExecuteReader(query, CommandType.Text, connectionstate);
}
public DbDataReader ExecuteReader(string query, CommandType commandtype, ConnectionState connectionstate)
{
objCommand.CommandText = query;
objCommand.CommandType = commandtype;
DbDataReader reader = null;
try
{
if (objConnection.State == System.Data.ConnectionState.Closed)
{
objConnection.Open();
}
if (connectionstate == ConnectionState.CloseOnExit)
{
reader = objCommand.ExecuteReader(CommandBehavior.CloseConnection);
}
else
{
reader = objCommand.ExecuteReader();
}
}
catch (Exception ex)
{
HandleExceptions(ex);
}
finally
{
objCommand.Parameters.Clear();
}
return reader;
}
public DataSet ExecuteDataSet(string query)
{
return ExecuteDataSet(query, CommandType.Text, ConnectionState.CloseOnExit);
}
public DataSet ExecuteDataSet(string query, CommandType commandtype)
{
return ExecuteDataSet(query, commandtype, ConnectionState.CloseOnExit);
}
public DataSet ExecuteDataSet(string query, ConnectionState connectionstate)
{
return ExecuteDataSet(query, CommandType.Text, connectionstate);
}
public DataSet ExecuteDataSet(string query, CommandType commandtype, ConnectionState connectionstate)
{
DbDataAdapter adapter = objFactory.CreateDataAdapter();
objCommand.CommandText = query;
objCommand.CommandType = commandtype;
adapter.SelectCommand = objCommand;
DataSet ds = new DataSet();
try
{
adapter.Fill(ds);
}
catch (Exception ex)
{
HandleExceptions(ex);
}
finally
{
objCommand.Parameters.Clear();
if (connectionstate == ConnectionState.CloseOnExit)
{
if (objConnection.State == System.Data.ConnectionState.Open)
{
objConnection.Close();
}
}
}
return ds;
}
private void HandleExceptions(Exception ex)
{
throw ex;
}
private void WriteToLog(string msg)
{
StreamWriter writer = File.AppendText(LogFile);
writer.WriteLine(DateTime.Now.ToString() + " - " + msg);
writer.Close();
}
public void Dispose()
{
objConnection.Close();
objConnection.Dispose();
objCommand.Dispose();
}
public enum Providers
{
SqlServer, OleDb, Oracle, ODBC, ConfigDefined
}
public enum ConnectionState
{
KeepOpen, CloseOnExit
}
public interface ILoadFromDataRow
{
bool LoadFromDataRow(DataRow row);
}
}
}
You can you the following sample code for 3 tier architecture :-
CLASS - BAL.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Collections;
public class BAL
{
DAL objDAL;
public BAL()
{
}
public string _Name;
public string Name
{
get { return _Name; }
set { _Name = value; }
}
public int insert()
{
objDAL = new DAL();
int val = 0;
try
{
Hashtable objHash = new Hashtable();
objHash.Add("#Name", Convert.ToString(_Name));
val = objDAL.Insert("Your SP Name", objHash);
}
catch (Exception ex)
{
throw ex;
}
finally
{
objDAL = null;
}
return val;
}
}
CLASS - DAL.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.SqlClient;
using System.Collections;
public class DAL : IDisposable
{
SqlConnection con;
public DAL()
{
con = new SqlConnection("Connection String");
}
public int Insert(string CMD, Hashtable objHash)
{
int val = 0;
try
{
SqlCommand cmd1 = new SqlCommand(CMD, con);
cmd1.CommandType = CommandType.StoredProcedure;
foreach (DictionaryEntry de in objHash)
{
cmd1.Parameters.AddWithValue(Convert.ToString(de.Key), Convert.ToString(de.Value));
}
con.Open();
val = cmd1.ExecuteNonQuery();
}
catch (Exception ex)
{
throw ex;
}
finally
{
con.Close();
}
return val;
}
#region IDisposable Members
public void Dispose()
{
throw new NotImplementedException();
}
#endregion
}
UI:-
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class _Default : System.Web.UI.Page
{
BAL objBAL;
protected void Page_Load(object sender, EventArgs e)
{
Insert();
}
public void Insert()
{
int val = 0;
objBAL = new BAL();
objBAL.Name = "stackoverflow";
try
{
val = objBAL.insert();
}
catch { }
finally
{
objBAL = null;
}
if (val != 0)
{
//Insert sucessful
}
else
{
//Error in Insert.
}
}
}
I have a WCF service, from which users can request large datafiles (stored in an SQL database with FileStream enabled). These files should be streamed, and not loaded into memory before sending them off.
So I have the following method that should return a stream, which is called by the WCF service, so that it can return the Stream to the client.
public static Stream GetData(string tableName, string columnName, string primaryKeyName, Guid primaryKey)
{
string sqlQuery =
String.Format(
"SELECT {0}.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT() FROM {1} WHERE {2} = #primaryKey", columnName, tableName, primaryKeyName);
SqlFileStream stream;
using (TransactionScope transactionScope = new TransactionScope())
{
byte[] serverTransactionContext;
string serverPath;
using (SqlConnection sqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnString"].ToString()))
{
sqlConnection.Open();
using (SqlCommand sqlCommand = new SqlCommand(sqlQuery, sqlConnection))
{
sqlCommand.Parameters.Add("#primaryKey", SqlDbType.UniqueIdentifier).Value = primaryKey;
using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
{
sqlDataReader.Read();
serverPath = sqlDataReader.GetSqlString(0).Value;
serverTransactionContext = sqlDataReader.GetSqlBinary(1).Value;
sqlDataReader.Close();
}
}
}
stream = new SqlFileStream(serverPath, serverTransactionContext, FileAccess.Read);
transactionScope.Complete();
}
return stream;
}
My problem is with the TransactionScope and the SqlConnection. The way I'm doing it right now doesn't work, I get a TransactionAbortedException saying "The transaction has aborted". Can I close the transaction and the connection before returning the Stream? Any help is appreciated, thank you
Edit:
I've created a wrapper for a SqlFileStream, that implements IDisposable so that I can close everything up once the stream is disposed. Seems to be working fine
public class WcfStream : Stream
{
private readonly SqlConnection sqlConnection;
private readonly SqlDataReader sqlDataReader;
private readonly SqlTransaction sqlTransaction;
private readonly SqlFileStream sqlFileStream;
public WcfStream(string connectionString, string columnName, string tableName, string primaryKeyName, Guid primaryKey)
{
string sqlQuery =
String.Format(
"SELECT {0}.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT() FROM {1} WHERE {2} = #primaryKey",
columnName, tableName, primaryKeyName);
sqlConnection = new SqlConnection(connectionString);
sqlConnection.Open();
sqlTransaction = sqlConnection.BeginTransaction();
using (SqlCommand sqlCommand = new SqlCommand(sqlQuery, sqlConnection, sqlTransaction))
{
sqlCommand.Parameters.Add("#primaryKey", SqlDbType.UniqueIdentifier).Value = primaryKey;
sqlDataReader = sqlCommand.ExecuteReader();
}
sqlDataReader.Read();
string serverPath = sqlDataReader.GetSqlString(0).Value;
byte[] serverTransactionContext = sqlDataReader.GetSqlBinary(1).Value;
sqlFileStream = new SqlFileStream(serverPath, serverTransactionContext, FileAccess.Read);
}
protected override void Dispose(bool disposing)
{
sqlDataReader.Close();
sqlFileStream.Close();
sqlConnection.Close();
}
public override void Flush()
{
sqlFileStream.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
return sqlFileStream.Seek(offset, origin);
}
public override void SetLength(long value)
{
sqlFileStream.SetLength(value);
}
public override int Read(byte[] buffer, int offset, int count)
{
return sqlFileStream.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count)
{
sqlFileStream.Write(buffer, offset, count);
}
public override bool CanRead
{
get { return sqlFileStream.CanRead; }
}
public override bool CanSeek
{
get { return sqlFileStream.CanSeek; }
}
public override bool CanWrite
{
get { return sqlFileStream.CanWrite; }
}
public override long Length
{
get { return sqlFileStream.Length; }
}
public override long Position
{
get { return sqlFileStream.Position; }
set { sqlFileStream.Position = value; }
}
}
Normally I might suggest wrapping the stream in a custom stream that closes the transaction when disposed, however IIRC WCF makes no guarantees about which threads do what, but TransactionScope is thread-specific. As such, perhaps the better option is to copy the data into a MemoryStream (if it isn't too big) and return that. The Stream.Copy method in 4.0 should make that a breeze, but remember to rewind the memory-stream before the final return (.Position = 0).
Obviously this will be a big problem if the stream is big, ... but, if the stream is big enough for that to be a concern, then personally I'd be concerned at the fact that it is running in TransactionScope at all, since that has inbuilt time limits, and causes serializable isolation (by default).
A final suggestion would be to use a SqlTransaction, which is then not thread-dependent; you could write a Stream wrapper that sits around the SqlFileStream, and close the reader, transaction and connection (and the wrapped stream) in the Dispose(). WCF will call that (via Close()) after processing the results.
Hmm I might be missing something here, but it seems to me a simpler approach would be to provide the stream to the WCF method and writing to it from there, rather than trying to return a stream which the client reads from?
Here's an example for a WCF method:
public void WriteFileToStream(FetchFileArgs args, Stream outputStream)
{
using (SqlConnection conn = CreateOpenConnection())
using (SqlTransaction tran = conn.BeginTransaction(IsolationLevel.ReadCommitted))
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "usp_file";
cmd.Transaction = tran;
cmd.Parameters.Add("#FileId", SqlDbType.NVarChar).Value = args.Id;
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{
string path = reader.GetString(3);
byte[] streamContext = reader.GetSqlBytes(4).Buffer;
using (var sqlStream = new SqlFileStream(path, streamContext, FileAccess.Read))
sqlStream.CopyTo(outputStream);
}
}
tran.Commit();
}
}
In my app, the consumer happens to be an ASP.NET application, and the calling code looks like this:
_fileStorageProvider.WriteFileToStream(fileId, Response.OutputStream);
Logically none of the SQL related stuff belongs to Stream wrapper class (WcfStream) especially if you intend to send WcfStream instance to external clients.
What you could’ve done was to have an event that would be triggered once WcfStream is disposed or closed:
public class WcfStream : Stream
{
public Stream SQLStream { get; set; }
public event EventHandler StreamClosedEventHandler;
protected override void Dispose(bool disposing)
{
if (disposing)
{
SQLStream.Dispose();
if (this.StreamClosedEventHandler != null)
{
this.StreamClosedEventHandler(this, new EventArgs());
}
}
base.Dispose(disposing);
}
}
Then in you main code you would hook up an event handler to StreamClosedEventHandler and close all sql-related objects there as such:
...
WcfStream test = new WcfStream();
test.SQLStream = new SqlFileStream(filePath, txContext, FileAccess.Read);
test.StreamClosedEventHandler +=
new EventHandler((sender, args) => DownloadStreamCompleted(sqlDataReader, sqlConnection));
return test;
}
private void DownloadStreamCompleted(SqlDataReader sqlDataReader, SQLConnection sqlConnection)
{
// You might want to commit Transaction here as well
sqlDataReader.Close();
sqlConnection.Close();
}
This looks to be working for me and it keeps Streaming logic separate from SQL-related code.