Reputation: 157
Long time lurker, first time poster.
I've found tons of stuff on here about how to share dbContext across repositories using CodeFirst, but I can't seem to relate that to the project I'm working on, which doesn't use code first or dependency injection.
First, a little background on the project to make sure that I'm approaching this the right way. I came into this project and they were using EF4 and with a DB first. I'm far from an expert on EF, but I've fumbled around with several different projects now.
I've had to implement several different requirements that have forced me to intervene between their "service" level and the database. In other words, their objects were making calls directly to the EF db objects like
using (var db = new MYDB()){
var bar = db.Foo
.Include("Transactions")
.Include("blah")
.Where(...);
//do stuff
db.SaveChanges();
}
One thing I had to do was track all fields that changed, so I abstracted back a level and now we have
FooObject bar = GetFooObject(...);
bar.Title = "asdfasdf";
//do stuff to bar
bar.Save();
which wraps up all the fields into properties so I can log out any changes. In bar.save I open a db context, get the existing Foo or create a new one, assign all the values and then call db.SaveChanges.
As it turns out they also do lots of sub-queries based on Transactions and blah. So when they do something like
var bar = GetFooObject(...);
var t = new Transaction();
//do stuff to t
...
bar.Transactions.Add(t);
bar.Save();
I get hit with all kinds of context errors saying that the dbcontext is no longer available etc. Which I totally understand. What I don't know is how to fix it. I've seen lots of stuff about creating a dbContext before it's used and then passing it in, but I can't seem to figure out the proper way to do it so it will work with my code.
My most recent attempt based on several examples about how to convert DBContext to ObjectContext (which was in turn based on the fact that all of the examples I found about sharing a connection referenced ObjectContext and not DBContext) looks like this:
using (var db = ((IObjectContextAdapter)(new FooDB())).ObjectContext)
{
using (var context = new DbContext(db, false))
{
var bar = FooObject.GetFooObject(fooId);
Result r = bar.ProcTrans(amount,
transDate,
db.TransactionTypes
.Include(tt => tt.Description)
.SingleOrDefault(tt => tt.TypeID == transactionTypeId),
employeeId,
comment);
But with this code I get an error that I have no definition for TransactionTypes. It doesn't recognize any of my db Objects.
How can I create a DBContext and pass it to my FooObject so that I can keep it open for the related updates? I don't even know if I'm asking the question exactly right. How do I bridge this gap without recoding the whole thing?
Here are some things I've found since opening this question. Maybe one of the two will do the trick.
Well, this finding certainly is more along the lines of recoding the whole thing but I did find this when looking for links regarding the "do change tracking" with triggers response.
poco in the entity framework part-3: change tracking with poco
And I just found this how do I share a data context across various model repositories in asp.net which might be a simple way to approach it.
Upvotes: 3
Views: 727
Reputation: 157
The answer, for me, was related to one of the links I posted.
how do I share a data context across various model repositories in asp.net
What threw me off when I saw these types of injection answers was that syntactically they didn't work for me. I don't have DataContext nor do I have any Repository models, but I decided to give it a try conceptually and pass the Context around everywhere.
Basically, I passed in the connection to the Object constructor or to any factory methods where a new object is created and store that in a local variable, sorta like this.
public class Foo{
private MyDB _db;
private Foo _foo;
public FooObject(MyDB dbContext)
{
_db = dbContext;
}
public static FooObject GetFooObject(int FooID, MyDB db){
bool closeFlag = false;
//if null was passed in, then we will create our own connection and manage it
if (db == null)
{
_db = new MyDB();
closeFlag = true;
} else {
//otherwise, we set our local variable
_db = db;
}
//from now on, all queries are done using the local variable
var _f = _db.Foos
.Include("x")
.Include("y")
.Include("z")
.SingleOrDefault(f => f.FooID == FooID);
var fo = FooObjectFromFoo(_f, db);
if (closeFlag)
db.Dispose();
return fo;
}
// This copies all of the values from Foo and puts the into a FooObject
public static FooObject FooObjectFromFoo(Foo f, MyDB dbContext){
if (l == null)
return null;
// note that we pass the dbContext to the constuctor
FooObject _f = new FooObject(dbContext){
_foo = f,
...
//note x, y, and z are the other EF "table references". I'm not sure what you technically call them.
x = f.x,
y = f.y,
z = f.z
};
return _f;
}
//we call this to save the changes when we're done
public bool Save(){
bool close = false;
bool retval = true;
MyDB db = _db;
//remember we set _db in the constructor
if (db == null) {
db = new MyDB();
close = true;
}
try
{
// a reference to this row should have been saved in _foo if we loaded it from the db.
// take a look at FooObjectFromFoo
if (_foo == null)
{
_foo = db.Foos.SingleOrDefault(x => x.FooID == _FooID);
}
if (_foo == null)
{
_foo = new Foo();
}
//copy all my object values back to the EF Object
_foo.blah = blah;
_foo.x = x;
_foo.y = y;
try
{
//save the new one.
db.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
TransactionResult.AddErrors(dbEx);
retval = false;
}
}
catch { throw new Exception("Something went wrong here.");}
finally { if (close) db.Dispose(); } //if we created this connection then let's close it up.
}
}
And now in my methods, I always use the local _db connection. Outside of my FooObject we have a FooService which is what is called from all of the controllers. So when FooService is instantiated I create a db connection using my class below. If I understand it properly, this should give me a context that exists for the duration of my service request which, in my case, fairly reliably mimics the request.
namespace My.Domain
{
public class MyDataContext : IDisposable {
private MyDB _context;
private bool _ownContext;
public MyDataContext(){
_context = new MyDB();
_ownContext = true;
}
public MyDataContext(MyDB db)
{
_context = db;
_ownContext = false;
}
public MyDB Context
{
get { if (_context == null) { _context = new MyDB(); _ownContext = true; } return _context; }
set { _context = value; }
}
public bool OwnContext
{
get { return _ownContext; }
set { _ownContext = value; }
}
public void Dispose()
{
if (_context != null && _ownContext)
_context.Dispose();
}
}
}
In the FooService I do stuff like this.
private MyDb db;
public FooService (){
var _db = new MyDataContext();
db = _db.Context;
}
public Result ProcessTransaction(int FooId, string comment)
{
var foo = FooObject.GetFooObject(FooId,db);
Result r = foo.ProcessTransaction(comment);
if (r.Success)
foo.Save();
return r;
}
I think to do it "right" I should only save the changes when I close out the context... but I already had a Save method on my FooObject, so I just call db.SaveChanges in there.
I know there are lots of ways to improve this and I'm sure I'll implement some of them over time, but for now, this did the trick. This was how I got around all of the "Context is no longer available" and this object was from a different context errors.
The thing that tripped me up when looking at other peoples examples is they were all using CodeFirst and dependency injection of some sort. They commonly used Repository patterns and we don't have any of that. But it turns out that I just had to implment my own hacked-up localized version of connection injection! :)
Upvotes: 0
Reputation: 2139
I would leave behind the object context stuff.
The way I achieve a shared DBContext in my MVC app is like this:
public class BaseRepository
{
public static MyAppContext GetDataContext()
{
string ocKey = "ocm_" + HttpContext.Current.GetHashCode().ToString("x");
if (!HttpContext.Current.Items.Contains(ocKey))
HttpContext.Current.Items.Add(ocKey, new MyAppContext());
return HttpContext.Current.Items[ocKey] as MyAppContext;
}
}
Then whenever I need to do a database operation I can call:
BaseRepository.GetDataContext().YourObjects.Where(x => ...);
....
BaseRepository.GetDataContext().SaveChanges();
As long as you are still in the same HTTP context you will be sharing the same DB Context. Not entirely sure this will eliminate the errors that you are getting, but it's at least a way to share your context.
Upvotes: 1