Create a DbContext that handle a DatabaseFactory to use DapperExtensions more easily - factory

This days I try to create an abstract base repository using some basic CRUD functions proposed by DapperExtensions. But the code given as an exemple use a SqlConnection which is made to connect to a SQL Server database. I want to be able to connect to all kind of Database (SQL Server, MySql, etc...). Also their code sample is repeated for each CRUD function as the code below show
using (SqlConnection cn = new SqlConnection(_connectionString))
{
cn.Open();
//Code doing something here...
cn.Close();
}
So i was thinking about to create a DbContext that can handle the creation, the opening and closing of the connection and also can create the right connection object depending on the database type I want to use (a kind of database factory).
Is there somebody that already done it and could share his code?
Thank you guys !

You are using Dapper-Extensions; following code is with Dapper only. But it does not change the entire concept. Just instead of sql you need to pass poco.
Refer this answer for how I implemented IUnitOfWork and DalSession. In below code, BaseDal is just like BaseRepository.
public abstract class BaseDal
{
internal BaseDal(IUnitOfWork unitOfWork)
{
dapperHandler = new DapperHandler(unitOfWork);
}
DapperHandler dapperHandler = null;
protected T Get<T>(string sql, DynamicParameters param) where T : class
{
var result = dapperHandler.Query<T>(sql, param).FirstOrDefault();
return result;
}
protected List<T> GetList<T>(string sql, DynamicParameters param) where T : class
{
var result = dapperHandler.Query<T>(sql, param).ToList();
return result;
}
protected int Insert(string sql, DynamicParameters param)
{
var result = dapperHandler.Execute(sql, param);
return result;
}
}
Edit 1
For example code with Dapper-Extensions, refer this answer that I recently posted.

public abstract class ABaseRepository<M> : IBaseRepository<M>
where M : BaseModel
{
private static DbProviderFactory factory = DbProviderFactories.GetFactory(ConfigurationManager.ConnectionStrings["DatabaseConnectionString"].ProviderName);
protected static DbConnection connection;
public static IDbConnection CreateOpenConnection()
{
connection = factory.CreateConnection();
connection.Open();
return connection;
}
public dynamic Insert(M model)
{
dynamic multiKey;
using (IDbConnection con = CreateOpenConnection())
{
multiKey = con.Insert(model);
}
return multiKey;
}
}

Related

How to work with an DevArt edml file and start a connection?

