I have a class that contains several common methods customized for use in my MVC app, that I use in several places. Here is an example of some:
private MyEntities db = new MyEntities();
public List<SelectListItem> GetLocationList()
{
var query =
db.v_LocationsAlphabetical.OrderByDescending(x => x.Category).ThenBy(x => x.LocationName).ToList()
.Select(x => new SelectListItem
{
Value = x.LocationID.ToString(),
Text = x.LocationName
});
return (query).ToList();
}
public IEnumerable<SelectListItem> GetStates()
{
var query = db.States.Select(x => new SelectListItem
{
Value = x.Abbr,
Text = x.Name
});
return(query);
}
public List<Person> GetPeople()
{
var query = db.Person.OrderBy(m => m.LastName).ThenBy(m => m.FirstName).ToList();
return (query);
}
Each one of these methods makes a call to the database to get data and I was wondering if I need to add a dispose to each method. If not, why? Thanks.
You shouldn't call dispose in each method, because the lifetime of db is the same as that of the enclosing class since it's not a local variable in a method.
The typical way to handle this is to make the current class IDisposable and call db.Dispose() in the Dispose() method.
There are multiple ways of handling db connection in .NET
One of my favorites is the one called one dbcontext per request, which basically means you initialize your dbcontext when needed, do the work without thinking about instantiating or disposing, and dispose automatically when the request is done. (kinda UnitOfWork-ish)
I've already shown this approach here. It's not only applicable to EF, but to Linq2SQL, ADO.NET, etc. as well.
No. DbContexts don't have to be manually disposed, unless you manually manage the connection yourself. Therefore, disposing them is usually optional.
Related
Got a small confusion here.
I'm not sure if I am handling my DbContext throughout the WebApi properly.
I do have some controllers that do some operations on my DB (Inserts/Updates with EF) and after doing these actions I do trigger an event.
In my EventArgs (I have a custom class which inherits from EventArgs) I pass my DbContext and I use it in the event handler to log these operations (basically I just log authenticated user API requests).
In the event handler when I am trying to commit my changes (await SaveChangesAsync) I get an error : "Using a disposed object...etc" basically noticing me that at the first time I use await in my async void (fire and forget) I notify the caller to dispose the Dbcontext object.
Not using async works and the only workaround that I've mangaged to put out is by creating another instance of DbContext by getting the SQLConnectionString of the EventArgs passed DbContext.
Before posting I did made a small research based on my issue
Entity Framework disposing with async controllers in Web api/MVC
This is how I pass parameters to my OnRequestCompletedEvent
OnRequestCompleted(dbContext: dbContext,requestJson: JsonConvert.SerializeObject);
This is the OnRequestCompleted() declaration
protected virtual void OnRequestCompleted(int typeOfQuery,PartnerFiscalNumberContext dbContext,string requestJson,string appId)
{
RequestCompleted?.Invoke(this,new MiningResultEventArgs()
{
TypeOfQuery = typeOfQuery,
DbContext = dbContext,
RequestJson = requestJson,
AppId = appId
});
}
And this is how I process and use my dbContext
var appId = miningResultEventArgs.AppId;
var requestJson = miningResultEventArgs.RequestJson;
var typeOfQuery = miningResultEventArgs.TypeOfQuery;
var requestType = miningResultEventArgs.DbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery).Result;
var apiUserRequester = miningResultEventArgs.DbContext.ApiUsers.FirstAsync(x => x.AppId == appId).Result;
var apiRequest = new ApiUserRequest()
{
ApiUser = apiUserRequester,
RequestJson = requestJson,
RequestType = requestType
};
miningResultEventArgs.DbContext.ApiUserRequests.Add(apiRequest);
await miningResultEventArgs.DbContext.SaveChangesAsync();
By using SaveChanges instead of SaveChangesAsync everything works.
My only idea is to create another dbContext by passing the previous DbContext's SQL connection string
var dbOptions = new DbContextOptionsBuilder<PartnerFiscalNumberContext>();
dbOptions.UseSqlServer(miningResultEventArgs.DbContext.Database.GetDbConnection().ConnectionString);
using (var dbContext = new PartnerFiscalNumberContext(dbOptions.Options))
{
var appId = miningResultEventArgs.AppId;
var requestJson = miningResultEventArgs.RequestJson;
var typeOfQuery = miningResultEventArgs.TypeOfQuery;
var requestType = await dbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery);
var apiUserRequester = await dbContext.ApiUsers.FirstAsync(x => x.AppId == appId);
var apiRequest = new ApiUserRequest()
{
ApiUser = apiUserRequester,
RequestJson = requestJson,
RequestType = requestType
};
dbContext.ApiUserRequests.Add(apiRequest);
await dbContext.SaveChangesAsync();
}
The latter code excerpt is just a small test to check my supposition, basically I should pass the SQL connection string instead of the DbContext object.
I am not sure (in terms of best practice) if I should pass a connection string and create a new dbContext object (and dispose it by using a using clause) or if I should use/have another mindset for this issue.
From what I know, using a DbContext should be done for a limited set of operations and not for multiple purposes.
EDIT 01
I'm going to detail more thorough what I've been doing down below.
I think I got an idea of why this error happens.
I have 2 controllers
One that receives a JSON and after de-serializing it I return a JSON to the caller and another controller that gets a JSON that encapsulates a list of objects that I iterate in an async way, returning an Ok() status.
The controllers are declared as async Task<IActionResult> and both feature an async execution of 2 similar methods.
The first one that returns a JSON executes this method
await ProcessFiscalNo(requestFiscalView.FiscalNo, dbContext);
The second one (the one that triggers this error)
foreach (string t in requestFiscalBulkView.FiscalNoList)
await ProcessFiscalNo(t, dbContext);
Both methods (the ones defined previously) start an event OnOperationComplete()
Within that method I execute the code from my post's beginning.
Within the ProcessFiscalNo method I DO NOT use any using contexts nor do I dispose the dbContext variable.
Within this method I only commit 2 major actions either updating an existing sql row or inserting it.
For edit contexts I select the row and tag the row with the modified label by doing this
dbContext.Entry(partnerFiscalNumber).State = EntityState.Modified;
or by inserting the row
dbContext.FiscalNumbers.Add(partnerFiscalNumber);
and finally I execute an await dbContext.SaveChangesAsync();
The error always gets triggered within the EventHandler ( the one detailed # the beginning of the thread) during the await dbContext.SaveChangedAsync()
which is pretty weird since 2 lines before that I do await reads on my DB with EF.
var requestType = await dbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery);
var apiUserRequester = await dbContext.ApiUsers.FirstAsync(x => x.AppId == appId);
dbContext.ApiUserRequests.Add(new ApiUserRequest() { ApiUser = apiUserRequester, RequestJson = requestJson, RequestType = requestType });
//this throws the error
await dbContext.SaveChangesAsync();
For some reason calling await within the Event Handler notifies the caller to dispose the DbContext object.
Also by re-creating the DbContext and not re-using the old one I see a huge improvement on access.
Somehow when I use the first controller and return the info the DbContext object appears to get flagged by the CLR for disposal but for some unknown reason it still functions.
EDIT 02
Sorry for the bulk-ish content that follows, but I've placed all of the areas where I do use dbContext.
This is how I'm propagating my dbContext to all my controllers that request it.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMemoryCache();
// Add framework services.
services.AddOptions();
var connection = #"Server=.;Database=CrawlerSbDb;Trusted_Connection=True;";
services.AddDbContext<PartnerFiscalNumberContext>(options => options.UseSqlServer(connection));
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("PowerUser",
policy => policy.Requirements.Add(new UserRequirement(isPowerUser: true)));
});
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IAuthorizationHandler, UserTypeHandler>();
}
In Configure I'm using the dbContext for my custom MiddleWare
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
var context = app.ApplicationServices.GetService<PartnerFiscalNumberContext>();
app.UseHmacAuthentication(new HmacOptions(),context);
app.UseMvc();
}
In the custom MiddleWare I'm only using it for a query.
public HmacHandler(IHttpContextAccessor httpContextAccessor, IMemoryCache memoryCache, PartnerFiscalNumberContext partnerFiscalNumberContext)
{
_httpContextAccessor = httpContextAccessor;
_memoryCache = memoryCache;
_partnerFiscalNumberContext = partnerFiscalNumberContext;
AllowedApps.AddRange(
_partnerFiscalNumberContext.ApiUsers
.Where(x => x.Blocked == false)
.Where(x => !AllowedApps.ContainsKey(x.AppId))
.Select(x => new KeyValuePair<string, string>(x.AppId, x.ApiHash)));
}
In my controller's CTOR I'm passing the dbContext
public FiscalNumberController(PartnerFiscalNumberContext partnerContext)
{
_partnerContext = partnerContext;
}
This is my Post
[HttpPost]
[Produces("application/json", Type = typeof(PartnerFiscalNumber))]
[Consumes("application/json")]
public async Task<IActionResult> Post([FromBody]RequestFiscalView value)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var partnerFiscalNo = await _fiscalNoProcessor.ProcessFiscalNoSingle(value, _partnerContext);
}
Within the ProcessFiscalNoSingle method I have the following usage, If that partner exists then I'll grab him, if not, create and return him.
internal async Task<PartnerFiscalNumber> ProcessFiscalNoSingle(RequestFiscalView requestFiscalView, PartnerFiscalNumberContext dbContext)
{
var queriedFiscalNumber = await dbContext.FiscalNumbers.FirstOrDefaultAsync(x => x.FiscalNo == requestFiscalView.FiscalNo && requestFiscalView.ForceRefresh == false) ??
await ProcessFiscalNo(requestFiscalView.FiscalNo, dbContext, TypeOfQuery.Single);
OnRequestCompleted(typeOfQuery: (int)TypeOfQuery.Single, dbContextConnString: dbContext.Database.GetDbConnection().ConnectionString, requestJson: JsonConvert.SerializeObject(requestFiscalView), appId: requestFiscalView.RequesterAppId);
return queriedFiscalNumber;
}
Further down in the code, there's the ProcessFiscalNo method where I use the dbContext
var existingItem =
dbContext.FiscalNumbers.FirstOrDefault(x => x.FiscalNo == partnerFiscalNumber.FiscalNo);
if (existingItem != null)
{
var existingGuid = existingItem.Id;
partnerFiscalNumber = existingItem;
partnerFiscalNumber.Id = existingGuid;
partnerFiscalNumber.ChangeDate = DateTime.Now;
dbContext.Entry(partnerFiscalNumber).State = EntityState.Modified;
}
else
dbContext.FiscalNumbers.Add(partnerFiscalNumber);
//this gets always executed at the end of this method
await dbContext.SaveChangesAsync();
Also I've got an Event called OnRequestCompleted() where I pass my actual dbContext (after it ends up with SaveChangesAsync() if I update/create it)
The way I initiate the event args.
RequestCompleted?.Invoke(this, new MiningResultEventArgs()
{
TypeOfQuery = typeOfQuery,
DbContextConnStr = dbContextConnString,
RequestJson = requestJson,
AppId = appId
});
This is the notifier class (where the error occurs)
internal class RequestNotifier : ISbMineCompletionNotify
{
public async void UploadRequestStatus(object source, MiningResultEventArgs miningResultArgs)
{
await RequestUploader(miningResultArgs);
}
/// <summary>
/// API Request Results to DB
/// </summary>
/// <param name="miningResultEventArgs">EventArgs type of a class that contains requester info (check MiningResultEventArgs class)</param>
/// <returns></returns>
private async Task RequestUploader(MiningResultEventArgs miningResultEventArgs)
{
//ToDo - fix the following bug : Not being able to re-use the initial DbContext (that's being used in the pipeline middleware and controller area),
//ToDo - basically I am forced by the bug to re-create the DbContext object
var dbOptions = new DbContextOptionsBuilder<PartnerFiscalNumberContext>();
dbOptions.UseSqlServer(miningResultEventArgs.DbContextConnStr);
using (var dbContext = new PartnerFiscalNumberContext(dbOptions.Options))
{
var appId = miningResultEventArgs.AppId;
var requestJson = miningResultEventArgs.RequestJson;
var typeOfQuery = miningResultEventArgs.TypeOfQuery;
var requestType = await dbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery);
var apiUserRequester = await dbContext.ApiUsers.FirstAsync(x => x.AppId == appId);
var apiRequest = new ApiUserRequest()
{
ApiUser = apiUserRequester,
RequestJson = requestJson,
RequestType = requestType
};
dbContext.ApiUserRequests.Add(apiRequest);
await dbContext.SaveChangesAsync();
}
}
}
Somehow when the dbContext reaches the Event Handler CLR gets notified to dispose the dbContext object (because I'm using await?)
Without recreating the object I was having huge lag when I wanted to use it.
While writing this I have an idea, I did upgrade my solution to 1.1.0 and I'm gonna try to see if it behaves similarly.
Concerning Why you get the error
As pointed out at the Comments by #set-fu DbContext is not thread safe.
In addition to that, since there is no explicit lifetime management of your DbContext your DbContext is going to get disposed when the garbage collector sees fit.
Judging from your context, and your mention about Request scoped DbContext
I suppose you DI your DbContext in your controller's constructor.
And since your DbContext is request scoped it is going to be disposed as soon as your Request is over,
BUT since you have already fired and forgot your OnRequestCompleted events there is no guarantee that your DbContext won't be disposed.
From there on , the fact that one of our methods succeeds and the other fails i think is seer "Luck".
One method might be faster than the other and completes before the Garbage collector disposes the DbContext.
What you can do about this is to change the return type of your Events from
async void
To
async Task<T>
This way you can wait your RequestCompleted Task within your controller to finish and that will guarantee you that your Controller/DbContext will not get Disposed until your RequestCompleted task is finished.
Concerning Properly handling DbContexts
There are two contradicting recommendations here by microsoft and many people use DbContexts in a completely divergent manner.
One recommendation is to "Dispose DbContexts as soon as posible"
because having a DbContext Alive occupies valuable resources like db
connections etc....
The other states that One DbContext per request is highly
reccomended
Those contradict to each other because if your Request is doing a lot of unrelated to the Db stuff , then your DbContext is kept for no reason.
Thus it is waste to keep your DbContext alive while your request is just waiting for random stuff to get done...
So many people who follow rule 1 have their DbContexts inside their "Repository pattern" and create a new Instance per Database Query
public User GetUser(int id)
{
User usr = null;
using (Context db = new Context())
{
usr = db.Users.Find(id);
}
return usr;
}
They just get their data and dispose the context ASAP.
This is considered by MANY people an acceptable practice.
While this has the benefits of occupying your db resources for the minimum time it clearly sacrifices all the UnitOfWork and "Caching" candy EF has to offer.
So Microsoft's recommendation about using 1 Db Context per request it's clearly based on the fact that your UnitOfWork is scoped within 1 request.
But in many cases and i believe your case also this is not true.
I consider Logging a separate UnitOfWork thus having a new DbContext for your Post-Request Logging is completely acceptable (And that's the practice i also use).
An Example from my project i have 3 DbContexts in 1 Request for 3 Units Of Work.
Do Work
Write Logs
Send Emails to administrators.
Wondering if anyone can think of an elegant solution to this use case:
I am consuming an observable (IObservable of type TEntity) which is providing me a stream of entities. If any of those entities are updated, then the provider of the observable will push down the updated entity.
I am using a Replay() on this stream so that I only need to subscribe to the underlying stream once and so that late subscribers can see all the values. The problem is that there is potential for a memory-leak here, because the Replay() will hold onto all the updates it sees, whereas all I need is the latest update for each entity.
I could replace the Replay() with a Scan() which allows me to maintain the latest updates only, but then I would have to push out a Dictionary of all the updates observed so far, rather than just the specific entity that has changed.
The only solution I can think of is to use a Scan() as above, but in the Scan() implementation I will push all updates into an Subject. Subscribers to the IObservable I will expose will receive a merge of the Snapshot stored in the Scan() dictionary plus any updates, as follows:
private Subject<Entity> _updateSubject = new Subject<Entity>();
private IObservable<Dictionary<string, Entity>> _subscriptionStream;
//called once on initialisation
private void CreateSubscription()
{
IObservable<Entity> source = GetSomeLongRunningSubscriptionStream();
_subscriptionStream = source
.Scan(new Dictionary<string, Entity>(), (accumulator,update) =>
{
accumulator[update.ID] = update;
_updateSubject.OnNext(update);
return accumulator;
})
.Replay(1);
}
//called each time a consumer wants access to the stream
public IObservable<Entity> GetStream()
{
return _subscriptionStream.Take(1).SelectMany(x => x).Select(x => x.Value)
.Merge(_updateSubject.AsObservable());
}
Can anyone think of a more elegant solution with holds the state within a single stream rather than resorting to Subjects?
Thanks
************** Edit **************
As per my comment, I've gone with something similar to this. Let me know your thoughts
//called once on initialisation
private void CreateSubscription()
{
_baseSubscriptionObservable = GetSomeLongRunningSubscriptionStream ().Publish();
_snapshotObservable = _baseSubscriptionObservable
.Scan(new Dictionary<string,Entity>(), (accumulator, update) =>
{
accumulator[update.ID] = update;
return accumulator;
})
.StartWith(new Dictionary<string, Entity>())
.Replay(1);
_baseSubscriptionObservable.Connect ();
_snapshotObservable.Connect ();
}
public IObservable<Entity> GetStream()
{
return _snapshotObservable.Take (1).Select (x => x.Values).SelectMany (x => x)
.Merge (_baseSubscriptionObservable);
}
I generally like what you're doing, but there are a number of issues that I can see.
To start with you've split CreateSubscription and GetStream into two methods, with the idea that you'll have one underlying subscription to the GetSomeLongRunningSubscriptionStream() stream. Unfortunately, in this case, you'll have zero subscriptions regardless how many subscriptions you get to your final observable as .Replay(1) returns an IConnectableObservable<> which you need to call .Connect() on to begin the flow of values.
The next thing is that you're updating your accumulator with the latest value and then in GetStream you're adding in the latest value along with merging in a flattened stream of your accumulator. You're returning the latest value twice each time.
Here's how I would suggest that you do it:
private IObservable<IList<Timestamped<Entity>>> GetStream()
{
return
Observable
.Create<IList<Timestamped<Entity>>>(o =>
GetSomeLongRunningSubscriptionStream()
.Timestamp()
.Scan(
new Dictionary<string, Timestamped<Entity>>(),
(accumulator, update) =>
{
accumulator[update.Value.ID] = update;
return accumulator;
})
.Select(x => x.Select(y => y.Value).ToList())
.Replay(1)
.RefCount()
.Subscribe(o));
}
It's almost always best to avoid any state when using Rx (that isn't localized within the observable). So I've merged together CreateSubscription and GetStream into a single GetStream method and I've encapsulated the whole observable into a Observable.Create.
In order to avoid pushing out values twice and to facilitate your ability to know what the latest update is I've added a call to .Timestamp() to put the latest time an Entity was returned.
I've kept the .Scan(...) with the dictionary, but it is now a Dictionary<string, Timestamped<Entity>>.
For each value added/updated I then flatten the dictionary and return the underlying values as a list. At this point you could order the list to make sure that the latest values are either first or last to suit your needs.
I've then used the .Replay(1).RefCount() combination to turn the IConnectableObservable<> returned by .Replay(1) back into an IObservable<>, with the understanding that you'll dispose of the underlying subscription when all subscribers dispose. This is probably the most crucial part of your query. It should be done this way. This is the Rx way of ensuring that you avoid memory leaks.
If you desperately need to keep the underlying connection open then you would need to encapsulate all of your code within a class that implements IDisposable to clean up the .Connect() that you would require.
Something like this:
public class EntityStream : IDisposable
{
private IDisposable _connection = null;
public EntityStream(IObservable<Entity> someLongRunningSubscriptionStream)
{
_stream =
someLongRunningSubscriptionStream
.Timestamp()
.Scan(
new Dictionary<string, Timestamped<Entity>>(),
(accumulator, update) =>
{
accumulator[update.Value.ID] = update;
return accumulator;
})
.Select(x => x.Select(y => y.Value).ToList())
.Replay(1);
_connection = _stream.Connect();
}
private IConnectableObservable<IList<Timestamped<Entity>>> _stream = null;
public IObservable<IList<Timestamped<Entity>>> GetStream()
{
return _stream.AsObservable();
}
public void Dispose()
{
if (_connection != null)
{
_connection.Dispose();
_connection = null;
}
}
}
I so very rarely do this though. I would thoroughly recommend the doing the first method. You should only mix OOP and Rx when you have to.
Please let me know if you need any clarification.
My project is C# .NET, MVC 5, EF6. I'm getting an ObjectDisposedException on using an object in a view that has been gotten from the database. I've read probably EVERY similar question, but .Include() does NOT work; I'm thinking the problem doesn't have anything to do with lazy loading.
The controller method:
public ActionResult Browse()
{
List<QuestionGroup> questionGroupsWithLinks = new List<QuestionGroup>();
using (CLASSContext context = new CLASSContext())
{
questionGroupsWithLinks = context.QuestionGroup.Include(qg => qg.Questions.Select(q => q.Answers))
.Where(qg => qg.QuestionGroupID == 128).ToList();
return View("Browse", questionGroupsWithLinks);
}
}
I've tried having the using statement not wrap around the view, I've tried declaring questionGroupWithLinks in different places, I've tried iterating through questionGroupWithLinks and assigning one of its properties in hopes that that would load it (didn't make any difference, because the problem is only in the view. It's always loaded as long as you're in the controller method), and I've tried other things as well.
The view (simplified):
#model List<CLASSOnlineAssessments.Models.Assessments.QuestionGroup>
<div class="page-copy">
#if (Model != null)
{
foreach (QuestionGroup qg in Model)
{
//More code here; but it always fails before this point.
}
}
</div>
I've tried using Model.First() to access a question group instead of foreach, but that doesn't make any difference.
Let me know if I can clarify anything or post more information.
Have you tried this?
public ActionResult Browse()
{
CLASSContext context = new CLASSContext();
List<QuestionGroup> questionGroupsWithLinks = context.QuestionGroup
.Include(qg => qg.Questions.Select(q => q.Answers))
.Where(qg => qg.QuestionGroupID == 128).ToList();
return View("Browse", questionGroupsWithLinks);
}
If this does not cause the error, then it does smell like an issue with lazy loading, and you should post your source for the QuestionGroup entity as well as the Question and Answer entities so that we can provide more help.
In all likelihood you have some other virtual navigation or collection property on QuestionGroup, Question, or Answer that EF is trying to load while rendering your view. However since you disposed of the context, EF cannot lazy load it, and throws the exception.
BTW, don't use the above in production. You should always have the DbContext disposed of at the end of the request somehow. In reality, you should be doing something more like this:
public ActionResult Browse()
{
using (CLASSContext context = new CLASSContext())
{
List<QuestionGroupViewModel> questionGroupsWithLinks = context.QuestionGroup
.Include(qg => qg.Questions.Select(q => q.Answers))
.Where(qg => qg.QuestionGroupID == 128)
.Select(x => new QuestionGroupViewModel
{
Id = x.Id,
// ...etc.
})
.ToList();
return View("Browse", questionGroupsWithLinks);
}
}
With the above, you completely transfer all of the data from the entity attached to the context to a ViewModel Data Transfer Object. If the ObjectDisposedException error really is coming from EF, then the above would ensure that nothing further would happen with the DbContext after it gets disposed.
I have a class Worker which sends emails periodically,I start in Global.asax.cs on App_start()
public static class Worker
{
public static void Start()
{
ThreadPool.QueueUserWorkItem(o => Work());
}
public static void Work()
{
var r = new DbContext();
var m = new MailSender(new SmtpServerConfig());
while (true)
{
Thread.Sleep(600000);
try
{
var d = DateTime.Now.AddMinutes(-10);
var ns = r.Set<Notification>().Where(o => o.SendEmail && !o.IsRead && o.Date < d);
foreach (var n in ns)
{
m.SendEmailAsync("noreply#example.com", n.Email, NotifyMailTitle(n) + " - forums", NotifyMailBody(n));
n.SendEmail = false;
}
r.SaveChanges();
}
catch (Exception ex)
{
ex.Raize();
}
}
}
}
So I keep this dbcontext alive for the entire lifetime of the application is this a good practice ?
DbContext is a very light-weight object.
It doesn't matter whether your DbContext stays alive or you instantiate it just before making the call because the actual DB Connection only opens when you SubmitChanges or Enumerate the query (in that case it is closed on end of enumeration).
In your specific case. It doesn't matter at all.
Read Linq DataContext and Dispose for details on this.
I would wrap it in a using statement inside of Work and let the database connection pool do it's thing:
using (DbContext r = new DbContext())
{
//working
}
NOTE: I am not 100% sure how DbContext handles the db connections, I am assuming it opens one.
It is not good practice to keep a database connection 'alive' for the lifetime of an application. You should use a connection when needed and close it via the API(using statement will take care of that for you). The database connection pool will actually open and close connections based on connection demands.
I agree with #rick schott that you should instantiate the DbContext when you need to use it rather than keep it around for the lifetime of the application. For more information, see Working with Objects (Entity Framework 4.1), especially the section on Lifetime:
When working with long-running context consider the following:
As you load more objects and their references into memory, the
memory consumption of the context may increase rapidly. This may cause
performance issues.
If an exception causes the context to be in an unrecoverable state,
the whole application may terminate.
In the following code doesn't work as
public void Foo()
{
CompanyDataContext db = new CompanyDataContext();
Client client = (select c from db.Clients ....).Single();
Bar(client);
}
public void Bar(Client client)
{
CompanyDataContext db = new CompanyDataContext();
db.Client.Attach(client);
client.SomeValue = "foo";
db.SubmitChanges();
}
This doens't work, I get error msg. "An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported."
How do you work with DataContexts throughout an application so you don't need to pass around a reference?
What
They really mean it with 'This is not supported.'. Attaching to an object fetched from another data context is not implemented.
There are a number of workarounds to the problem, the recommended way is by serializing objects, however this is not easy nor a clean approach.
The most simple approach I found is to use a readonly DataContext for fetching objects like this:
MyDataContext dataContext = new MyDataContext()
{
DeferredLoadingEnabled = false,
ObjectTrackingEnabled = false
};
The objects obtained from this context can be attached to another context but only applies to some scenarios.
The PLINQO framework generates detach for all entities making it easy to detach and reattach objects without receiving that error.
public void Foo()
{
CompanyDataContext db = new CompanyDataContext();
Client client = (select c from db.Clients ....).Single();
// makes it possible to call detach here
client.Detach();
Bar(client);
}
public void Bar(Client client)
{
CompanyDataContext db = new CompanyDataContext();
db.Client.Attach(client);
client.SomeValue = "foo";
db.SubmitChanges();
}
Here is the article that describing how the detach was implemented.
http://www.codeproject.com/KB/linq/linq-to-sql-detach.aspx
Yep. That's how it works.
You have tagged this asp.net so I guess it's a web app. Maybe you want one datacontext per request?
http://blogs.vertigo.com/personal/keithc/Blog/archive/2007/06/28/linq-to-sql-and-the-quote-request-scoped-datacontext-quote-pattern.aspx
(P.S. It's a lot harder in WinForms!)
I've created data access classes that encapsulate all the communication with Linq2Sql.
These classes have their own datacontext that they use on their objects.
public class ClientDataLogic
{
private DataContext _db = new DataContext();
public Client GetClient(int id)
{
return _db.Clients.SingleOrDefault(c => c.Id == id);
}
public void SaveClient(Client c)
{
if (ChangeSetOnlyIncludesClient(c))
_db.SubmitChanges();
}
}
Ofcourse you will need to keep this object instantiated as long as you need the objects.
Checking if only the rigth object has been changed is altso somewhat bothersom, you could make methods like
void ChangeClientValue(int clientId, int value);
but that can become a lot of code.
Attaching and detaching is a somewhat missing feature from Linq2Sql, if you need to use that a lot, you sould probably use Linq2Entities.
I took a look at this and found that it appears to work fine as long as the original DataContext has been disposed.
Try wrapping the DataContext with using() and make sure your changes occur after you've attached to the second DataContext? It worked for me..
public static void CreateEntity()
{
User user = null;
using (DataClassesDataContext dc = new DataClassesDataContext())
{
user = (from u in dc.Users
select u).FirstOrDefault();
}
UpdateObject(user);
}
public static void UpdateObject(User user)
{
using (DataClassesDataContext dc = new DataClassesDataContext())
{
dc.Users.Attach(user);
user.LastName = "Test B";
dc.SubmitChanges();
}
}
You need to handle object versioning.
An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy.
So, if there's no timestamp member or other 'versioning' mechanism provided there's no way for LINQ to determine whether that data has changed - hence the error you are seeing.
I resolved this issue by adding a timestamp column to my tables but there are other ways around it. Rick Strahl has written some decent articles about exactly this issue.
Also, see this and this for a bit more info.