How to refresh an Entity Framework Core DBContext?
Dependency Injection and DbContext
You mention that when you try to recreate your DbContext
, you get an error about the context being managed by your dependency injection (DI) system. There are two different styles of using a dependency injection system for object creation. The DI can either create a global singleton instance that is shared as a service between all consumers or it can create an instance per scope/unit of work (e.g., per request in a web server).
If your DI system is configured to create a single global shared instance of DbContext
, then you will encounter various problems associated with long-lived DbContext
.
DbContext
, by design, never automatically removes objects from its cache because it is not designed to be long-lived. Thus, a long-livedDbContext
will retain memory wastefully.- Your code will never see changes to items loaded into its cache without manually reloading each entity it loads.
DbContext
only allows one query to run at any time and is not threadsafe. If you try to run multiple queries on a globally shared instance, it will throwDbConcurrencyException
(at least on its async interface, not sure about its sync interface).
Thus, the best practice is to use a single DbContext
per unit of work. Your DI system can help you with this by being configured to provide a fresh instance for each request your application processes within a scope. For example, ASP.NET Core’s Dependency Injection system supports scoping instances by request.
Refreshing a Single Entity
The easiest way to get fresh data is to create a new DbContext
. However, within your unit of work, or within the constraints of the granularity of scoping provided by your DI system, you may trigger an external process which is supposed to modify your entity directly in the database. You may need to see that change before exiting your DI’s scope or completing your unit of work. In that case, you can force a reload by detaching your instance of the data object.
To do this, first get the EntityEntry<>
for your object. This is an object which lets you manipulate DbContext
’s internal cache for that object. You can then mark this entry detached by assigning EntitytState.Detached
to its State
property. I believe that this leaves the entry in the cache but causes the DbContext
to remove and replace it when you actually load the entry in the future. What matters is that it causes a future load to return a freshly loaded entity instance to your code. For example:
var thing = context.Things.Find(id);
if (thing.ShouldBeSentToService) {
TriggerExternalServiceAndWait(id);
// Detach the object to remove it from context’s cache.
context.Entities(thing).State = EntityState.Detached;
// Then load it. We will get a new object with data
// freshly loaded from the database.
thing = context.Things.Find(id);
}
UseSomeOtherData(thing.DataWhichWasUpdated);
How to Refresh DbContext
I just found that the Enumerable
result should be evaluated because the Refresh
method gets it as object and doesn't evaluate it.
var context = ((IObjectContextAdapter)myDbContext).ObjectContext;
var refreshableObjects = (from entry in context.ObjectStateManager.GetObjectStateEntries(
EntityState.Added
| EntityState.Deleted
| EntityState.Modified
| EntityState.Unchanged)
where entry.EntityKey != null
select entry.Entity).ToList();
context.Refresh(RefreshMode.StoreWins, refreshableObjects);
And I prefer the following:
var refreshableObjects = myDbContext.ChangeTracker.Entries().Select(c=>c.Entity).ToList();
context.Refresh(RefreshMode.StoreWins, refreshableObjects);
Entity Framework.Refresh context
A couple things. DbContexts are not thread-safe so they should only be scoped within the thread they are accessed. Module-level variables would be a no-no. DbContexts are also pretty cheap to spin up once that first one-off hit for resolving their mapping has been paid. (Very first query against a DbContext of that type) Spin one up when it's needed, especially for multi-threaded or async operations.
string LL = _context.Logs.OrderByDescending(s => s.OccurTime).FirstOrDefault().Flag; //that data which I want to streaming
This can be more efficient. Your code effectively does:
SELECT TOP 1 * FROM Logs ORDER BY OccurTime DESC
Better would be:
string LL = context.Logs
.OrderByDescending(s => s.OccurTime)
.Select(s => s.Flag)
.FirstOrDefault();
This amounts to:
SELECT TOP 1 Flag FROM Logs ORDER BY OccurTime DESC
The difference being the first query returns all properties of the log when we only care about the Flag property.
These lines look fine...
await writer.WriteAsync(LL);
await Task.Delay(delay);
These don't make any sense. The "flag" is not an entity to reload, and these are the same method, just async vs sync flavours.
await _context.Entry(LL).ReloadAsync();
_context.Entry(LL).Reload();
I would change the code to look something like this for a start:
private async Task WriteItems(ChannelWriter<string> writer, int count, int delay)
{
while (!_abort)
{
using(var context = new LContext())
{
string flag = _context.Logs
.OrderByDescending(s => s.OccurTime)
.Select(s => s.Flag)
.FirstOrDefault();
await writer.WriteAsync(flag);
}
await Task.Delay(delay);
}
}
where _abort is a volatile Boolean by which we could have code/user to signal that the background task should terminate gracefully.
Given that this code would be running frequently, I would be considering a solution that writes to the log database but then also keeps a recent in-memory rolling cache of entries for something like this to draw down on as hitting the DB every 200ms adds to load for something that might not be critical in real-time.
Refresh entity instance with DbContext
You must use this:
public void Refresh(Document instance)
{
_ctx.Entry<Document>(instance).Reload();
}
WPF Entity Framework refresh one context entity
Well, i figured out what was wrong with my code.
My databindig was set to SelectedClient.last_status
. For some reason it didn't work as i expected. So i created a new viewmodel property called LastStatus and modified my RefreshClientInfo:
private void RefreshClientInfo(Client client)
{
Global.Database.Entry(client).Reload();
LastStatus = client.last_status;
SetValue(SelectedClientProperty, client);
}
and binded label to this new property. Now everything works correct.
Related Topics
Format of the Initialization String Does Not Conform to Specification Starting at Index 0
Getting Path Relative to the Current Working Directory
How to Programmatically Fill in a Form and 'Post' a Web Page
Tuples( or Arrays ) as Dictionary Keys in C#
C# Dictionary - One Key, Many Values
Write File from Assembly Resource Stream to Disk
Order of Event Handler Execution
Linq to Entities Does Not Recognize the Method Last. Really
How to Send Ctrl+C to a Process in C#
Dynamically Created Controls Losing Data After Postback
Is There Any Benefit of Using an Object Initializer
Why Can't a Duplicate Variable Name Be Declared in a Nested Local Scope
Deserializing JSON with Dynamic Keys