I am working with the newest DevArt Oracle version and created a EDML file that connects to my Oracle 12 database and get the models with the db first approach.
I followed this howto:
https://www.devart.com/entitydeveloper/docs/
So I have my context and my model auto generated:
public partial class KiddataAdminEntities : DbContext
{
#region Constructors
/// <summary>
/// Initialize a new KiddataAdminEntities object.
/// </summary>
public KiddataAdminEntities() :
base(#"name=KiddataAdminEntitiesConnectionString")
{
Configure();
}
/// <summary>
/// Initializes a new KiddataAdminEntities object using the connection string found in the 'KiddataAdminEntities' section of the application configuration file.
/// </summary>
public KiddataAdminEntities(string nameOrConnectionString) :
base(nameOrConnectionString)
{
Configure();
}
private void Configure()
{
this.Configuration.AutoDetectChangesEnabled = true;
this.Configuration.LazyLoadingEnabled = true;
this.Configuration.ProxyCreationEnabled = true;
this.Configuration.ValidateOnSaveEnabled = true;
}
#endregion
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Anrede> Anrede { get; set; }
}
Now I try to get it to work in my main in another project (just a simple console application with a start.cs):
KiddataAdminEntities context = new KiddataAdminEntities("User Id=xxxx;Password=xxxx;Server=xx;Direct=True;Sid=xxxx;Persist Security Info=True");
var listOfAnrede = context.Anrede.ToList();
So now I get the error "Keyword user id not supported".
I googled this and I found out that problably EF6 is trying to get a default connection, not an oracle connection with DevArt...
I tried to play with the app.config in different ways but it didnt help.
Now I tried to create my own connection with the DevArt.Data.Oracle provider, like shown here:
https://www.devart.com/dotconnect/oracle/articles/tutorial-connection.html
OracleConnection oc = new OracleConnection();
oc.ConnectionString = constring2;
oc.Open();
var test = oc.ServerVersion;
This works fine, so the connectionstring is okay, but still I can't put these two together. I tried to overload the constructor so I can put in my Connection:
public KiddataAdminEntities(DbConnection con, bool contextOwnsConnection)
: base(con, contextOwnsConnection)
{
}
Then I got the error on
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
That I should not do that...
If you are using XML mapping with Devart Entity Model (*.edml), try this code:
using Devart.Data.Oracle;
using System.Data.EntityClient;
...
OracleConnectionStringBuilder oracleBuilder = new OracleConnectionStringBuilder();
oracleBuilder.UserId = "...";
oracleBuilder.Password = "...";
oracleBuilder.Server = "...";
oracleBuilder.Direct = true;
oracleBuilder.Sid = "...";
oracleBuilder.PersistSecurityInfo = true;
EntityConnectionStringBuilder entityBuilder = new EntityConnectionStringBuilder();
entityBuilder.Provider = "Devart.Data.Oracle";
entityBuilder.ProviderConnectionString = oracleBuilder.ConnectionString;
entityBuilder.Metadata = #"res://*/MyModel.csdl|res://*/MyModel.ssdl|res://*/MyModel.msl";
using (Entities context = new Entities(entityBuilder.ToString())) {
var a = context.MyEntity.First();
}
Refer to
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/how-to-build-an-entityconnection-connection-string
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/connection-strings
FYI, you can generate fluent mapping (instead of XML mapping). For this, disable a predefined EntityObject template, enable the DbContext template and set the options:
Fluent Mapping=True in the properties of DbContext template
Metadata Artifact Processing=Do Not Generate Mapping Files in the properties of EntityContextModel

Entity Framework 6 "DbContext has been disposed" exception

Something very strange is happening in production, and it only happens in production. I have a Web API running and in one of the APIs, there is a repository created in the constructor and used in the functions. This is how the flow of a request works:
HTTP request comes in
MVC API controller decides which "worker" class to instantiate and creates it using Activator.CreateInstance
API controller calls worker.OnExecute inside of a Task.Run() and returns the http response
Worker calls _engine.Execute
Each worker instantiates another "engine" class that has all of the logic.
The engine in case constructs 3 repositories created using a UnitOfWork that is created per engine instance, like so:
public class MyWorker : Worker
{
private readonly MyEngine _engine;
public MyWorker()
{
_engine = new MyEngine();
}
protected override WorkerResult OnExecute(JObject data, CancellationToken cta)
{
return new WorkerResult(HttpStatusCode.OK, _engine.Execute(data));
}
}
public class MyEngine : EngineBase
{
private BaseRepository<Order> OrderRepo { get; set; }
private BaseRepository<OrderItem> OrderItemRepo { get; set; }
public MyEngine()
{
OrderRepo = new BaseRepository<Order>(MyUnitOfWork);
OrderItemRepo = new BaseRepository<OrderItem>(MyUnitOfWork);
}
public string Execute(JObject data)
{
return IsOrderValid(data).ToString();
}
public bool IsOrderValid(JObject data)
{
var orderId = data.Value<int>("OrderId");
// Without this line it crashes. With this line it crashes
//OrderRepo = new BaseRepository<Order>(InternationalWork);
// This is where it crashes
Order order = OrderRepo.First(x => x.OrderID == orderId);
// more code
}
}
public class EngineBase : UnitOfWorker, IDisposable
{
private UnitOfWork _myUnitOfWork;
public EngineBase() { }
public UnitOfWork MyUnitOfWork
{
get
{
return _myUnitOfWork ?? (_myUnitOfWork = new UnitOfWork(new DbContextAdapter(new MyDbContext())));
}
}
}
This is the actual stack trace:
The operation cannot be completed because the DbContext has been disposed.
StackTrace1
at System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
at System.Data.Entity.Internal.LazyInternalContext.get_ObjectContext()
at System.Data.Entity.Internal.Linq.InternalSet`1.CreateObjectQuery(Boolean asNoTracking, Nullable`1 streaming, IDbExecutionStrategy executionStrategy)
at System.Data.Entity.Internal.Linq.InternalSet`1.InitializeUnderlyingTypes(EntitySetTypePair pair)
at System.Data.Entity.Internal.Linq.InternalSet`1.get_InternalContext()
at System.Data.Entity.Infrastructure.DbQuery`1.System.Linq.IQueryable.get_Provider()
at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source, Expression`1 predicate)
The stack trace shows "FirstOrDefault" because OrderRepo.First internally calls DbSet.FirstOrDefault, like so:
public virtual T First(Expression<Func<T, bool>> query)
{
return _dbSet.FirstOrDefault(query);
}
I'm stumped because each worker is created per http request. Each DBContext is created per engine instance so I don't know how it could be disposed when it was just created in the constructor. And this only happens on the production web server where I presume it's being called more. Any tips would be greatly appreciated.

