mercredi 6 avril 2016

EF7 DbContext disposal

I am building a desktopp app which uses WPF and EF7 with SqLite. In my service classes I have an instance of IContextScopeLocator injected, which main job is to create and reuse instances of EF DbContexts.

ContextScope

   public class ContextScope : IDisposable
{
    private readonly PrzylepaDbContext _context;

    public ContextScope(PrzylepaDbContext context)
    {
        _context = context;
    }

    public EventHandler OnDisposed { get; set; }

    public PrzylepaDbContext Context
    {
        get { return _context; }
    }

    public void Dispose()
    {
        OnDisposed.Invoke(this, EventArgs.Empty);
    }
}

ContextScopeLocator

    public class ContextScopeLocator : IContextScopeLocator
{
    private readonly IContextFactory _factory;

    public ContextScopeLocator(IContextFactory factory)
    {
        _factory = factory;
    }

    private PrzylepaDbContext _currentContext;
    private readonly List<ContextScope> _currentScopes = new List<ContextScope>();

    public ContextScope GetScope()
    {
        if (_currentContext == null)
        {
            //building new EF DbContext if nescesary
            _currentContext = _factory.Create();
        }

        var scope = new ContextScope(_currentContext);

        scope.OnDisposed += OnDisposed;
        _currentScopes.Add(scope);
        return scope;
    }

    private void OnDisposed(object sender, EventArgs eventArgs)
    {
        var scope = sender as ContextScope;
        Debug.Assert(_currentScopes.Contains(scope));
        _currentScopes.Remove(scope);

        if (_currentScopes.Count == 0)
        {
            _currentContext.Dispose();
            _currentContext = null;
        }
    }
}

Then in my service method I can use it like that:

public IEnumerable<Client> GetPublicClients()
    {
        using (var scope = _scopeLocator.GetScope())
        {
            return scope.Context.Clients.Where(x => x.IsPublic).IncludeStandard().ToList();
        }
    }

And even with nested queries I can still get the same context. I will not be calling service methods from multiple threads so I thought this approach would work more less fine for me.

Then in my viewmodel class I receive a message in the following way

  private void ClientModifiedMessageHandler(NotifyEntityModifiedMessage<Client> msg)
    {
        if (msg.EntityId == ModifiedOffer.ClientId)
        {
            var client = _clientService.GetById(ModifiedOffer.ClientId);               
            ModifiedOffer.Client = client; //exception
        }
    }

Exception is raised by the DbContext which was used to get ModifiedOffer from the Db:

"The instance of entity type 'Przylepa.Data.Client' cannot be tracked because another instance of this type with the same key is already being tracked. For new entities consider using an IIdentityGenerator to generate unique key values."

The problem is that the old DbContext is still alive because it subscribes PropertyChanged event in the ModifiedOffer even though Dispose() was called on it (DbContext._disposed is true).

How can I make these DbContexts unsubscribe these events, so that I can do what I want with my model class instances? Thank you

Aucun commentaire:

Enregistrer un commentaire