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.
Related
I am trying to unit test our DB layer's stored procedures/functions using OrmLite's ScalarAsync(), for example, with the PostgreSQL dialect. (I tried using SqlLite in-memory but it doesn't do stored procedures or functions.)
I found some hints in the unit-tests for OrmLite on GitHub as well as an article which points to them.
Here's what I have:
[Fact]
public async Task TestMyMethod_CallsMyStoredProcedure()
{
// Arrange
Mock<IDashboardDbConnectionFactory> mockConnFac = new();
MockDialectProvider prov = new();
prov.ExecFilter = new MockStoredProcExecFilter();
OrmLiteConnectionFactory dbFactory =
new(
"User ID=asdf;Password=asdf;Host=localhost;Port=5432;Database=asdf;Pooling=true;Connection Lifetime=0;",
prov, false);
OrmLiteConfig.ExecFilter = new MockStoredProcExecFilter();
mockConnFac.Setup(m => m.OpenAsync(It.IsAny<ISecurityContext>()))
.Returns(async () =>
{
OrmLiteConnection asdf = new(dbFactory);
OrmLiteConfig.ExecFilter = new MockStoredProcExecFilter();
await asdf.OpenAsync();
return asdf;
});
mockConnFac.Setup(m => m.Open(It.IsAny<ISecurityContext>()))
.Returns(() =>
{
OrmLiteConnection asdf = new(dbFactory);
OrmLiteConfig.ExecFilter = new MockStoredProcExecFilter();
asdf.Open();
return asdf;
});
// Act
MyDataLayerCLass target = new(mockConnFac.Object, new NullLoggerFactory());
bool test1 =
await target.ExecMyStoredProcAsync(new Mock<ISecurityContext>().Object, Guid.NewGuid());
// Assert
Assert.True(test1);
}
private class MockDialectProvider : PostgreSqlDialectProvider
{
public MockDialectProvider()
{
base.ExecFilter = new MockStoredProcExecFilter();
}
public new IOrmLiteExecFilter ExecFilter { get; set; } = new MockStoredProcExecFilter();
}
private class MockStoredProcExecFilter : OrmLiteExecFilter
{
public override T Exec<T>(IDbConnection dbConn, Func<IDbCommand, T> filter)
{
try
{
T val = base.Exec(dbConn, filter);
if (dbConn.GetLastSql() == "select central_data.my_stored_function(#UserId, #ParentId)")
return (T)(object)true;
return val;
}
catch (Exception)
{
if (dbConn.GetLastSql() == "select central_data.my_stored_function(#UserId, #ParentId)")
return (T)(object)true;
throw;
}
}
public override async Task<T> Exec<T>(IDbConnection dbConn, Func<IDbCommand, Task<T>> filter)
{
try
{
// This is where the breakpoint hits. Returns false b/c the ids
// don't match actual records in the DB.
T val = await base.Exec(dbConn, filter);
if (dbConn.GetLastSql() == "select central_data.my_stored_function(#UserId, #ParentId)")
return (T)(object)true;
return val;
}
catch (Exception)
{
string sql = dbConn.GetLastSql();
if (sql == "select central_data.my_stored_function(#UserId, #ParentId)")
{
return (T)(object)true;
}
throw;
}
}
}
The problem is that it requires a valid connection to a valid database. So it's really an integration test when what I want is a unit test. Is there a way to run the dialect provider without an open connection?
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();
}
}
We are currently using dapper ORM to access data by calling store procedures. The current code is having a class BusinessFunctions which inherits another class DataFunctions which are having helper methods to execute the stored procedures.
I am not happy with this code. It's just too rigid and not future proof. And above all it's not coded to an interface rather coded to an implementation. I propose an interface IRepository with an abstract class Repository which implements all helper generic methods. Then I create BusinessRepository that implements the abstract Repository class and call the generic helpers method. Again, my colleague is telling me to remove the IRepository interface and just use the Repository abstract class.
public class BusinessFunctions : DataFunctions
{
public BusinessFunctions(ConnectionManager conMgr, LogWriter logWriter, AppUser appUser) : base(conMgr, logWriter, appUser)
{
}
public async Task<Business> FindAsync(int businessId)
{
throw new NotImplementedException();
}
public async Task<Business> FindAsync(string businessGuid)
{
var lst = await StoredProcQueryAsync<Business>("spBusinessGetSetupGUID", new { BusinessGuid = businessGuid });
if (lst.Count() == 0)
throw new NotFoundInDatabaseException("Business", businessGuid);
else
return lst.Single();
}
public async Task<bool> IsHostedTokenizeCardAllowedAsync(string businessGuid)
{
var b = await FindAsync(businessGuid);
if (b.HostedPaymentEnabled)
return true;
else
return false;
}
}
public class DataFunctions : IDisposable
{
private ConnectionManager _conMgr;
private LogWriter _logWriter;
private AppUser _appUser;
public ConnectionManager ConnMgr
{
get { return _conMgr; }
}
protected LogWriter Logger
{
get { return _logWriter; }
}
protected AppUser User
{
get { return _appUser; }
}
public DataFunctions(ConnectionManager conMgr, LogWriter logWriter, AppUser appUser)
{
_conMgr = conMgr;
_logWriter = logWriter;
_appUser = appUser;
}
public void Dispose()
{
}
public async Task StoredProcExecuteNonQueryAsync(string storedProc,
List<StoredProcParameter> storedProcParameters = null,
SqlCommandTimeout commandTimeout = SqlCommandTimeout.Default,
SqlAccessType accessType = SqlAccessType.ReadWrite
)
{
using (SqlConnection conn = new SqlConnection(ConnMgr.SqlConnectionString))
{
await conn.OpenAsync();
await StoredProcExecuteNonQueryAsync(conn,
storedProc,
storedProcParameters: storedProcParameters,
commandTimeout: commandTimeout,
accessType: accessType);
}
}
public async Task StoredProcExecuteNonQueryAsync(SqlConnection conn,
string storedProc,
List<StoredProcParameter> storedProcParameters = null,
SqlCommandTimeout commandTimeout = SqlCommandTimeout.Default,
SqlAccessType accessType = SqlAccessType.ReadWrite,
SqlTransaction trans = null
)
{
using (SqlCommand cmd = new SqlCommand(storedProc, conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = (int)commandTimeout;
if (trans != null) cmd.Transaction = trans;
if (storedProcParameters != null)
{
foreach(var p in storedProcParameters)
{
cmd.Parameters.Add(p.ToSqlParameter());
}
}
await cmd.ExecuteNonQueryAsync();
}
}
public async Task<IEnumerable<T>> StoredProcQueryAsync<T>(string storedProc,
object storedProcParameters = null,
SqlCommandTimeout commandTimeout = SqlCommandTimeout.Default,
SqlAccessType accessType = SqlAccessType.ReadWrite)
{
using (SqlConnection conn = new SqlConnection(ConnMgr.SqlConnectionString))
{
conn.Open();
return await StoredProcQueryAsync<T>(conn,
storedProc,
storedProcParameters,
commandTimeout);
}
}
public async Task<IEnumerable<T>> StoredProcQueryAsync<T>(SqlConnection conn,
string storedProc,
object storedProcParameters = null,
SqlCommandTimeout commandTimeout = SqlCommandTimeout.Default)
{
return await conn.QueryAsync<T>(storedProc,
commandType: CommandType.StoredProcedure,
commandTimeout: (int)commandTimeout,
param: storedProcParameters);
}
}
I think the reason you're unhappy with the code is that it seems to be intermingling service functionality into the repository layer. The repository layer should simply call the stored procedure.
public async Task<bool> IsHostedTokenizeCardAllowedAsync(string businessGuid)
{
var b = await FindAsync(businessGuid);
if (b.HostedPaymentEnabled)
return true;
else
return false;
}
This for example is a good candidate to be in the service layer.
Your repo layer should really just have your ConnectionManager or a Connection factory injected via IoC.
The trick we use is to put an attribute on data model fields that we know are going to be stored procedure parameters (usually most or all). Then we have an extension method that reflects over the attributes and pulls the fields, values, and types creating a dapper DynamicParameters object. Most of our repository calls look like this:
public async Task<User> AddUserAsync(UserAdd user)
{
using (var connection = _connectionFactory.Create()
{
var result = connection.ExecuteAsync("dbo.AddUser", user.GetParameters(), commandType: CommandType.StoredProcedure";
return result;
}
}
It's relatively quick and easy to use. Gets are very easy to test. Inserts/Deletes/Updates not so much. You get into needing to mock the SqlConnection which can be problematic.
In addition, if you get into more complex areas that are subject to change, you can use the strategy pattern to move methods into their own classes. Below would be an example of splitting your add method into its own class:
public class MyRepository
{
private readonly IAddMethod<UserAdd> _addMethod;
private readonly IConnectionFactory _connectionFactory;
public MyRepository(IAddMethod<UserAdd> userAddMethod,
IConnectionFactory connectionFactory)
{
//..guard clauses, assignments, etc.
}
public async Task<int> AddAsync(UserAdd user)
{
return _addMethod.AddAsync(user);
}
}
You can even decorate these strategy methods in IoC to hide/augment them as needed. (in structuremap it's .DecorateAllWith()
In short, move any logic to the service layer, consider a generic extension method for creating your DynamicParameters list, and inject the connection factory via IoC. I think you'll find the separation of concerns will simplify things significantly.
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 have a repository like this :
public abstract class DbRepository : IDbRepository
{
public TEntity Insert<TEntity>(TEntity entity) where TEntity : class
{
_context.Entry(entity).State = EntityState.Added;
return entity;
}
public TEntity Update<TEntity>(TEntity entity) where TEntity : class
{
_context.Entry(entity).State = EntityState.Modified;
return entity;
}
}
Service Contract is like :
[ServiceContract]
public interface IDbRepository
{
[OperationContract]
TEntity Insert<TEntity>(TEntity entity) where TEntity : class;
[OperationContract]
TEntity Update<TEntity>(TEntity entity) where TEntity : class;
}
Now I know I cant send this via wcf, I have to make the open generic class clossed.
But the problem is I have many entities in my Domain data repository and I want it should be decided by the client what entity it is needed may be via reflection or predefined known types.
So my question :
Is there a smart or fake way to send these generics service via wcf ?
My Goal is I dont want to write this servicecontract for each and every entity.
Many thanks.
Edit: Guys have you seen this Here Tweak in app.config file below:
<endpoint
address="myAddress" binding="basicHttpBinding"
bindingConfiguration="myBindingConfiguration1"
contract="Contracts.IEntityReadService`1[[Entities.mySampleEntity, Entities]], Service.Contracts" />
Can somebody please explain this how this contract has been implemented.
Has anybody tried to implement this tweak in app.config file. I have tried but not working for me for now. Need helpful answer !
Have you look into WCF Data Services? This seems to be the route you want to go down without hand crafting the interfaces and plumbing yourself.
As you have stated, interfaces are not good over WCF. One particular flaw is the expectation of the IQueryable<T> over WCF, which does not work at all. Even IEnumerable<T> doesn't give the expected results all of the time.
Is there a smart or fake way to send these generics service via wcf ?
My Goal is I dont want to write this servicecontract for each and
every entity. Many thanks.
hmm, why not?
Lets us try the following :
This interface is necessary as it will identify which objects can be used by your Repository.I don't know what your implementation of your T Entity is or how your CRUD operations work; however, just in case you don't have it covered, we're also going to add the methid GetPrimaryKeys.
public interface IRepositoryEntry
{
IList<String> GetPrimaryKeys();
}
So now we need a repository, since your biggest concern is that you don't want to rewrite code you should try something like this:
This implementation means that whatever our database entries are, they must support the default constructor. This is important for the implementation of this this interface :
public interface IRepository<T> where T : IRepositoryEntry, new()
{
event EventHandler<RepositoryOperationEventArgs> InsertEvent;
event EventHandler<RepositoryOperationEventArgs> UpdateEvent;
event EventHandler<RepositoryOperationEventArgs> DeleteEvent;
IList<String> PrimaryKeys { get; }
void Insert(T Entry);
void Update(T Entry);
void Delete(Predicate<T> predicate);
bool Exists(Predicate<T> predicate);
T Retrieve(Predicate<T> predicate);
IEnumerable<T> RetrieveAll();
}
Now we will make our service:
[ServiceContract]
public interface IDbRepository
{
[OperationContract]
object Insert(object entity);
[OperationContract]
object Update(object entity);
}
Notice no generics? That's important. Now we need to have a creative implementation for our Repository. I'm going to give two, one for Memory so unit testing can be done and the other for a database.
public class OracleRepository
{
const string User = "*";
const string Pass = "*";
const string Source = "*";
const string ConnectionString = "User Id=" + User + ";" + "Password=" + Pass + ";" + "Data Source=" + Source + ";";
public static IDbConnection GetOpenIDbConnection(){
//Not really important; however, for this example I Was using an oracle connection
return new OracleConnection(ConnectionString).OpenConnection();
}
protected IEnumerable<String> GetEntryPropertyNames(Type type){
foreach (var propInfo in type.GetProperties())
yield return propInfo.Name;
}
}
public class OracleRepository<T> : OracleRepository,IDisposable, IRepository<T> where T : IRepositoryEntry, new()
{
#region Public EventHandlers
public event EventHandler<RepositoryOperationEventArgs> InsertEvent;
public event EventHandler<RepositoryOperationEventArgs> UpdateEvent;
public event EventHandler<RepositoryOperationEventArgs> DeleteEvent;
#endregion
#region Public Properties
public IList<String> PrimaryKeys{ get { return primaryKeys.AsReadOnly(); } }
public IList<String> Properties { get; private set; }
public String InsertText { get; private set; }
public String UpdateText { get; private set; }
public String DeleteText { get; private set; }
public String SelectText { get; private set; }
#endregion
#region Private fields
List<String> primaryKeys;
IDbConnection connection;
IDbTransaction transaction;
bool disposed;
#endregion
#region Constructor(s)
public OracleRepository()
{
primaryKeys = new List<String>(new T().GetPrimaryKeys());
Properties = new List< String>(GetEntryPropertyNames(typeof(T))).AsReadOnly();
SelectText = GenerateSelectText();
InsertText = GenerateInsertText();
UpdateText = GenerateUpdateText();
DeleteText = GenerateDeleteText();
connection = GetOpenIDbConnection();
}
#endregion
#region Public Behavior(s)
public void StartTransaction()
{
if (transaction != null)
throw new InvalidOperationException("Transaction is already set. Please Rollback or commit transaction");
transaction = connection.BeginTransaction();
}
public void CommitTransaction()
{
using(transaction)
transaction.Commit();
transaction = null;
}
public void Rollback()
{
using (transaction)
transaction.Rollback();
transaction = null;
}
public void Insert(IDbConnection connection, T entry)
{
connection.NonQuery(InsertText, Properties.Select(p => typeof(T).GetProperty(p).GetValue(entry)).ToArray());
if (InsertEvent != null) InsertEvent(this, new OracleRepositoryOperationEventArgs() { Entry = entry, IsTransaction = (transaction != null) });
}
public void Update(IDbConnection connection, T entry)
{
connection.NonQuery(UpdateText, Properties.Where(p => !primaryKeys.Any(k => k == p)).Concat(primaryKeys).Select(p => typeof(T).GetProperty(p).GetValue(entry)).ToArray());
if (UpdateEvent != null) UpdateEvent(this, new OracleRepositoryOperationEventArgs() { Entry = entry, IsTransaction = (transaction != null) });
}
public void Delete(IDbConnection connection, Predicate<T> predicate)
{
foreach (var entry in RetrieveAll(connection).Where(new Func<T, bool>(predicate)))
{
connection.NonQuery(DeleteText, primaryKeys.Select(p => typeof(T).GetProperty(p).GetValue(entry)).ToArray());
if (DeleteEvent != null) DeleteEvent(this, new OracleRepositoryOperationEventArgs() { Entry = null, IsTransaction = (transaction != null) });
}
}
public T Retrieve(IDbConnection connection, Predicate<T> predicate)
{
return RetrieveAll(connection).FirstOrDefault(new Func<T, bool>(predicate));
}
public bool Exists(IDbConnection connection, Predicate<T> predicate)
{
return RetrieveAll(connection).Any(new Func<T, bool>(predicate));
}
public IEnumerable<T> RetrieveAll(IDbConnection connection)
{
return connection.Query(SelectText).Tuples.Select(p => RepositoryEntryBase.FromPlexQueryResultTuple(new T(), p) as T);
}
#endregion
#region IRepository Behavior(s)
public void Insert(T entry)
{
using (var connection = GetOpenIDbConnection())
Insert(connection, entry);
}
public void Update(T entry)
{
using (var connection = GetOpenIDbConnection())
Update(connection, entry);
}
public void Delete(Predicate<T> predicate)
{
using (var connection = GetOpenIDbConnection())
Delete(connection, predicate);
}
public T Retrieve(Predicate<T> predicate)
{
using (var connection = GetOpenIDbConnection())
return Retrieve(connection, predicate);
}
public bool Exists(Predicate<T> predicate)
{
using (var connection = GetOpenIDbConnection())
return Exists(predicate);
}
public IEnumerable<T> RetrieveAll()
{
using (var connection = GetOpenIDbConnection())
return RetrieveAll(connection);
}
#endregion
#region IDisposable Behavior(s)
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#region Protected Behavior(s)
protected virtual void Dispose(Boolean disposing)
{
if(disposed)
return;
if (disposing)
{
if(transaction != null)
transaction.Dispose();
if(connection != null)
connection.Dispose();
}
disposed = true;
}
#endregion
#region Private Behavior(s)
String GenerateInsertText()
{
String statement = "INSERT INTO {0}({1}) VALUES ({2})";
//Do first entry here becasse its unique input.
String columnNames = Properties.First();
String delimiter = ", ";
String bph = ":a";
String placeHolders = bph + 0;
//Start # 1 since first entry is already done
for (int i = 1; i < Properties.Count; i++)
{
columnNames += delimiter + Properties[i];
placeHolders += delimiter + bph + i;
}
statement = String.Format(statement, typeof(T).Name, columnNames, placeHolders);
return statement;
}
String GenerateUpdateText()
{
String bph = ":a";
String cvpTemplate = "{0} = {1}";
String statement = "UPDATE {0} SET {1} WHERE {2}";
//Can only set Cols that are not a primary Keys, Get those Columns
var Settables = Properties.Where(p => !PrimaryKeys.Any(k => k == p)).ToList();
String cvp = String.Format(cvpTemplate, Settables.First(), bph + 0);
String condition = String.Format(cvpTemplate, PrimaryKeys.First(), bph + Settables.Count);
//These are the values to be set | Start # 1 since first entry is done above.
for (int i = 1; i < Settables.Count; i++)
cvp += ", " + String.Format(cvpTemplate, Settables[i], bph + i);
//This creates the conditions under which the values are set. | Start # 1 since first entry is done above.
for (int i = Settables.Count + 1; i < Properties.Count; i++)
condition += " AND " + String.Format(cvpTemplate, PrimaryKeys[i - Settables.Count], bph + i);
statement = String.Format(statement, typeof(T).Name, cvp, condition);
return statement;
}
String GenerateDeleteText()
{
String bph = ":a";
String cvpTemplate = "{0} = {1}";
String statement = "DELETE FROM {0} WHERE {1}";
String condition = String.Format(cvpTemplate, PrimaryKeys.First(), bph + 0);
for (int i = 1; i < PrimaryKeys.Count; i++)
condition += " AND " + String.Format(cvpTemplate, PrimaryKeys[i], bph + i);
statement = String.Format(statement, typeof(T).Name, condition);
return statement;
}
String GenerateSelectText()
{
String statement = "SELECT * FROM {0}";
statement = String.Format(statement, typeof(T).Name);
return statement;
}
#endregion
#region Destructor
~OracleRepository()
{
Dispose(false);
}
#endregion
}
The second implementation for in memory operation is this :
public class InMemoryRepository<T> : IRepository<T> where T : IRepositoryEntry, new()
{
//RepositoryEntryBase,
public event EventHandler<RepositoryOperationEventArgs> InsertEvent;
public event EventHandler<RepositoryOperationEventArgs> UpdateEvent;
public event EventHandler<RepositoryOperationEventArgs> DeleteEvent;
public IList<String> PrimaryKeys { get; protected set; }
List<T> data;
public InMemoryRepository()
{
PrimaryKeys = new List<String>(new T().GetPrimaryKeys());
data = new List<T>();
}
public void Insert(T Entry)
{
if (Get(Entry) != null)
throw new Exception("Duplicate Entry - Identical Key already exists");
data.Add(Entry);
if (InsertEvent != null)
InsertEvent(this, new RepositoryOperationEventArgs() { Entry = Entry });
}
public void Update(T Entry)
{
var obj = Get(Entry);
if (obj == null)
throw new Exception("Object does not exist");
obj = Entry;
if (UpdateEvent != null)
UpdateEvent(this, new RepositoryOperationEventArgs() { Entry = obj });
}
public void Delete(Predicate<T> predicate)
{
data.RemoveAll(predicate);
if (DeleteEvent != null)
DeleteEvent(this, new RepositoryOperationEventArgs() { Entry = null });
}
public bool Exists(Predicate<T> predicate)
{
return data.Exists(predicate);
}
public T Retrieve(Predicate<T> predicate)
{
return data.FirstOrDefault(new Func<T, bool>(predicate));
}
public IEnumerable<T> RetrieveAll()
{
return data.ToArray();
}
T Get(T Entry)
{
//Returns Entry based on Identical PrimaryKeys
Type entryType = typeof(T);
var KeyPropertyInfo = entryType.GetProperties().Where(p => PrimaryKeys.Any(p2 => p2 == p.Name));
foreach (var v in data)
{
//Assume the objects are identical by default to prevent false positives.
Boolean AlreadyExists = true;
foreach (var property in KeyPropertyInfo)
if (!property.GetValue(v).Equals(property.GetValue(Entry)))
AlreadyExists = false;
if (AlreadyExists)
return v;
}
return default(T);
}
}
Whew, that was a lot of code. Now there are a few non standard functions. These are what they all are:
public static class IDbConnectionExtensions
{
public static IDbCommand CreateCommand(this IDbConnection Conn, string CommandText, params object[] Parameters)
{
var Command = Conn.CreateCommand();
Command.CommandText = CommandText;
foreach (var p in Parameters ?? new object[0])
Command.Parameters.Add(Command.CreateParameter(p));
return Command;
}
public static IDbDataParameter CreateParameter(this IDbCommand Command, object Value)
{
var Param = Command.CreateParameter();
Param.Value = Value;
return Param;
}
public static PlexQueryResult Query(this IDbConnection conn, String CommandText, params object[] Arguments)
{
using (var Comm = conn.CreateCommand(CommandText, Arguments))
using (var reader = Comm.ExecuteReader(CommandBehavior.KeyInfo))
return new PlexQueryResult(reader);
}
public static int NonQuery(this IDbConnection conn, String CommandText, params object[] Arguments)
{
using (var Comm = conn.CreateCommand(CommandText, Arguments))
return Comm.ExecuteNonQuery();
}
public static IDbConnection OpenConnection(this IDbConnection connection)
{
connection.Open();
return connection;
}
}
Now, how we tie everything together is simple, this one I'm writing off the top of my head with no editor so please bear with me :
Lets say we have the following class which inherits from IRepostoryEntry:
//Feel free to ignore RepostoryEntryBase
public class COMPANIES : RepositoryEntryBase, IRepositoryEntry
{
public string KEY { get; set; } //KEY VARCHAR2(20) N
public int COMPANY_ID { get; set; } //COMPANY_ID NUMBER(10) N
public string DESCRIPTION { get; set; }//DESCRIPTION VARCHAR2(100) N
public COMPANIES() : base ()
{
primaryKeys.Add("COMPANY_ID");
}
}
public abstract class DbRepository : IDbRepository
{
public Dictionary<Type,IRepository> Repositories { get;set; }
public DbRepository(){
Repositories = new Dictionary<Type,IRepository>();
Repositories .add(typeof(COMPANIES)),new OracleRepository<COMPANIES>());
}
public object Insert(object entity)
{
if(!(entity is IRepositoryEntry))
throw new NotSupportedException("You are bad and you should feel bad");
if(!Repositories.ContainsKey(entity.GetType()))
throw new NotSupportedException("Close but no cigar");
Dictionary[entity.GetType()].Insert(entity);
}
//You can add additional operations here:
}
That had to be the longest answer I ever wrote:
I built this DLL to get my jump started on this method of storing data; however, its really intended for Oracle. That said, its easy to adapt to your needs.
My suggestion is to not fight WCF constraints and possibly making your solution more complex than necessary. Instead, try using code generators or rollout your own to generate the numerous service contracts your application requires.
In your current implementation, you do not have your OperationContract attributes set on your contract interface.
Try something like this:
public abstract class DbRepository : IDbRepository
{
[OperationalContract(Name="Insert")]
public TEntity Insert<TEntity>(TEntity entity) where TEntity : class
{
_context.Entry(entity).State = EntityState.Added;
return entity;
}
[OperationalContract(Name="Update")]
public TEntity Update<TEntity>(TEntity entity) where TEntity : class
{
_context.Entry(entity).State = EntityState.Modified;
return entity;
}
}
It probably seems redundant, but I believe the generics mess with operational names accidentally and you are required to specify them.
Will WCF generate a WSDL for that contract and allow you to host the service? Is the problem you have just down to serialization and known types? If so you may want to look at the SharedTypeResolver in this blog post. It's a pretty simple and awesome piece of magic that allows you to transparently pass any subclass of a data contract without having to declare it, so long as the type is shared between both client and server.
You could then dispense with the generics and simply talk about things as TEntity. Internally in the service you could map the call to your generic service implementations; think of the WCF service as a non-generic facade to expose your generic classes. The caller will know what type to expect because they gave it to you in the first place, so could cast. You could provide a client that puts a generic wrapper around this if casting offends.
Since you are using a BasicHttpBinding, I am going to assume you are sending this over the web. I'm also going to assume that you are using SOAP/XML. If this is the case try something like this:
[ServiceContract]
public interface IDbRepository
{
[OperationContract]
XElement Insert(XElement entity);
[OperationContract]
XElement Update(XElement entity);
}
Now all you have to do is parse the XML you receive and return whatever XML you see fit! I did something similar where I had an abstract base class that has 2 methods one to generate XML to represent the object and one to parse XML to populate the object's properties. One drawback to this is that your interface implementation will still need to know about all the types of objects in the class hierarchy.