Entity Framework + 'using' pattern best practice - c#

I'm wondering what would be best practice in this scenario:
I have method which calls some other method in using statement (disposable Database Context), and that other method also needs to access Database Context, so this is option 1:
using (var db = new Database())
{
// some code here
//calling other function
var cnt = SomeFunction();
}
int SomeFunction()
{
using (var db = new Database())
{
// some code here for example:
return db.Users.Count();
}
}
And here is option 2:
using (var db = new Database())
{
// some code here
//calling other function
var cnt = SomeFunction(db);
}
int SomeFunction(Database db)
{
return db.Users.Count();
}
Note: Database looks smth. like this:
public class Database : IdentityDbContext<User>
{
public Database()
: base("DefaultConnection", throwIfV1Schema: false)
{
Configuration.LazyLoadingEnabled = true;
}
public DbSet<IdentityUser> AspNetUsers { get; set; }
}
I would go with option 2, because there is no need for initialization of Database Context again, but I'm not sure if this is correct way of doing this.

DbContext instances have their own changetracker. In case you want to use a changed DbSet inside SomeFunction, use the same DbContext. If not, you can choose whatever you prefer.

I would go with option 3: overload your SomeFunction.
int SomeFunction()
{
using (var db = new Database())
return SomeFunction(db);
}
int SomeFunction(Database db)
{
return db.Users.Count();
}
Let the caller decide whether to pass in an existing Database. Sometimes it makes sense, if the caller has one already, as in your example. Other times, the caller just has one database operation to perform, and then there is some benefit in simplifying the API by not bothering the caller with the responsibility of creating a Database and remembering to dispose of it properly.

Related

How to use multiple contexts for single DB, without violating dependency injection

Let's say I have a method that loads some entities from database, makes an API call for each, gets a token from a third party and saves them.
The API call is critical so we want each call to be logged into db instantly.
So I put context.SaveChanges in CriticalAPI, to save each log separately.
The problem is that this SaveChanges also saves Posts entities modified in Method().
I want CriticalAPI to only save logs, not other objects. One way is to create another context and use in that method, but it violates dependency injection because I should instantiate a new context in my method.
What is the correct way to achieve this requirements?
public void Method(){
var entities = context.Posts.Where(/* something */).ToList();
foreach (var entity in entities){
var result = CriticalAPI(entity.Id);
entity.Token = result;
}
context.SaveChanges();
}
public int CriticalAPI(int id){
var token = /* do something critical */
context.Logs.Add(new Log(){
entityId = id
});
context.SaveChanges();
return token;
}
You can use abstract factory pattern. Just define context factory interface and inject the factory. Then use the factory anywhere you need to create new context instance:
interface IDbContextFactory {
DbContext CreateContext();
}
//...
public void Method() {
using (var context = contextFactory.CreateContext()) {
context.Posts.Where(/* something */)
.ForEach(entity => {
var result = CriticalAPI(entity.Id);
entity.Token = result;
});
context.SaveChanges();
}
}
public int CriticalAPI(int id) {
using (var context = contextFactory.CreateContext()) {
var token = /* do something critical */
context.Logs.Add(new Log(){
entityId = id
});
context.SaveChanges();
return token;
}
}
You can add .AsNoTracking() method after when you're calling context.Posts and the entity will not track changes.
public void method(){
var entities = context.Posts
.AsNoTracking() // <---- add this Method here
.Where(/* something */).ToList();
foreach (var entity in entities){
var result = CriticalAPI(entity.Id);
entity.Token = result;
}
context.SaveChanges();
}
You could alternatively create an ENTIRELY separate dbContext for Logs, and you would set and save that context.

Create a table if it does not exist?

