web-dev-qa-db-fra.com

Invalidation / désactivation du cache Entity Framework

Je vois qu'il y a beaucoup de questions sur le cache EF, mais je n'ai pas encore trouvé de solution à mon problème.

La question directe est

Comment désactiver complètement le cache Entity Framework 6? Ou, puis-je dire par programme à EF d'oublier le cache car quelque chose est arrivé aux données?

Contexte

Tout d'abord, j'ai hérité d'une application faite d'un étrange mélange d'EF (modèle d'abord pour définir les entités) et de SQL ancien (pour manipuler les données). Ce que j'ai fait, c'est de refactoriser l'application afin de:

  • Faire des requêtes simples (comme GetAll() pour une entité) utilisez EF6 LINQ
  • Laisser la manipulation de données complexes en SQL, en utilisant DbContext.Database.Connection si nécessaire
  • Ajouter Spring.Web support pour activer DI et les transactions (pas encore)

À l'heure actuelle, j'ai réorganisé le code afin que la fonction principale de l'application (exécution de requêtes SQL complexes sur d'énormes ensembles de données) fonctionne comme auparavant, mais la recherche d'entités de domaine se fait plus intelligemment en utilisant comme la plupart Entity Framework possible

Comme la plupart ...

L'une des pages dont j'ai hérité est une page à cases à cocher multiples que je vais vous montrer pour une meilleure compréhension. Je ne discuterai pas du choix de l'implémentateur précédent, car il est moins coûteux de résoudre mon problème actuel et le code de refactorisation ultérieur que de bloquer le développement d'une fonctionnalité cassée.

Voici à quoi ressemble la page

enter image description here

Fondamentalement, la méthode Controller est la suivante

    [HttpPost]
    public ActionResult Index(string[] codice, string[] flagpf, string[] flagpg, string[] flagammbce, string[] flagammdiv, string[] flagammest,
        string[] flagintab, string[] flagfinanz, string[] flagita, string[] flagest, string pNew){
            Sottogruppo2015Manager.ActivateFlagFor("pf", flagpf);
            Sottogruppo2015Manager.ActivateFlagFor("pg", flagpg);
            Sottogruppo2015Manager.ActivateFlagFor("ammbce", flagammbce);
            Sottogruppo2015Manager.ActivateFlagFor("ammdiv", flagammdiv);
            Sottogruppo2015Manager.ActivateFlagFor("ammest", flagammest);
            Sottogruppo2015Manager.ActivateFlagFor("intab", flagintab);
            Sottogruppo2015Manager.ActivateFlagFor("finanz", flagfinanz);
            Sottogruppo2015Manager.ActivateFlagFor("ita", flagita);
            Sottogruppo2015Manager.ActivateFlagFor("est", flagest);

            return RedirectToAction("Index", new { pNew });
 }

Chaque string[] paramètre est une colonne du tableau. La méthode ActivateFlagFor exécute deux requêtes en séquence

UPDATE table SET --param1-- = 0;
UPDATE table SET --param1-- = 1 where id in (--param2--)

Quand le cache entre en action

Voici le comportement:

  • Je charge d'abord la page émettant une sélection LINQ: les chèques correspondent aux uns et aux zéros dans les colonnes
  • Je modifie un ou plusieurs chèques et je soumets
  • Le contrôleur émet les requêtes pour mettre à jour les vérifications dans la base de données
  • Avant de rediriger (! Signifie nouvelle demande!) Pour recharger la page, je vérifie la base de données et les changements sont appliqués
  • La page se recharge en émettant la même sélection LINQ ci-dessus: les anciens chèques sont affichés

Je suis sûr qu'il s'agit d'un problème de mise en cache, car le rechargement de l'application résout le problème. Étant donné que la principale caractéristique de l'application est entièrement basée sur SQL, les modifications apportées aux tables de recherche se reflètent dans le fonctionnement principal et c'est le comportement correct.

Je comprends que la mise en cache EF est une excellente fonctionnalité pour les performances, mais dans mon cas, je ne le veux tout simplement pas, du moins jusqu'à ce que je migre toute l'application vers LINQ DML (probablement impossible).

Comment j'utilise le DbContext

Bien sûr, certains d'entre vous peuvent se demander "comment utilisez-vous votre DbContext?" "vous en débarrassez-vous correctement?".

  • Je n'ai pas encore intégré les transactions Spring dans mes classes Manager
  • Chaque objet qui exécute actions sur la base de données est un I<Entity>Manager extension BaseManager
  • DbContext est un objet Spring à portée de requête. J'ai déjà interrogé sur la suppression des objets à portée de requête mais j'ai actuellement implémenté une solution de contournement qui, bien que mauvaise, supprime correctement le DbContext à la fin de la demande.

Exemple de code

public class ExampleManagerImpl : BaseManager, IExampleManager
{
    public void ActivateFlagFor(string aFlag, string[] aList)
    {
        string sql = "UPDATE table SET flag" + aFlag + " = 0";
        RunStatementV1(sql);

        if (aList != null && aList.Any())
        {
            sql = "UPDATE table SET flag" + aFlag + " = 1 WHERE id in (" + aList.ToCsvApex() + ")";
            RunStatementV1(sql);
        }
    }

    public IList<Models.Example> GetAll()
    {
        return DataContext.example.ToList(); //I don't dispose of the DataContext willingly
    }
}

et

public abstract class BaseManager {

    public DbContext DataContext { get; set; } //Autowired

    protected void RunStatementV1(string aSqlStatement)
    {
        IDbConnection connection = DataContext.Database.Connection;
        if (connection.State == ConnectionState.Closed || connection.State == ConnectionState.Broken) connection.Open(); //Needed because sometimes I found the connection closed, even if I don't dispose of it
        using (IDbCommand command = connection.CreateCommand())
        {
            command.CommandText = aSqlStatement;
            command.ExecuteNonQuery();
        }

    }
}

Ce que j'ai essayé

16

Si vous souhaitez ignorer complètement le cache d'EF6 pour la récupération des données, ajoutez AsNoTracking() à la fin de votre requête (avant d'appeler ToList() ou de faire quoi que ce soit qui exécuterait la requête.

MSDN sur AsNoTracking()

Veuillez noter que cela ne vérifiera pas le cache pour les données existantes, ni ajoutera les résultats de l'appel de base de données au cache. En outre, Entity Framework ne détectera pas automatiquement les modifications apportées aux entités que vous récupérez de la base de données. Si vous souhaitez modifier des entités et les sauvegarder dans la base de données, vous devrez attacher les entités modifiées avant d'appeler SaveChanges().

Votre méthode est actuellement:

public IList<Models.Example> GetAll()
{
    return DataContext.example.ToList();
}

Il se changerait en:

public IList<Models.Example> GetAll()
{
    return DataContext.example.AsNoTracking().ToList();
}

Si vous êtes intéressé par d'autres options pour gérer le cache d'EF, j'ai écrit un blog sur EF6 Cache Busting .

35
CodeThug

J'ai aussi eu ce problème, mais je pouvais le réparer.

J'utilise le modèle de référentiel et j'utilise DI par défaut de .Net Core. J'utilise AddSingleton (...), mais il est faux de l'utiliser avec DbContext.

J'ai donc changé pour AddScoped, comme je l'ai lu dans les documents: Les services de durée de vie sont créés une fois par demande.

Cela a résolu mon problème.

Vous devez lire cette section de ms docs: Service Lifetimes and Registration Options

5
Alef Carlos