Lorsque ma table est mise à jour par une autre partie, le contexte de base de données dans CoreNet renvoie toujours l'ancienne valeur. Comment puis-je forcer l'actualisation du contexte de base de données?
J'ai effectué des recherches, mais je n'ai constaté que l'utilisation de la méthode Reload
, qui n'est pas disponible dans EF core, pour forcer le contexte à s'actualiser.
Une autre solution suggère de supprimer le contexte après l’utilisation, mais j’obtiens une erreur en disant que le contexte de la base de données est créé par une injection de dépendance et que je ne devrais pas le gâcher.
Vous indiquez que lorsque vous essayez de recréer votre DbContext
, vous obtenez une erreur concernant le contexte géré par votre système d'injection de dépendance. Il existe deux styles d'utilisation d'un système d'injection de dépendances pour la création d'objets. L’ID peut soit créer une instance singleton globale partagée en tant que service entre tous les consommateurs, soit créer une instance par étendue/unité de travail (par exemple, par demande sur un serveur Web).
Si votre système DI est configuré pour créer une seule instance partagée globale de DbContext
, vous rencontrerez divers problèmes associés à DbContext
de longue durée. DbContext
, de par sa conception, ne supprime jamais automatiquement les objets de son cache car il n'est pas conçu pour durer. Ainsi, une DbContext
de longue durée conservera la mémoire inutilement. En outre, votre code ne verra jamais les modifications apportées aux éléments chargés dans son cache sans recharger manuellement chaque entité qu'il charge. Ainsi, la meilleure pratique consiste à utiliser une seule DbContext
par unité de travail . Votre système DI peut vous aider à cet égard en étant configuré pour fournir une nouvelle instance pour chaque demande traitée par votre application dans une étendue. Par exemple, le système Dependency Injection d’ASP.NET Core prend en charge les instances de cadrage sur demande.
Le moyen le plus simple d'obtenir de nouvelles données consiste à créer une nouvelle variable DbContext
. Toutefois, au sein de votre unité de travail, ou dans les limites de la granularité de la portée fournie par votre système d’ID, vous pouvez déclencher un processus externe censé modifier votre entité directement dans la base de données. Vous devrez peut-être prendre connaissance de cette modification avant de quitter le domaine de votre ID ou de terminer votre unité de travail. Dans ce cas, vous pouvez forcer un rechargement en détachant votre instance de l'objet de données.
Pour ce faire, commencez par obtenir le EntityEntry<>
de votre objet. C’est un objet qui vous permet de manipuler le cache interne de DbContext
pour cet objet. Vous pouvez ensuite marquer cette entrée comme détachée en définissant EntitytState.Detached
dans sa propriété State
. Je pense que cela laisse l’entrée dans le cache, mais provoque le retrait de la DbContext
et le remplace lorsque vous chargez l’entrée ultérieurement. Ce qui compte, c’est qu’un chargement ultérieur renvoie une instance d’entité fraîchement chargée à votre code. Par exemple:
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);
Oh, cette question m'a noué pendant des jours.
J'utilise Visual Studio 2017 avec .Net Core 2.1, et mon code EF Core ressemblait à ceci:
// 1. Load a [User] record from our database
int chosenUserID = 12345;
User usr = dbContext.Users.FirstOrDefault(s => s.UserID == chosenUserID);
// 2. Call a web service, which updates that [User] record
HttpClient client = new HttpClient()
await client.PostAsync("http://someUrl", someContent);
// 3. Attempt to load an updated copy of the [User] record
User updatedUser = dbContext.Users.FirstOrDefault(s => s.UserID == chosenUserID);
À l'étape 3, il suffirait de définir "updatedUser" sur original version de l'enregistrement [User], plutôt que d'essayer de charger une nouvelle copie. Ainsi, si, après l’étape 3, je modifié cet enregistrement [Utilisateur], les paramètres que le service Web lui avait appliqués seraient perdus.
J'ai finalement trouvé deux solutions.
Je pourrais changer les paramètres ChangeTracker
. Cela a fonctionné, mais je m'inquiétais des effets secondaires de cette opération:
dbContext.ChangeTracker.QueryTrackingBehavior = Microsoft.EntityFrameworkCore.QueryTrackingBehavior.NoTracking;
Ou, je pourrais glisser la commande suivante avant de tenter de recharger l'enregistrement [Utilisateur] ...
await dbContext.Entry(usr).ReloadAsync();
Cela semble forcer .Net Core à recharger cet enregistrement [User], et la vie est redevenue belle.
J'espère que c'est utile ...
Repérer et corriger ce bogue m'a pris des jours ...
Il existe également un excellent article décrivant les différentes manières de contourner ce problème de mise en cache ici .
Vous devrez detach
l'entité du contexte ou implémenter votre propre extension pour .Reload()
Voici l'implémentation .Reload()
. Source: https://weblogs.asp.net/ricardoperes/implementing-missing-features-in-entity-framework-core
public static TEntity Reload<TEntity>(this DbContext context, TEntity entity) where TEntity : class
{
return context.Entry(entity).Reload();
}
public static TEntity Reload<TEntity>(this EntityEntry<TEntity> entry) where TEntity : class
{
if (entry.State == EntityState.Detached)
{
return entry.Entity;
}
var context = entry.Context;
var entity = entry.Entity;
var keyValues = context.GetEntityKey(entity);
entry.State = EntityState.Detached;
var newEntity = context.Set<TEntity>().Find(keyValues);
var newEntry = context.Entry(newEntity);
foreach (var prop in newEntry.Metadata.GetProperties())
{
prop.GetSetter().SetClrValue(entity,
prop.GetGetter().GetClrValue(newEntity));
}
newEntry.State = EntityState.Detached;
entry.State = EntityState.Unchanged;
return entry.Entity;
}
Où GetEntityKey()
:
public static object[] GetEntityKey<T>(this DbContext context, T entity) where T : class
{
var state = context.Entry(entity);
var metadata = state.Metadata;
var key = metadata.FindPrimaryKey();
var props = key.Properties.ToArray();
return props.Select(x => x.GetGetter().GetClrValue(entity)).ToArray();
}
Voici ma solution, j'espère que cela vous aidera.
Repository
public void Detach(TEntity entity)
{
foreach (var entry in _ctx.Entry(entity).Navigations)
{
if (entry.CurrentValue is IEnumerable<IEntity> children)
{
foreach (var child in children)
{
_ctx.Entry(child).State = EntityState.Detached;
}
}
else if (entry.CurrentValue is IEntity child)
{
_ctx.Entry(child).State = EntityState.Detached;
}
}
_ctx.Entry(entity).State = EntityState.Detached;
}
Donc par exemple avec:
Des classes:
public interface IEntity<TPrimaryKey>
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
TPrimaryKey Id { get; set; }
}
public class Sample : IEntity
{
public Guid Id { get; set; }
public string Text { get; private set; }
public Guid? CreatedByUserId { get; set; }
public virtual User CreatedByUser { get; set; }
public List<SampleItem> SampleItems { get; set; } = new List<SampleItem>();
}
public class SampleItem : IEntity
{
public Guid Id { get; set; }
public string Text { get; private set; }
public Guid? CreatedByUserId { get; set; }
public virtual User CreatedByUser { get; set; }
}
Manager
public async Task<Sample> FindByIdAsync(Guid id, bool includeDeleted = false, bool forceRefresh = false)
{
var result = await GetAll()
.Include(s => s.SampleItems)
.IgnoreQueryFilters(includeDeleted)
.FirstOrDefaultAsync(s => s.Id == id);
if (forceRefresh)
{
_sampleRepository.Detach(result);
return await FindByIdAsync(id, includeDeleted);
}
return result;
}
Manette
SampleManager.FindByIdAsync(id, forceRefresh: true);