// ========================================================================
//
// Copyright (C) 2016-2017 MARC CHEVALDONNE
//                         marc.chevaldonne.free.fr
//
// Module        : Program.cs
// Author        : Marc Chevaldonné
// Creation date : 2016-11-12
//
// ========================================================================

using static System.Console;
using System.Linq;
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;

namespace ex_042_018_EF_concurrency_databaseWins
{
    /// <summary>
    /// Cet exemple simule des mises à jours concurrentes de données dans la table.
    /// Contrairement à l'exemple précédent, il montre comment il est possible de gérer facilement ces insertions concurrentes avec EntityFramework.
    /// Il laisse la priorité à la base de données, c'est-à-dire que si le client essaye de modifier les données alors qu'elles ont été modifiées entre sa récupération et sa mise à jour,
    /// son action est rejetée.
    /// Cette méthode est souvent appelée "Database Wins" et change l'état de l'entité en cours de modification pour donner la priorité à la base de données.
    /// 
    /// Deux méthodes sont proposées pour cela :
    /// - (METHODE 1) : la première consiste à dédier une colonne pour les vérifications (update et delete) qu'on appelle généralement RowVersion et qu'on décore avec l'attribut Timestamp (cf. classe Nounours)
    /// - la seconde permet de ne pas rajouter de colonne spécifique mais de préciser quelles seront les colonnes à utiliser pour les tests de vérification
    ///     - (METHODE 2a) : soit en annotation de données ([ConcurrencyCheck()]) (cf. classe Nounours)
    ///     - (METHODE 2b) : soit en fluent API (modelBuilder.Entity<Nounours>().Property<int>(n => n.NbPoils).IsConcurrencyToken();) (cf. classe NounoursDBEntities)
    ///     
    /// Attention de bien noter que la méthode UpdateNounours a été modifiée pour catcher l'exception DbUpdateConcurrencyException et appeler la méthode Reload() en cas d'exception.
    /// 
    /// Commentez et décommentez les méthodes pour tester les différentes possibilités
    /// </summary>
    class Program
    {
        /// <summary>
        /// récupère un Nounours à partir de son id
        /// </summary>
        /// <param name="id">l'id du Nounours à récupérer</param>
        /// <returns>le Nounours récupéré (null, si l'id n'est pas connu dans la base)</returns>
        static Nounours GetNounours(Guid id)
        {
            Nounours nounours = null;

            using (NounoursDBEntities db = new NounoursDBEntities())
            {
                nounours = db.NounoursSet.Single(n => n.UniqueId == id);
            }

            return nounours;
        }

        /// <summary>
        /// mise à jour du Nounours dans la base
        /// </summary>
        /// <param name="nounours">nounours à mettre à jour</param>
        static void UpdateNounours(Nounours nounours)
        {
            using (NounoursDBEntities db = new NounoursDBEntities())
            {
                db.NounoursSet.Attach(nounours);
                db.Entry<Nounours>(nounours).State = EntityState.Modified;

                try
                {
                    db.SaveChanges();
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    //observez en mode debug de quelle manière la variable nounours est affectée par l'instruction suivante
                    ex.Entries.Single().State = EntityState.Unchanged;
                    db.SaveChanges();
                }
            }
        }

        static void Main(string[] args)
        {
            List<Guid> ids = new List<Guid>();

            //création du DbContext et injection de la dépendance à MyStubDataInitializationStrategy
            using (NounoursDBEntities db = new NounoursDBEntities())
            {
                DbContextInitializer.Seed(db);

                //récupère la liste des ids
                ids = db.NounoursSet.Select(n => n.UniqueId).ToList();
            }

            PrintTable("Au démarrage, la table contient :");

            WriteLine();

            //DEBUT DE LA SIMULATION DE MISES A JOUR CONCURRENTES

            //1. Le premier utilisateur récupère les données du premier nounours
            Nounours nounours1 = GetNounours(ids[0]);
            WriteLine("L'utilisateur 1 récupère le premier nounours");

            //2. Le deuxième utilisateur réupère les données du premier nounours
            Nounours nounours2 = GetNounours(ids[0]);
            WriteLine("L'utilisateur 2 récupère le premier nounours");

            //jusque-là, la table contient :
            // "Chewbacca", 27/05/1977, 1234567 poils
            // "Yoda", 21/05/1980, 3 poils
            // "Ewok", 25/05/1983, 3456789 poils

            //3. Le premier utilisateur tente de mettre à jour le nombre de poils, mais ne met pas tout de suite la table à jour
            nounours1.NbPoils = 2;

            WriteLine("Soudain, la table est modifiée par des facteurs extérieurs... (un recommandé peut-être...)");
            //4. La table est mise à jour par un autre biais
            using (NounoursDBEntities db = new NounoursDBEntities())
            {
                db.Database.ExecuteSqlCommand($"UPDATE dbo.TableNounours SET NbPoils = 5 WHERE UniqueId = {ids[0]}");
                db.SaveChanges();
            }
            PrintTable("Maintenant, la table contient :");
            //maintenant, la table contient :
            // "Chewbacca", 27/05/1977, 5 poils <==========================
            // "Yoda", 21/05/1980, 3 poils
            // "Ewok", 25/05/1983, 3456789 poils

            //5. L'utilisateur 1 tente de mettre à jour le nombre de poils
            UpdateNounours(nounours1);
            PrintTable("Après la tentative de modification du nombre de poils de Chewbacca à 2 par l'utilisateur 1 :");
            //une exception est lancée.
            //En database wins, nounours1 est modifié et son nombre de poils passe à 5.

            //6. Le deuxième utilisateur tente de mettre à jour la propriété Date De Naissance
            nounours2.DateDeNaissance = DateTime.Today;
            UpdateNounours(nounours2);
            PrintTable("Après la tentative de modification de la date de naissance de Chewbacca à aujourd'hui par l'utilisateur 2 :");
            //une exception est lancée.
            //En database wins, nounours2 est modifié et sa date de naissance repasse au 27/05/1977.
        }

        static void PrintTable(string intro)
        {
            WriteLine();
            WriteLine(intro);
            using (NounoursDBEntities db = new NounoursDBEntities())
            {
                //affiche les nounours après les modifications
                WriteLine("Après modifications");
                foreach (var n in db.NounoursSet)
                {
                    WriteLine($"\t{n}");
                }
            }
        }
    }
}