Using Entity Framework Core, is there a way to create the table if it does not yet exist? Exception will throw even if EnsureCreated is called in the context:
DbSet<Ticker> Ticker { get; set }
Database.EnsureCreated();
Ticker.Add(...);
dbctx.SaveChanges(); <== exception
Results in exception:
System.Data.SqlClient.SqlException: Invalid object name 'Ticker'
Is there a way to create the table Ticker before data is inserted?
== EDIT==
This questions is not to create/migrate the entire database, the database always exist and most of its tables also exists, but some of the tables may not. So I just need create one or two tables in runtime.
In Entity framework Core (on version 2.2.4) you can use the following code in your DbContext to create tables in your database if they don't exist:
try
{
var databaseCreator = (Database.GetService<IDatabaseCreator>() as RelationalDatabaseCreator);
databaseCreator.CreateTables();
}
catch (System.Data.SqlClient.SqlException)
{
//A SqlException will be thrown if tables already exist. So simply ignore it.
}
Database.EnsureCreated() doesn't create the schema (so your tables) when the database already exists. That's the reason why you get that exception.
You can check that method's documentation.
PS: Make sure you catch the right exception if it changes in the new versions of Entity framework Core.
My guess is that your context is wrongly defined. Maybe you forgot to add the DbSet to your context implementation?
Below code is working perfectly, and I honestly prefer to EnsureCreated() in the constructor of the actual DBContext implementation.
internal class AZSQLDbContext : DbContext
{
public AZSQLDbContext() {
this.Database.EnsureCreated();
}
internal DbSet<TaskExecutionInformation> TaskExecutionInformation { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var dbUser = "your-user";
var dbPW = "your-pw";
optionsBuilder.UseSqlServer(
$"Server=tcp:sample-sql.database.windows.net,1433;Initial Catalog=sample-db;Persist Security Info=False;User ID={dbUser};Password={dbPW};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;");
}
}
TaskExecutionInformation is just a PoCo and could be anything. See below though if you need a bit of guidance.
public class TaskExecutionInformation
{
public Guid Id { get; set; }
public string Status { get; set; }
public int Duration { get; set; }
}
In my case there was 2 applications using same database and those could create its own code-first tables, if they were missing.
So my solution for that is following extension method used in startup on dbcontext:
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Extensions
{
internal static class DbContextExtensions
{
internal static void EnsureCreatingMissingTables<TDbContext>(this TDbContext dbContext) where TDbContext : DbContext
{
var type = typeof(TDbContext);
var dbSetType = typeof(DbSet<>);
var dbPropertyNames = type.GetProperties().Where(p => p.PropertyType.Name == dbSetType.Name)
.Select(p => p.Name).ToArray();
foreach (var entityName in dbPropertyNames)
{
CheckTableExistsAndCreateIfMissing(dbContext, entityName);
}
}
private static void CheckTableExistsAndCreateIfMissing(DbContext dbContext, string entityName)
{
var defaultSchema = dbContext.Model.GetDefaultSchema();
var tableName = string.IsNullOrWhiteSpace(defaultSchema) ? $"[{entityName}]" : $"[{defaultSchema}].[{entityName}]";
try
{
_ = dbContext.Database.ExecuteSqlRaw($"SELECT TOP(1) * FROM {tableName}"); //Throws on missing table
}
catch (Exception)
{
var scriptStart = $"CREATE TABLE {tableName}";
const string scriptEnd = "GO";
var script = dbContext.Database.GenerateCreateScript();
var tableScript = script.Split(scriptStart).Last().Split(scriptEnd);
var first = $"{scriptStart} {tableScript.First()}";
dbContext.Database.ExecuteSqlRaw(first);
Log.Information($"Database table: '{tableName}' was created.");
}
}
}
}
You have a few options here. The simplest is to do:
MyContext.Database.CreateIfNotExists();
Or, do it initialization style, by putting this in your context's constructor:
Database.SetInitializer<MyContext>(new CreateDatabaseIfNotExists<MyContext>());
Both of these however require you to drop your schema manually every time you have modified your model and need to re-create the database. If you don't want to do that, you can use the following initialization instead:
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyContext>());
This will check your model against the database every time you run your program, and automatically drop and re-create the database if the model has been modified.
EDIT:
If you don't want to drop the database, and simply update it, then you can use the following initialization:
Database.SetInitializer<MyContext>(new MigrateDatabaseToLatestVersion<MyContext, Config>());

Database connection errors on EF