Unable to setup MiniProfiler w/ Enity Framework 4.0 (Not code first)

I installed MiniProfiler and MiniProfiler.EF in my project via nuget.
Before using MiniProfiler I would open a connection using this in my model repository:
public class NotificationRepository
{
private CBNotificationModel.CB_NotificationEntities db;
public NotificationRepository()
{
db = new CB_NotificationEntities();
}
public NotificationContact GetNotificationContacts()
{
return db.NotificationContacts.ToList();
}
}
To use mini profiler I created:
public static class ConnectionHelper
{
public static CB_NotificationEntities GetEntityConnection()
{
var conn = new StackExchange.Profiling.Data.EFProfiledDbConnection(GetConnection(), MiniProfiler.Current);
return ObjectContextUtils.CreateObjectContext<CB_NotificationEntities>(conn); // resides in the MiniProfiler.EF nuget pack
}
public static EntityConnection GetConnection()
{
return new EntityConnection(ConfigurationManager.ConnectionStrings["CB_NotificationEntities"].ConnectionString);
}
}
The model repository now uses
db = ConnectionHelper.GetEntityConnection();
However this gives the error:
An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
Am I missing a step? I tried adding MiniProfilerEF.Initialize() and MiniProfilerEF.Initialize_EF42() in Application_start() however that just changes the errors given.
There does not seem to be much information for setting up a entity framework project to use miniprofiler unless it is codefirst.
I was able to get this working by changing my ConnectionHelper class to the following:
public static class ConnectionHelper
{
public static CB_NotificationEntities GetEntityConnection()
{
var connectionString = ConfigurationManager.ConnectionStrings["CB_NotificationEntities"].ConnectionString;
var ecsb = new EntityConnectionStringBuilder(connectionString);
var sqlConn = new SqlConnection(ecsb.ProviderConnectionString);
var pConn = new StackExchange.Profiling.Data.EFProfiledDbConnection(sqlConn, MiniProfiler.Current);
var context = ObjectContextUtils.CreateObjectContext<CB_NotificationEntities>(pConn);
return context;
}
}

How to pass a mvc-mini-profiler connection to a base class from MVC3

