Repository pattern in C# without Entity Framework - c#

I have a new project that requires me to connect to Oracle 9i. I want to use the repository pattern (which I am new at) to organise my code. I will use some stored procedures for some queries.
I want to write my code in such a way that there should be no duplication and also following the best practices.
Please check my code below and let me know if I am doing it right. I have a feeling that I am not.
I tried to read other posts on the same topic but no luck.
public interface IDeliveryRepository : IDisposable
{
IEnumerable<Delivery> GetDeliveries();
Task GetDelivery(int id);
void Insert(Delivery delivery);
void Delete(Delivery delivery);
void Update(Delivery delivery);
}
Repository:
public class DeliveryRepository: IDeliveryRepository
{
public Delivery GetDelivery(int id)
{
Delivery delivery = null;
var sql = "SELECT d.id , o.owner_id, o.name FROM delivery d JOIN owner o on o.id = d.owner_id where id = :t";
using (var con = new OracleConnection(AppConfig.CALL_CENTER_CONNECTION_STRING))
{
con.Open();
using (var cmd = new OracleCommand(sql, con))
{
cmd.BindByName = true;
cmd.Parameters.Add("t", id);
using (var oraReader = cmd.ExecuteReader())
{
while (oraReader.Read())
{
delivery = new Delivery
{
Id = oraReader.GetString(oraReader.GetOrdinal("id")),
Owner = new Owner
{
Id = oraReader.GetString(oraReader.GetOrdinal("owner_id")),
Name = oraReader.GetString(oraReader.GetOrdinal("name"))
}
};
}
}
}
}
return delivery;
}
.
.
.
.
.

You don't need to make your repository IDisposable, and to use Task you should implement your entire class with the Async programming model. I have updated your code below which should get you close to a compiling baseline that you can then extend.
// Removed the IDisposable interface
public interface IDeliveryRepository
{
IEnumerable<Delivery> GetDeliveries();
// Changed the below from a Task to a Delivery as the return type. To use Task,
// your entire implementation should be asynchronous.
Delivery GetDelivery(int id);
void Insert(Delivery delivery);
void Delete(Delivery delivery);
void Update(Delivery delivery);
}
public class DeliveryRepository: IDeliveryRepository
{
public Delivery GetDelivery(int id)
{
Delivery delivery = null;
var sql = "SELECT d.id , o.owner_id, o.name FROM delivery d JOIN owner o on o.id = d.owner_id where id = :t";
using (var con = new OracleConnection(AppConfig.CALL_CENTER_CONNECTION_STRING))
{
con.Open();
using (var cmd = new OracleCommand(sql, con))
{
cmd.BindByName = true;
cmd.Parameters.Add("t", id);
using (var oraReader = cmd.ExecuteReader())
{
while (oraReader.Read())
{
delivery = new Delivery
{
Id = oraReader.GetString(oraReader.GetOrdinal("id")),
Owner = new Owner
{
Id = oraReader.GetString(oraReader.GetOrdinal("owner_id")),
Name = oraReader.GetString(oraReader.GetOrdinal("name"))
}
};
}
}
}
}
return delivery;
}
public void Insert(Delivery delivery)
{
/// Add your code here
throw new NotImplementedException();
}
public void Delete(Delivery delivery);
{
/// Add your code here
throw new NotImplementedException();
}
public void Update(Delivery delivery);
{
/// Add your code here
throw new NotImplementedException();
}
public IEnumerable<Delivery> GetDeliveries();
{
/// Add your code here
throw new NotImplementedException();
}
}

Related

Getting startes Repository pattern with Dapper (WinForms) [duplicate]

This question already has answers here:
How to implement Generic Repository Design Pattern with Dapper?
(3 answers)
Closed 7 months ago.
I want to start using Dapper and also try to use Repository pattern.
But I don't understand how to do.
I tried this, but this is with EF Repository Pattern C#
So can anybody tell me a simple example for a gerneric repository pattern and the usage of Dapper?
What I tried/found:
IGenericRepository.cs
public interface IGenericRepository<T>
{
Task<IEnumerable<T>> GetAllAsync();
Task DeleteRowAsync(Guid id);
Task<T> GetAsync(Guid id);
Task<int> SaveRangeAsync(IEnumerable<T> list);
Task UpdateAsync(T t);
Task InsertAsync(T t);
}
GenericRepository.cs
public abstract class GenericRepository<T> : IGenericRepository<T> where T : class
{
private readonly string _tableName;
protected GenericRepository(string tablename)
{
_tableName = tablename;
}
private MySqlConnection SqlConnection()
{
return new MySqlConnection("xxx");
}
private IDbConnection CreateConnection()
{
var conn = SqlConnection();
conn.Open();
return conn;
}
private IEnumerable<PropertyInfo> GetProperties => typeof(T).GetProperties();
public async Task DeleteRowAsync(Guid id)
{
using (var connection = CreateConnection())
{
await connection.ExecuteAsync($"DELETE FROM {_tableName} WHERE Id=#Id", new { Id = id });
}
}
public async Task<IEnumerable<T>> GetAllAsync()
{
using (var connection = CreateConnection())
{
return await connection.QueryAsync<T>($"SELECT * FROM {_tableName}");
}
}
public async Task InsertAsync(T t)
{
var insertQuery = GenerateInsertQuery();
using (var connection = CreateConnection())
{
await connection.ExecuteAsync(insertQuery, t);
}
}
public async Task<int> SaveRangeAsync(IEnumerable<T> list)
{
var inserted = 0;
var query = GenerateInsertQuery();
using (var connection = CreateConnection())
{
inserted += await connection.ExecuteAsync(query, list);
}
return inserted;
}
private string GenerateInsertQuery()
{
var insertQuery = new StringBuilder($"INSERT INTO {_tableName} ");
insertQuery.Append("(");
var properties = GenerateListOfProperties(GetProperties);
properties.ForEach(prop => { insertQuery.Append($"[{prop}],"); });
insertQuery
.Remove(insertQuery.Length - 1, 1)
.Append(") VALUES (");
properties.ForEach(prop => { insertQuery.Append($"#{prop},"); });
insertQuery
.Remove(insertQuery.Length - 1, 1)
.Append(")");
return insertQuery.ToString();
}
private string GenerateUpdateQuery()
{
var updateQuery = new StringBuilder($"UPDATE {_tableName} SET ");
var properties = GenerateListOfProperties(GetProperties);
properties.ForEach(property =>
{
if (!property.Equals("Id"))
{
updateQuery.Append($"{property}=#{property},");
}
});
updateQuery.Remove(updateQuery.Length - 1, 1);
updateQuery.Append(" WHERE Id=#Id");
return updateQuery.ToString();
}
private static List<string> GenerateListOfProperties(IEnumerable<PropertyInfo> listOfProperties)
{
return (from prop in listOfProperties
let attributes = prop.GetCustomAttributes(typeof(DescriptionAttribute), false)
where attributes.Length <= 0 || (attributes[0] as DescriptionAttribute)?.Description != "ignore"
select prop.Name).ToList();
}
public async Task UpdateAsync(T t)
{
var updateQuery = GenerateUpdateQuery();
using (var connection = CreateConnection())
{
await connection.ExecuteAsync(updateQuery, t);
}
}
public async Task<T> GetAsync(Guid id)
{
using (var connection = CreateConnection())
{
var result = await connection.QuerySingleOrDefaultAsync<T>($"SELECT * FROM {_tableName} WHERE Id=#Id", new { Id = id });
if (result == null)
throw new KeyNotFoundException($"{_tableName} com o id {id} não foi encontrado");
return result;
}
}
}
Person.cs
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
PersonRepository.cs
public class PersonRepository : GenericRepository<Person>
{
public PersonRepository(string tablename) : base(tablename) { }
}
Now my problem:
What should I do to fill a dataGridView with all persons?
Is there a need to change something in the PersonRepository.cs file? What?
Ans how does the code in my form look like? e.g. dataGridView1.DataSource = ... and also filter this?
And for example, if I add another Class Car, what points I have to do?
Add another CarRepository.cs and Car.cs? Anything else?
Kind regards!
First of all you need to understand the differences between EF and Dapper. Basically Dapper is a micro-ORM that has minimal features compared with EF. Entity Framework has a lot of features included along with performance improvements and others (more details here).
As the dapper is a Micro-ORM you don't have the same features available in EF. You can use dapper.contrib that has some abstractions that helps you with generic repository.
I have an example in my github an application that use dapper in repository (without dapper.contrib) with clean arch.

Get source of an call function, invocation or task execution

In my app, I use an class (MySqlClass) to execute and download data from my database.
public class MySqlClass
{
public void ExecuteQuery(string query) { /* ... */ }
public DataSet GetDataSet(string query) { /* ... */ }
public void Transaction(Action queryToCommit, Action whenRollback) { /* ... */ }
}
For example :
public class MyApp
{
List<MyObjectClass> myList = MyObjectClass.GetMyObjectClass("white");
}
public class MyObjectClass
{
private static MySqlClass sqlConn = new MySqlClass();
public static List<MyObjectClass> GetMyObjectClass(string color)
{
List<MyObjectClass> obj = new List<MyObjectClass>();
using (DataSet ds = sqlConn.GetDataSet(" ... my query ... "))
{
foreach (DataRow dr in ds.Tables[0].Rows)
{
obj.Add(ConvertDataRow(dr));
}
}
return obj;
}
public static MyObjectClass ConvertDataRow(DataRow dr) { /* ... */ }
}
For some criticals utilizations, I want use transaction WITHOUT modify originals functions.
I developed for this "Transaction" function in MySqlClass :
public void Transaction(Action queryToCommit, Action whenRollback)
{
CancellationTokenSource cts = new CancellationTokenSource();
try
{
Task executeAction = new Task(new Action(() =>
{
queryToCommit.Invoke(); // user code executed here
cts.Token.ThrowIfCancellationRequested();
}),
cts.Token
);
executeAction.Start();
executeAction.Wait(cts.Token);
Commit();
}
catch (Exception)
{
Rollback();
whenRollback.Invoke();
/* ... */
}
}
In my app :
public class MyApp
{
MySqlClass sqlConn = new MySqlClass();
List<MyObjectClass> myList = MyObjectClass.GetMyObjectClass("white");
private void WithoutTransaction()
{
MyObjectClass objA = new MyObjectClass();
MyObjectClass objB = new MyObjectClass();
/* ... */
if (...)
{
objA.Insert();
}
else
{
objA.Delete();
objB.Update();
// Exception raised in objB.Update();
// Data correspond to objA in database are lost
}
}
private void WithTransaction()
{
MyObjectClass objA = new MyObjectClass();
MyObjectClass objB = new MyObjectClass();
sqlConn.Transaction(
() => {
if (...)
{
objA.Insert();
}
else
{
objA.Delete();
objB.Update(); // Exception raised in objB.Update()
}
},
CallErrorLogFunction()
);
}
}
My problem :
How i can know, when I use GetDataSet(), ExecuteQuery() or whatever, that I'm in a transaction function ?
StackTrace and StackFrame indicate invocation but not the source function of invocation.
I can't use Thread (for current thread ID) because my colleagues could make operations on the GUI.
I thought to lock instruction to check if in a transaction but I no idea to implement the code
If you have an idea ... :)
Thanks !
EDIT March 19'21 :
public class MySqlClass
{
// They are initialized before use "ExecuteQuery()"
public MyTransactionClass transac;
public SqlConnection conn;
public void ExecuteQuery(string query, Dictionary<string, object> dict = null)
{
SqlDataAdapter da;
try
{
if (conn.State = ConnectionState.Closed)
{
conn.Open();
}
da = new SqlDataAdapter(query, conn);
/* section of code for parameters queries */
if (dict != null) { /* ... */ }
if (IsTransaction())
{
da.SelectCommand.Transaction = transac.TransacSql;
}
da.SelectCommand.ExecuteNonQuery();
}
catch (Exception)
{
throw;
}
finally
{
if (da != null)
{
da.Dispose();
}
}
}
public DataSet GetDataSet(string query) { /* ... */ }
private bool IsTransaction()
{
bool r = false;
if (transac != null)
{
/*
* Check if in StackTrace, "Transaction" of "MyTransactionClass" is in the list
*/
StackTrace st = new StackTrace(true);
StackFrame sf;
for (int i = 0; i < st.FrameCount; i++)
{
sf = st.GetFrame(i);
if (sf.GetMethod().Name == MyTransactionClass.FunctionName_Transaction)
{
r = true;
break;
}
}
}
return r;
}
}
public class MyTransactionClass
{
public static readonly string FunctionName_Transaction = nameof(Transaction);
public SqlTransaction TransacSql;
public void Transaction(Action queryToCommit, Action whenRollback)
{
CancellationTokenSource cts = new CancellationTokenSource();
try
{
Task executeAction = new Task(new Action(() =>
{
queryToCommit.Invoke();
cts.Token.ThrowIfCancellationRequested();
}),
cts.Token
);
executeAction.Start();
executeAction.Wait(cts.Token);
Commit();
}
catch (Exception)
{
Rollback();
whenRollback.Invoke();
/* ... */
}
}
void Commit() { }
void Rollback() { }
}
EDIT March 31'21 :
I make a unit test, with different use cases :
with just a transaction
a transaction and a Action
a transaction with a Action in a Task
Is the last case (similar to I want), System.Transactions.Transaction.Current is null.
[TestMethod]
public void TestTransactionScope()
{
Console.WriteLine($"Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
using (TransactionScope scope = new TransactionScope())
{
Console.WriteLine($"(scope) Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
MyFunction("direct call");
}
// if I use a invocation
Action<string> fnctTransac = new Action<string>((msg) => MyFunction(msg));
using (TransactionScope scope = new TransactionScope())
{
Console.WriteLine($"(scope) Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
fnctTransac.Invoke("with invocation");
}
// if I use invocation with a Task, similar to my use
using (TransactionScope scope = new TransactionScope())
{
Console.WriteLine($"(scope) Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
Task t = new Task(() => fnctTransac.Invoke("with invocation, from a Task"));
t.Start();
Task.WaitAll(t);
}
}
public void MyFunction(string msg)
{
Console.WriteLine($"{msg} - Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
}
Logs :
Transaction ID :
(scope) Transaction ID : 1c0127fe-44d5-4954-826c-ece6ad261ee5:1
direct call - Transaction ID : 1c0127fe-44d5-4954-826c-ece6ad261ee5:1
(scope) Transaction ID : 1c0127fe-44d5-4954-826c-ece6ad261ee5:2
with invocation - Transaction ID : 1c0127fe-44d5-4954-826c-ece6ad261ee5:2
(scope) Transaction ID : 1c0127fe-44d5-4954-826c-ece6ad261ee5:3
with invocation, from a Task - Transaction ID :
EDIT April 16'21
With TransactionScope :
Source :
public class MySqlClass
{
public MyTransactionClass transac;
public SqlConnection conn;
public void ExecuteQuery(string query, Dictionary<string, object> dict = null) { /* ... */ }
public DataSet GetDataSet(string query) { /* ... */ }
private bool IsTransaction()
{
bool r = false;
string idTransaction = Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier
if (
transac == null &&
!string.IsNullOrEmpty(idTransaction)
)
{
transac = MyTransactionClass.GetTransaction(idTransaction)
}
return r;
}
}
public class MyTransactionClass
{
public const int MAX_TIME_TRANSAC_SEC = 5
public static readonly string FunctionName_Transaction = nameof(Transaction);
public SqlTransaction TransacSql;
public static void Transaction(Action queryToCommit, Action whenRollback)
{
CancellationTokenSource cts = new CancellationTokenSource();
try
{
Task executeAction = new Task(new Action(() =>
{
queryToCommit.Invoke();
cts.Token.ThrowIfCancellationRequested();
}),
cts.Token
);
using (TransactionScope scope = new TransactionScope(
TransactionScopeOption.Required,
New TimeSpan(0, 0, MAX_TIME_TRANSAC_SEC),
TransactionScopeAsyncFlowOption.Enabled
)
{
executeAction.Start();
executeAction.Wait(cts.Token);
scope.Complete();
Commit();
}
}
catch (Exception)
{
Rollback();
whenRollback.Invoke();
/* ... */
}
}
void Commit() { }
void Rollback() { }
}
Tests :
[TestMethod]
public void E_TransactionScope()
{
Console.WriteLine($"Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
Console.WriteLine($"(scope) Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
MyFunction("direct call");
}
Console.WriteLine();
// if I use a invocation
Action<string> fnctTransac = new Action<string>((msg) => MyFunction(msg));
using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
Console.WriteLine($"(scope) Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
fnctTransac.Invoke("with invocation");
}
Console.WriteLine();
// if I use invocation with a Task, similar to my use
using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
Console.WriteLine($"(scope) Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
Task t = new Task(() => fnctTransac.Invoke("with invocation, from a Task"));
t.Start();
Task.WaitAll(t);
}
Console.WriteLine();
// ultimate use case
Action userCode = () => MyFunction_First("last use case");
Task tk;
using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
Console.WriteLine($"(scope) Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
// TransactionID not available when userCode is defined out of using instruction and called directly
// userCode.Start();
tk = new Task(new Action(() => userCode.Invoke()));
tk.Start();
tk.Wait();
}
Console.WriteLine("-------------------------");
}
public void MyFunction(string msg)
{
Console.WriteLine($"{msg} - Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
}
public void MyFunction_First(string msg)
{
Console.WriteLine($"MyFunction_First - {msg} - Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
Task t = new Task(() => MyFunction_Second(msg));
t.Start();
Task.WaitAll(t);
}
public void MyFunction_Second(string msg)
{
Console.WriteLine($"MyFunction_Second - {msg} - Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
Thread th = new Thread(new ThreadStart(() => MyFunction_Third(msg)));
th.Start();
th.Join();
}
public void MyFunction_Third(string msg)
{
using (TransactionScope scope = new TransactionScope())
{
Console.WriteLine($"MyFunction_Third - {msg} - Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
Task t = new Task(() => MyFunction_Fourth(msg));
t.Start();
Task.WaitAll(t);
}
}
public void MyFunction_Fourth(string msg)
{
Console.WriteLine($"MyFunction_Fourth - {msg} - Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
Task t = new Task(() => MyFunction_Last(msg));
t.Start();
Task.WaitAll(t);
}
public void MyFunction_Last(string msg)
{
Console.WriteLine($"MyFunction_Last - {msg} - Transaction ID : {System.Transactions.Transaction.Current?.TransactionInformation.LocalIdentifier}");
}
Logs :
Transaction ID :
(scope) Transaction ID : 99e1d658-27d8-404d-b57e-d6ead2e2308e:1
direct call - Transaction ID : 99e1d658-27d8-404d-b57e-d6ead2e2308e:1
(scope) Transaction ID : 99e1d658-27d8-404d-b57e-d6ead2e2308e:2
with invocation - Transaction ID : 99e1d658-27d8-404d-b57e-d6ead2e2308e:2
(scope) Transaction ID : 99e1d658-27d8-404d-b57e-d6ead2e2308e:3
with invocation, from a Task - Transaction ID : 99e1d658-27d8-404d-b57e-d6ead2e2308e:3
(scope) Transaction ID : 99e1d658-27d8-404d-b57e-d6ead2e2308e:4
MyFunction_First - last use case - Transaction ID : 99e1d658-27d8-404d-b57e-d6ead2e2308e:4
MyFunction_Second - last use case - Transaction ID : 99e1d658-27d8-404d-b57e-d6ead2e2308e:4
MyFunction_Third - last use case - Transaction ID : 99e1d658-27d8-404d-b57e-d6ead2e2308e:4
MyFunction_Fourth - last use case - Transaction ID :
MyFunction_Last - last use case - Transaction ID :
Have you already considered using the TransactionScope class?
I'm not sure, but with that, you might not need to know whether you are in a transaction or not inside your ExecuteQuery and GetDataSet methods. It might just simply work. ;)
Your Transaction method could look something like this:
public void Transaction(Action queryToCommit, Action whenRollback = null)
{
if (queryToCommit == null)
{
throw new ArgumentNullException(nameof(queryToCommit));
}
CancellationTokenSource cts = new CancellationTokenSource();
Task executeAction = new Task(new Action(() =>
{
queryToCommit.Invoke(); // user code executed here
cts.Token.ThrowIfCancellationRequested();
}),
cts.Token
);
try
{
using (var scope = new System.Transactions.TransactionScope())
{
executeAction.Start();
executeAction.Wait(cts.Token);
scope.Complete();
}
}
catch (Exception)
{
whenRollback.Invoke();
/* ... */
}
}
Note that I haven't tested this. I just based it on your code and the sample in the documentation, so it probably needs some additional work.
Edit:
Based on recent comments, I added a code snippet that shows how I would personally implement your logic:
public class MySqlClass
{
private string connectionString;
public MySqlClass(string connectionString)
{
this.connectionString = connectionString;
}
public int ExecuteQuery(string query, params SqlParameter[] parameters)
{
using (SqlConnection con = new SqlConnection(connectionString))
using (SqlCommand cmd = new SqlCommand(query, con))
{
con.Open();
cmd.Parameters.AddRange(parameters);
return cmd.ExecuteNonQuery();
}
}
public DataSet GetDataSet(string query, params SqlParameter[] parameters)
{
using (SqlConnection con = new SqlConnection(connectionString))
using (SqlCommand cmd = new SqlCommand(query, con))
{
con.Open();
cmd.Parameters.AddRange(parameters);
DataSet ds = new DataSet();
using (SqlDataAdapter da = new SqlDataAdapter(cmd))
{
da.Fill(ds);
}
return ds;
}
}
public DataTable GetDataTable(string query, params SqlParameter[] parameters)
{
DataSet ds = GetDataSet(query, parameters);
return ds.Tables[0];
}
public static void Transaction(Action queryToCommit, Action whenRollback)
{
if (queryToCommit == null)
{
throw new ArgumentNullException(nameof(queryToCommit));
}
CancellationTokenSource cts = new CancellationTokenSource();
Task executeAction = new Task(new Action(() =>
{
queryToCommit.Invoke(); // user code executed here
cts.Token.ThrowIfCancellationRequested();
}),
cts.Token
);
try
{
using (var scope = new System.Transactions.TransactionScope())
{
executeAction.Start();
executeAction.Wait(cts.Token);
scope.Complete();
}
}
catch
{
whenRollback.Invoke();
/* ... */
}
}
public static SqlParameter CreateParameter(string parameterName, SqlDbType dbType, object value)
{
SqlParameter parameter = new SqlParameter(parameterName, dbType);
parameter.Value = value;
return parameter;
}
public static SqlParameter CreateParameter(string parameterName, SqlDbType dbType, int size, object value)
{
SqlParameter parameter = new SqlParameter(parameterName, dbType, size);
parameter.Value = value;
return parameter;
}
}
/*
CREATE TABLE [MyObjects] (
[Id] INT IDENTITY NOT NULL,
[Color] VARCHAR(20) NOT NULL,
--...
CONSTRAINT [PK_MyObjects] PRIMARY KEY ([Id])
);
*/
public class MyObjectModel
{
// Sample private fields
public int? Id { get; set; }
public string Color { get; set; }
/* ... */
}
public class MyObjectRepository
{
private MySqlClass sqlConn;
public MyObjectRepository(MySqlClass sqlConn)
{
this.sqlConn = sqlConn;
}
public List<MyObjectModel> GetMyObjects(string colorFilter)
{
List<MyObjectModel> obj = new List<MyObjectModel>();
string query = (string.IsNullOrWhiteSpace(colorFilter))
? "SELECT [Id], [Color] FROM [MyObjects];"
: "SELECT [Id], [Color] FROM [MyObjects] WHERE [Color] LIKE '%' + #Color + '%';";
using (DataTable dt = sqlConn.GetDataTable(query,
MySqlClass.CreateParameter("#Color", SqlDbType.VarChar, 20, colorFilter)))
{
foreach (DataRow dr in dt.Rows)
{
obj.Add(ConvertDataRow(dr));
}
}
return obj;
}
private static MyObjectModel ConvertDataRow(DataRow dr)
{
return new MyObjectModel
{
/* set class properties here... */
Id = dr.Field<int>("Id"),
Color = dr.Field<string>("Color")
/* ... */
};
}
public void Insert(MyObjectModel model)
{
if (model.Id.HasValue)
{
return;
}
using (DataTable dt = sqlConn.GetDataTable("INSERT INTO [MyObjects] ([Color]) OUTPUT INSERTED.[Id] VALUES (#Color);",
MySqlClass.CreateParameter("#Color", SqlDbType.VarChar, 20, model.Color)))
{
model.Id = (int)dt.Rows[0]["Id"];
}
}
public int? Update(MyObjectModel model)
{
if (!model.Id.HasValue)
{
return null;
}
return sqlConn.ExecuteQuery("UPDATE [MyObjects] SET [Color] = #Color WHERE [Id] = #Id;",
MySqlClass.CreateParameter("#Id", SqlDbType.Int, model.Id),
MySqlClass.CreateParameter("#Color", SqlDbType.VarChar, 20, model.Color));
}
public int Delete(int id)
{
return sqlConn.ExecuteQuery("DELETE FROM [MyObjects] WHERE [Id] = #Id;",
MySqlClass.CreateParameter("#Id", SqlDbType.Int, id));
}
}
public class MyApp
{
private MyObjectRepository repo;
public MyApp()
{
MySqlClass sqlConn = new MySqlClass(" ... connection string here ... ");
repo = new MyObjectRepository(sqlConn);
}
List<MyObjectModel> GetMyObjects(string colorFilter = "white")
{
return repo.GetMyObjects(colorFilter);
}
private void WithoutTransaction()
{
MyObjectModel objA = new MyObjectModel();
MyObjectModel objB = new MyObjectModel();
/* ... */
if (true)
{
repo.Insert(objA);
}
else
{
repo.Delete(objA.Id.Value);
repo.Update(objB);
// Exception raised in objB.Update();
// Data correspond to objA in database are lost
}
}
private void WithTransaction()
{
MyObjectModel objA = new MyObjectModel();
MyObjectModel objB = new MyObjectModel();
MySqlClass.Transaction(
() =>
{
if (true)
{
repo.Insert(objA);
}
else
{
repo.Delete(objA.Id.Value);
repo.Update(objB); // Exception still raised now???
}
},
CallErrorLogFunction
);
}
private void CallErrorLogFunction()
{
/* ... */
}
}
From recent edits to your question, I am not sure if this would be a valid solution, however. This is the best I could come up with so far. Perhaps you can extract something useful from it anyway. (I haven't tested it, so despite me being careful, the code might contain some stupid mistakes.)

How can I improve this working sqldependency implementation?

I'm a beginner with C# MVC (about 6 months of self-learning), currently working on my first project, and I recently came across the problem of an SQL dependency duplicating when the user refreshes a page.
Through looking at various posts and articles across sites, I have cobbled together a working implementation, but I'm still getting my head around some of the elements of it, especially the singleton design pattern and the various class and property modifiers.
I would like to know, are there any gaping holes in my code, or any other considerations I should be making as I plan to set up a few more dependencies?
To give a bit of background to the project scale, it will be used by up to 500 users at a time, and the data affecting the dependency will change up to around once a second during busy periods.
Many thanks in advance. :)
Here is my dependency class:
public sealed class UMCSingleton
{
private static UMCSingleton instance;
private static string connString = ConfigurationManager.ConnectionStrings["ServerConnection"].ConnectionString;
private static SqlConnection conn = new SqlConnection(connString);
private SqlDependency dependency;
private static volatile object padlock = new object();
private UMCSingleton() {}
public static UMCSingleton Instance
{
get
{
if (instance == null)
{
lock (padlock)
{
if (instance == null)
{
instance = new UMCSingleton();
}
}
}
return instance;
}
}
public static SqlConnection getUMCConnection()
{
try
{
if(conn == null)
{
conn = new SqlConnection(connString);
conn.Open();
return conn;
}
if(conn.State == ConnectionState.Closed)
{
conn.ConnectionString = connString;
conn.Open();
}
}
catch (SqlException e) {}
finally {}
return conn;
}
public void RegisterUnmanagdCallDependency()
{
using (getUMCConnection())
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandType = CommandType.Text;
cmd.CommandText = "SELECT [CallRef], [CallLoggedByUserId] FROM [dbo].[UnplannedCustomers] Where [ServiceFlowId] = 1";
cmd.Notification = null;
if (dependency == null || dependency.HasChanges == true)
{
dependency = new SqlDependency(cmd);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
}
using (SqlDataReader reader = cmd.ExecuteReader()) {}
}
}
}
void dependency_OnChange(object sender, SqlNotificationEventArgs e) //this will be called when any changes occur in db table.
{
if (e.Type == SqlNotificationType.Change)
{
SqlDependency dependency = (SqlDependency)sender;
if (dependency.HasChanges == true)
{
dependency.OnChange -= dependency_OnChange;
}
UnmanagedCallHub.ShowCalls();
RegisterUnmanagdCallDependency();
}
}
}
Here is my hub class:
public class UnmanagedCallHubData
{
public string CallRef { get; set; }
public string RaisedBy { get; set; }
}
public class UnmanagedCallHub : Hub
{
private ApplicationDbContext _context;
public UnmanagedCallHub()
{
_context = new ApplicationDbContext();
}
public static void ShowCalls()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<UnmanagedCallHub>();
context.Clients.All.UpdateCalls();
}
}
And finally my api controller:
public class UnmanagedCallController : ApiController
{
private ApplicationDbContext _context;
private UserProfile _uPro;
//CONSTRUCTOR SETS DB CONTEXT
public UnmanagedCallController()
{
_context = new ApplicationDbContext();
ApplicationUser user = HttpContext.Current.GetOwinContext()
.GetUserManager<ApplicationUserManager>().FindById(HttpContext.Current.User.Identity.GetUserId());
_uPro = _context.UserProfiles.SingleOrDefault(x => x.UserId == user.Id);
}
[HttpGet]
[Authorize]
public List<UnmanagedCallHubData> GetUnmanagedCalls()
{
List<UnmanagedCallHubData> calls = _context.UnplannedCustomers
.Where(x => x.ServiceFlowId == 1)
.Where(x => x.CurrentManagerUserId == null)
.Select(x => new UnmanagedCallHubData
{
CallRef = x.CallRef,
RaisedBy = x.CallLoggedByUserId
}).ToList();
UMCSingleton.Instance.RegisterUnmanagdCallDependency();
return calls;
}
}

C#: How to Wrap my Unit of Work code in my repository classes

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.

Populating Interface

Can anyone tell me how do I populate my fields in my interface IAccount? I'm having an error in x.Add(new IAccount ...
public class IPersonRepo : IAccount
{
string connectionstring = #"Server=SLI002/SQLEXPRESS;Database=atengturonDB;Trusted_Connection=true;";
public int AccountsID
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public byte[] AccountUserName
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public byte[] AccountPassword
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public byte[] AccountSalt
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public void getAccount()
{
SqlConnection conn = new SqlConnection(connectionstring);
using (SqlCommand comm = new SqlCommand())
{
List<IAccount> x = new List<IAccount>();
comm.Connection = conn;
comm.CommandText = "Select AccountsID,AccountsUserName,AccountsPassword,AccountsSalt from Accounts";
comm.CommandType = CommandType.Text;
SqlDataReader reader = null;
conn.Open();
comm.ExecuteNonQuery();
reader = comm.ExecuteReader();
while (reader.Read())
{
x.Add(new IAccount
{
AccountsID = (int)reader["AccountsID"],
AccountUserName = (byte[])reader["AccountsUserName"],
AccountPassword = (byte[])reader["AccountsPassword"],
AccountSalt = (byte[])reader["AccountsSalt"]
});
}
conn.Close();
}
}
}
Please, rename IPersonRepo to PersonRepo, prefix I means interface, but clearly it is class. Second, it doesnt look like repo (=repository) but like Person (but this is debatable... :) )
Third, you are trying to create interface - but you have to instance class which implements that interface:
//x.Add(new IAccount
//x.Add(new IPersonRepo
//x.Add(new PersonRepo
x.Add(new Person
{
AccountsID = (int)reader["AccountsID"],
AccountUserName = (byte[])reader["AccountsUserName"],
AccountPassword = (byte[])reader["AccountsPassword"],
AccountSalt = (byte[])reader["AccountsSalt"]
});
Fourth and last, maybe you should take a look at any ORM, like NHibernate or Entity Framework. Can help you, but its your call :)
First, never use the I prefix when deciding on a class name. It's not a compilation error but it's very confusing since the convention is to use I as a prefix to interface names.
So your class should be called PersonRepo and not IPersonRepo.
(you can, however, name a class that starts with I like (Ice), just don't use I as a prefix)
Second, you can't instantiate an interface. you can use a variable of the interface type but instantiate the actual class: IAccount MyAccount = new PersonRepo();

Categories

Resources