I am very new to entity framework and I am having a problem with a web api based site (connected to mssql) that I am writing. I keep getting seemingly random errors (mostly seeming to be database related). These errors happen most often when the site is first published but they do sometimes happen when it has been hours since the last publish. A selection of the errors:
Invalid operation. The connection is closed.
There is already an open DataReader associated with this Command which must be closed first.
The connection was not closed. The connection's current state is connecting.
The context cannot be viewed while the model is being created
Underlying provider failed to open
My context looks like this:
public class Context : DbContext
{
public Context() : base("name=DefaultConnection")
{
}
public override int SaveChanges()
{
DateTime now = DateTime.Now;
foreach (ObjectStateEntry entry in (this as IObjectContextAdapter).ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified))
{
if (!entry.IsRelationship)
{
IHasUpdated updated = entry.Entity as IHasUpdated;
if (updated != null)
updated.updated = now;
}
}
return base.SaveChanges();
}
public DbSet<Branch> Branches { get; set; }
public DbSet<Company> Companies { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UsefulLink> UsefulLinks { get; set; }
}
There are many more DbSets than this. Should I be creating a separate context for each?
One of my basic controllers:
public class UsefulLinksController : ApiController
{
private Context db = new Context();
[ResponseType(typeof(UsefulLinksWrapper))]
public IHttpActionResult GetUsefulLinks([FromUri]UsefulLinkParams prams)
{
UsefulLinksWrapper wrapper = new UsefulLinksWrapper();
Meta meta = new Meta();
IQueryable<UsefulLink> query = db.UsefulLinks;
if (prams.sortBy == null)
{
prams.sortBy = "ID";
}
// Paging
query = query.OrderBy(prams.sortBy + " " + prams.sortDirection).Skip(prams.offset - 1).Take(prams.limit);
List<UsefulLink> data = query.ToList();
meta.totalCount = query.Count();
meta.offset = 1;
meta.limit = prams.limit;
wrapper.meta = meta;
wrapper.data = data;
return Ok(wrapper);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
private bool UsefulLinkExists(int id)
{
return db.UsefulLinks.Count(e => e.ID == id) > 0;
}
}
I don't seem to see these errors when I run the site locally though there are two of us hitting it when it is published so perhaps the issue stems from multiple users?
Chris, I notice in your controller you are sharing your db context with all of the methods in your controller class.
This is generally not a best practice in Entity Framework (see: EntityFramework 4 ObjectContext Lifetime). You should keep your context alive as briefly as possible. Leaving the context alive to share across multiple methods could result in many of the errors that you list above.
I would recommend trying to instantiate a new instance of the context, instead, wherever it is used and quickly disposing of it.
This should generally result in more stable behavior.
So the below:
class SomeClass
{
private context = new Context(); //sharing your context with all methods
public someMethod()
{
context.doSomething;
}
public someMethod2()
{
context.doSomething;
}
}
should become:
class SomeClass
{
public someMethod()
{
Context context = new Context(); //now your context is declared and disposed of within each method
context.doSomething;
}
public someMethod2()
{
Context context = new Context(); //now your context is declared and disposed of within each method
context.doSomething;
}
}
Or even better, you can use a using construct to ensure that your context is properly disposed of:
class SomeClass
{
public someMethod3()
{
using(Context context = new Context()) //now wrapping the context in a using to ensure it is disposed
{
context.doSomething;
}
}
}
I would recommend trying the above changes and seeing if your behavior becomes more stable.
Since I do not know how your page uses the methods UsefulLinksController and in which order, I would say UsefulLinkExists is perhaps the culprit due to lazy loading
Lazy loading means delaying the loading of related data until you
specifically request it
Which would explain why your "reader" remains "open".
Try:
return db.UsefulLinks.ToList().Count(e => e.ID == id) > 0;
In any case, you can disable lazy loading by default in the context constructor as such noted here:
public MyEntitiesContext() : base("name=MyEntitiesContext", "MyEntitiesContext")
{
this.ContextOptions.LazyLoadingEnabled = false;
OnContextCreated();
}
As far as I know, it applies to EF4 and up.

Caching and lazy loading with entity framework

let's say I have an application, for example a web site, where my objectcontext leaves during the time of a request. Some datas I load with EF should be cached to avoid to read in DB and improve performance.
Ok, I read my datas with EF, I put my object in cache (says AppFabric, not in memory cache), but related datas that can be lazy loaded are now null (and access to this property results in a nullreferenceexception). I don't want to load everything in one request, because it's going to be too long, so I want to keep the loading on demand and as soon as it's read, I would like to complete the cache with the new fetched datas.
Note :
only read operations, no create/update/delete.
Don't want to use second level cache like "EF Provider Wrappers" made by Jarek Kowalski
How can I do that ?
EDIT : I've built this samples with northwind database, it's working :
class Program
{
static void Main(string[] args)
{
// normal use
List<Products> allProductCached = null;
using (NORTHWNDEntities1 db = new NORTHWNDEntities1())
{
allProductCached = db.Products.ToList().Clone<DbSet<Products>>();
foreach (var product in db.Products.Where(e => e.UnitPrice > 100))
{
Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName);
}
}
// try to use cache, but missing Suppliers
using (NORTHWNDEntities1 db = new NORTHWNDEntities1())
{
foreach (var product in allProductCached.Where(e => e.UnitPrice > 100))
{
if (product.Suppliers == null)
product.Suppliers = db.Suppliers.FirstOrDefault(s => s.SupplierID == product.SupplierID).Clone<Suppliers>();
Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName);
}
}
// try to use full cache
using (NORTHWNDEntities1 db = new NORTHWNDEntities1())
{
foreach (var product in allProductCached.Where(e => e.UnitPrice > 100))
{
Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName);
}
}
}
}
public static class Ext
{
public static List<Products> Clone<T>(this List<Products> list)
{
return list.Select(obj =>
new Products
{
ProductName = obj.ProductName,
SupplierID = obj.SupplierID,
UnitPrice = obj.UnitPrice
}).ToList();
}
public static Suppliers Clone<T>(this Suppliers obj)
{
if (obj == null)
return null;
return new Suppliers
{
SupplierID = obj.SupplierID,
CompanyName = obj.CompanyName
};
}
}
The problem is that I have to copy everything (without missing a property) and test everywhere if the property is null and load the needed property. My code is of course more and more complex, so that will be a problem if I miss something. No other solution ?
You cannot access the database in EF without an ObjectContext or a DbContext.
You can still use caching effectively, even if you don't have the original context any more.
Maybe your scenario is something like this... Imagine that you have some reference data that you use frequently. You do not want to hit the database each time you need it, so you store it in a cache. You also have per-user data that you don't want to cache. You have navigation properties from your user data to your reference data. You want to load your user data from the database, and have EF automatically "fix up" the navigation properties to point to the reference data.
For a request:
Create a new DbContext.
Retrieve reference data from the cache.
Make a deep copy of the reference objects. (You probably don't want to have the same entities attached to multiple contexts simultaneously.)
Attach each of the reference objects to the context. (e.g. with DbSet.Attach())
Execute whatever queries are required to load the per-user data. EF will automatically "fix up" the references to the reference data.
Identify newly loaded entities that could be cached. Ensure that they contain no references to entities that should not be cached, then save them to the cache.
Dispose of the context.
Cloned Objects and Lazy Loading
Lazy loading in EF is usually accomplished using dynamic proxies. The idea is that you make all properties that could potentially be loaded dynamically virtual. Whenever EF creates an instance of your entity type, it actually substitutes a derived type instead, and that derived type has the lazy loading logic in its overridden version of your properties.
This is all well and good, but in this scenario you are attaching entity objects to the context that were not created by EF. You created them, using a method called Clone. You instantiated the real POCO entity, not some mysterious EF dynamic proxy type. That means you won't get lazy loading on these entities.
The solution is simple. The Clone method must take an additional argument: the DbContext. Don't use the entity's constructor to create a new instance. Instead, use DbSet.Create(). This will return a dynamic proxy. Then initialize its properties to create a clone of the reference entity. Then attach it to the context.
Here is the code you might use to clone a single Products entity:
public static Products Clone(this Products product, DbContext context)
{
var set = context.Set<Products>();
var clone = set.Create();
clone.ProductName = product.ProductName;
clone.SupplierID = product.SupplierID;
clone.UnitProce = product.UnitPrice;
// Initialize collection so you don't have to do the null check, but
// if the property is virtual and proxy creation is enabled, it should get lazy loaded.
clone.Suppliers = new List<Suppliers>();
return clone;
}
Code Sample
namespace EFCacheLazyLoadDemo
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
class Program
{
static void Main(string[] args)
{
// Add some demo data.
using (MyContext c = new MyContext())
{
var sampleData = new Master
{
Details =
{
new Detail { SomeDetail = "Cod" },
new Detail { SomeDetail = "Haddock" },
new Detail { SomeDetail = "Perch" }
}
};
c.Masters.Add(sampleData);
c.SaveChanges();
}
Master cachedMaster;
using (MyContext c = new MyContext())
{
c.Configuration.LazyLoadingEnabled = false;
c.Configuration.ProxyCreationEnabled = false;
// We don't load the details here. And we don't even need a proxy either.
cachedMaster = c.Masters.First();
}
Console.WriteLine("Reference entity details count: {0}.", cachedMaster.Details.Count);
using (MyContext c = new MyContext())
{
var liveMaster = cachedMaster.DeepCopy(c);
c.Masters.Attach(liveMaster);
Console.WriteLine("Re-attached entity details count: {0}.", liveMaster.Details.Count);
}
Console.ReadKey();
}
}
public static class MasterExtensions
{
public static Master DeepCopy(this Master source, MyContext context)
{
var copy = context.Masters.Create();
copy.MasterId = source.MasterId;
foreach (var d in source.Details)
{
var copyDetail = context.Details.Create();
copyDetail.DetailId = d.DetailId;
copyDetail.MasterId = d.MasterId;
copyDetail.Master = copy;
copyDetail.SomeDetail = d.SomeDetail;
}
return copy;
}
}
public class MyContext : DbContext
{
static MyContext()
{
// Just for demo purposes, re-create db each time this runs.
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
}
public DbSet<Master> Masters { get { return this.Set<Master>(); } }
public DbSet<Detail> Details { get { return this.Set<Detail>(); } }
}
public class Master
{
public Master()
{
this.Details = new List<Detail>();
}
public int MasterId { get; set; }
public virtual List<Detail> Details { get; private set; }
}
public class Detail
{
public int DetailId { get; set; }
public string SomeDetail { get; set; }
public int MasterId { get; set; }
[ForeignKey("MasterId")]
public Master Master { get; set; }
}
}
Here is a sample model, different from yours, that shows how to get this working in principle.

How do I share dbContext without CodeFirst in EntityFramework 4.1?

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?
EDIT
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.
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.
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! :)

Categories