web-dev-qa-db-fra.com

Plusieurs appels SaveChanges dans la structure d'entité

Je construis mon propre référentiel personnalisé, basé sur une structure d'entité, et je crée des méthodes d'extension qui me permettent d'enregistrer des modèles de vue partielle en tant que modèles d'entité afin de créer mes propres méthodes d'ajout et de mise à jour.

Actuellement, chaque méthode a SaveChanges () de DbContext appelé à la fin, ce qui signifie que pour chaque modèle, un appel sera appelé.

Je construis ce schéma DAL de base pour les sites MVC4, ce qui signifie que la plupart du temps, je n’aurai accès qu’à 1 modèle, mais ce n’est pas forcément le cas.

Est-ce une trop mauvaise pratique d'appeler SaveChanges () pour chaque modèle lors de la mise à jour de 3 entités, ou dois-je tout ajouter en premier au contexte de l'objet et ensuite SaveChanges () comme une sorte de validation de transaction?

20
Admir Tuzović

Je sais que c'est un peu tardif, mais j'ai trouvé utile de le partager.

Maintenant, dans EF6, il est plus facile de résoudre ce problème en utilisant dbContext.Database.BeginTransaction()

comme ça :

using (var context = new BloggingContext())
{
    using (var dbContextTransaction = context.Database.BeginTransaction())
    {
        try
        {
            // do your changes
            context.SaveChanges();

            // do another changes
            context.SaveChanges();

            dbContextTransaction.Commit();
        }
        catch (Exception ex)
        {
            //Log, handle or absorbe I don't care ^_^
        }
    }
}

pour plus d'informations, regardez this

encore c'est dans EF6 à partir de

63
Wahid Bitar

Il est déconseillé d'appeler SaveChanges plusieurs fois (sans étendue de transaction) lorsque les entités associées doivent être conservées dans une seule transaction. Ce que vous avez créé est une abstraction qui fuit. Créez une classe d'unité de travail séparée ou utilisez le ObjectContext/DbContext lui-même.

5
Eranga

Je déconseille fortement d'appeler SaveChanges () dans chaque méthode. L'utilisation du modèle de référentiel et de l'unité de travail est la meilleure voie à suivre. Unit of work vous permet d’être plus efficace avec vos appels à la base de données et vous aide également à ne pas polluer votre base de données si certaines données ne sont pas valides (par exemple, les informations de l’utilisateur sont correctes, mais l’adresse échoue).

Voici un bon tutoriel pour vous aider. 

http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository- and-unit-of-work-patterns-in-an-asp- net-mvc-application

3
Colin Bacon

Ceci est une autre approche pour gérer plusieurs context.SaveChanges() en utilisant UnitOfWork que j'utilise actuellement.

Nous allons détenir tous les context.SaveChanges() méthode jusqu'à ce que ce dernier soit appelé.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace DataAccess
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly Context context;
        private readonly Dictionary<Type, object> repositories = new Dictionary<Type, object>();

        private int beginChangeCount;
        private bool selfManagedTransaction = true;

        public UnitOfWork(Context context)
        {
            this.context = context;
        }     

        //Use generic repo or init the instance of your repos here
        public IGenericRepository<TEntity> GetRepository<TEntity>() where TEntity : BaseEntityModel
        {
            if (repositories.Keys.Contains(typeof(TEntity)))
                return repositories[typeof(TEntity)] as IGenericRepository<TEntity>;

            var repository = new Repository<TEntity>(context);
            repositories.Add(typeof(TEntity), repository);

            return repository;
        }

        public void SaveChanges()
        {           
            if (selfManagedTransaction)
            {
                CommitChanges();
            }
        }

        public void BeginChanges()
        {
            selfManagedTransaction = false;
            Interlocked.Increment(ref beginChangeCount);
        }

        public void CommitChanges()
        {
            if (Interlocked.Decrement(ref beginChangeCount) > 0)
            {
                return;
            }

            beginChangeCount = 0;
            context.SaveChanges();
            selfManagedTransaction = true;
        }
    }
}

Échantillon à l'aide. 

Retrouvez mon commentaire dans le code ci-dessous

using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;

namespace BusinessServices.Domain
{
    public class AService : BaseBusinessService, IAService
    {
        private readonly IBService BService;
        private readonly ICService CService;
        private readonly IUnitOfWork uow;

        public AService (IBService BService, ICService CService, IUnitOfWork uow)
        {
            this.BService = BService;
            this.CService = CService;
            this.uow = uow;
        }

        public void DoSomeThingComplicated()
        {
            uow.BeginChanges();

            //Create object B - already have uow.SaveChanges() inside
            //still not save to database yet
            BService.CreateB();

            //Create object C  - already have uow.SaveChanges() inside
            //still not save to databse yet
            CService.CreateC();

            //if there are no exceptions, all data will be saved in database
            //else nothing in database
            uow.CommitChanges();

        }
    }
}
1
Hung Quach

Une nouvelle approche moderne telle que articulée ici est conseillée dans de tels scénarios.

Si vous connaissez la classe TransactionScope, vous savez déjà comment utiliser un DbContextScope. Ils sont très similaires en substance - la seule différence est que DbContextScope crée et gère des instances DbContext au lieu de transactions de base de données. Mais tout comme TransactionScope, DbContextScope est ambient, peut être imbriqué, son comportement d'imbrication peut être désactivé et fonctionne correctement avec les flux d'exécution async.

public void MarkUserAsPremium(Guid userId)  
{
    using (var dbContextScope = _dbContextScopeFactory.Create())
    {
        var user = _userRepository.Get(userId);
        user.IsPremiumUser = true;
        dbContextScope.SaveChanges();
    }
}

Dans une DbContextScope, vous pouvez accéder aux instances DbContext que l'étendue gère de deux manières. Vous pouvez les obtenir via la propriété DbContextScope.DbContexts comme ceci:

public void SomeServiceMethod(Guid userId)  
{
    using (var dbContextScope = _dbContextScopeFactory.Create())
    {
        var user = dbContextScope.DbContexts.Get<MyDbContext>.Set<User>.Find(userId);
        [...]
        dbContextScope.SaveChanges();
    }
}

Mais cela n’est bien sûr disponible que dans la méthode qui a créé la DbContextScope. Si vous devez accéder aux instances ambient DbContext n'importe où ailleurs (par exemple, dans une classe de référentiel), vous pouvez simplement créer une dépendance sur IAmbientDbContextLocator, que vous utiliseriez comme suit:

public class UserRepository : IUserRepository  
{
    private readonly IAmbientDbContextLocator _contextLocator;

    public UserRepository(IAmbientDbContextLocator contextLocator)
    {
        if (contextLocator == null) throw new ArgumentNullException("contextLocator");
        _contextLocator = contextLocator;
    }

    public User Get(Guid userId)
    {
        return _contextLocator.Get<MyDbContext>.Set<User>().Find(userId);
    }
}
0
Korayem