Given this snippet of code
public abstract class Foo
{
private static SqlConnection _sqlConnection;
protected SqlConnection GetOpenConnection()
{
if (_sqlConnection == null)
{
_sqlConnection = new SqlConnection("connection string");
}
return _sqlConnection;
}
protected abstract void Execute();
}
public class FooImpl : Foo
{
protected override void Execute()
{
var myConn = GetOpenConnection();
var dog = myConn.Query<dynamic>("select 'dog' Animal");
var first = dog.First();
string animalType = first.Animal;
// more stuff here
}
}
How would you wrap the connection in a profiled connection if you don't have access to the connection creation process? Rewrite the code in the super class and wrap it there? This would involve changing hundreds of classes that inherit from the base. I'd prefer a way to change the base class, with as little changes necessary to the supers.
Thank you,
Stephen
Well after a bit of trial and error I compromised and added a ref to MvcMiniProfiler in the base library and changed the connection code a bit.
protected DbConnection GetOpenConnection()
{
if (_connection == null)
{
_connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connection string "].ConnectionString);
_connection.Open();
}
return MvcMiniProfiler.Data.ProfiledDbConnection.Get(_connection, MiniProfiler.Current);
}
private static SqlConnection _connection;
This works for both hosting in the MVC project (for profiling purposes, where we don't have that capability (QA/Prod Databases)) and WPF/Windows Service

Linq to sql multiple data context in same transaction

I am working on a project Where I have multiple reposetories to fetch data from
differnt tables. All my repositories are independent, they create new dataContext, Add row to table and apply submit changes command. Now if in my service, there is a situation where I have to insert data into multiple tables, but it should happen in one transaction. I can achieve that using TrnasctionScope stuff, but thats needs same dataContext. As I am using StrucutreMap to create my objects, I cant get same data context, so my transaction fails.
Here is how my objects are.
interface IConnection
{
MyDataContext GetContext();
}
public class Connection : IConnection
{
public MyDataContext GetContext()
{
return new MyDataContext();
}
}
interface IRepositryA
{
int SaveDataA(objectA a);
}
public class RepositoryA : IRepositryA
{
public int SaveDataA(objectA a)
{
var context = objectFactory.GetInstance<IConnection>().GetContext();
context.TableA.InsertOnSubmit(a);
context.SubmitChanges();
return a.ID;
}
}
interface IRepositryB
{
int SaveDataA(objectB b);
}
public class RepositoryA : IRepositryB
{
public int SaveDataB(objectB b)
{
var context = objectFactory.GetInstance<IConnection>().GetContext();
context.TableB.InsertOnSubmit(b);
context.SubmitChanges();
return b.ID;
}
}
Now in my Service layer I have call them as
public MyServiceClass ()
{
public SaveAll(ObjectA a, ObjectB b)
{
using (TransactionScope trn);
{
ObjectFactory.GetInstance<IRepositryA>().SaveDataA(a);
b.FkeyID = a.ID;
ObjectFactory.GetInstance<IRepositryB>().SaveDataB(b);
trn.complete();
}
}
}
How will I pass same data context to two differnt repositories.
I option, I thought was creating one overloaded method in each repository which Accepts IDataContext parameter as given below.
interface IRepositryA
{
int SaveDataA(objectA a);
int SaveDataA(IConnection connection, objectA a);
}
public class RepositoryA : IRepositryA
{
public int SaveDataA(objectA a)
{
var connection = objectFactory.GetInstance<IConnection>();
return SaveData(connection, a);
}
public int SaveDataA(IConnection connection, objectA a)
{
var context = connection.GetContext();
context.TableA.InsertOnSubmit(a);
context.SubmitChanges();
return a.ID;
}
}
interface IRepositryB
{
int SaveDataA(objectB b);
int SaveDataA(IConnection connection, objectB b);
}
public class RepositoryA : IRepositryB
{
public int SaveDataB(objectB b)
{
var connection = objectFactory.GetInstance<IConnection>();
return SaveData(connection, b);
}
public int SaveDataA(IConnection connection, objectB b)
{
var context = connection.GetContext();
context.TableB.InsertOnSubmit(b);
context.SubmitChanges();
return b.ID;
}
}
and in my service layer, I would implement it as
public MyServiceClass ()
{
public SaveAll(ObjectA a, ObjectB b)
{
IConnection connection= ObjectFactory.GetInstance<IConnection>();
using (TransactionScope trn);
{
ObjectFactory.GetInstance<IRepositryA>().SaveDataA(connection,a);
b.FkeyID = a.ID;
ObjectFactory.GetInstance<IRepositryB>().SaveDataB(connection,b);
trn.complete();
}
}
}
Second thought I have is, if somehow I can configure DataContext to return same connection
when I call GetContext method. so all repositories will use same connection. But I feel that will keep the connection alive all the time. I want this connection to available during the time of this SaveAll method only.
Your help will be appritiated. Thanks in advance.
Cheers
Parminder
TransactionScope can be used with multiple DataContexts, but as soon as more than one connection is involved the transaction is escalated to a MSDTC/XA/distributed transaction. For that to work you need to have MSDTC running on both the system where you code runs and on the database server.
Alternatively, you can avoid escalation to a distributed transaction if you create an explicit connection within the transactionscope and pass that to your datacontexts; that way the TransactionScope will not escalate to a distributed transaction, and won't rely on MSDTC...

Resources