Open Closed

Dependencies injected that aren't fully wired up? #8392


0
tusksoft created

This is a strange one, and I'm not sure if the problem lies with A0, ABP, Windsor, or some combination of the 3. We're injecting a dependency (D1) into another dependency (D2) into an application service. Both D1 and D2 implement ITransientDependency. D1 takes an IRepository via constructor injection. Here's an arbitrary code snippet of D1 to illustrate:

public class StatusProvider : IStatusProvider, ITransientDependency
{
    private readonly StatusForStaffMember _statusForStaffMember;

    public StatusProvider(IRepository<StatusForStaffMember> statusRepo, IRepository<StaffMember> staffMemberRepo, IAbpSession abpSession)
    {
        _statusForStaffMember = statusRepo.GetAllIncluding(s => s.User).FirstOrDefault(s => s.User.Id == abpSession.GetUserId()); //I'm gonna throw right here
        if (_statusForStaffMember == null)
        {
            var staffMember = staffMemberRepo.Get(abpSession.GetUserId());
            _statusForStaffMember = new StatusForStaffMember(staffMember);
        }
    }

    public StaffMember GetCurrentStaffMember()
    {
        return _statusForStaffMember.User;
    }
}

This explodes with a big nasty exception when statusRepo is used, but it boils down to this:

System.ObjectDisposedException: 'Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.

This seems odd, as we were just handed the repo. Why has something already been disposed within it when we haven't even made it into our ApplicationService yet? I have no idea, yet if we refactor the above to lazily grab our _statusForStaffMember from the repo like the following, everything works fine:

public class StatusProvider : IStatusProvider, ITransientDependency
{
    private readonly Func<StatusForStaffMember> _statusForStaffMember;

    public StatusProvider(IRepository<StatusForStaffMember> statusRepo, IRepository<StaffMember> staffMemberRepo, IAbpSession abpSession)
    {
            StatusForStaffMember closedOver;
            _statusForStaffMember = () =>
            {
                closedOver = _statusRepo.GetAllIncluding(s => s.User).FirstOrDefault(t => t.User.Id == abpSession.GetUserId()); //everything is fine now
                
                if (closedOver == null)
                {
                    var staffMember = staffMemberRepo.Get(abpSession.GetUserId());
                    closedOver = new StatusForStaffMember(staffMember);
                }

                return closedOver;
            };
    }

    public StaffMember GetCurrentStaffMember()
    {
        return _statusForStaffMember().User;
    }
}

I do NOT understand why I'm getting the ObjectDisposedException if I access the repo early but not if I access it late. If I dig into the repo at runtime via ProxyUtil.GetUnproxiedInstance(_statusRepo) I get ArgumentNullExceptions on Connection, Context, DbQueryTable, Table, and Transaction. The only properties that are properly instantiated appear to be IocResolver and UnitOfWorkManager. If I were going to venture to guess, I'd say that the repo is using Property Injection, and it's being handed to our dependency via Constructor Injection before its fully wired up and that the ObjectDisposedException is misleading.

Please help me understand what is happening, why, and how to resolve it without a using a lazy loading approach.


10 Answer(s)
  • 0
    aaron created
    Support Team

    I'm not sure if the problem lies with A0, ABP, Windsor, or some combination of the 3.

    Or, in this case, your usage.

    There is no unit of work in the constructor.

    Not recommended, but you could try to inject IUnitOfWorkManager and begin a unit of work explicitly in the constructor.

  • 0
    tusksoft created

    Please elaborate. I'm not sure how expecting fully wired up dependencies to be injected into my own dependency injected class regardless of timing and calling safe methods within said dependency should require me to manage my own UoW.

  • 0
    aaron created
    Support Team

    This is not about "wired up".

    Instances have to be created and injected into the requesting service before ABP can begin a unit of work for you.

    GetAllIncluding is "safe" and returns a query, but you are evaluating that query outside a unit of work yourself.

    In general, it is bad to make DB calls in constructors.

  • 0
    aaron created
    Support Team

    It is not reasonable, if even possible, to expect ABP or ASP<span></span>.NET Zero to create a DB connection for every class that you inject.

  • 0
    tusksoft created

    It is not reasonable, if even possible, to expect ABP or ASP.NET Zero to create a DB connection for every class that you inject.

    There's clearly a different paradigm within ABP/A0 than with most other frameworks. Typically I wouldn't expect an IoC container to inject dependencies that aren't ready for use, but that seems to be the case here due to the implicit UoW management that ABP provides. The fact that this works under some circumstances but not others adds to the confusion - eg, removing the intermediate call to GetAllIncluding() and instead using onlyFirstOrDefault() results in a successful DB query via the repository.

    I've read the UoW, DI, and Repository documentation for ABP, but I'm not seeing code examples or guidance on best practices are around how to implement a dependency that has its own dependency on a repository or dbcontext. Can you point me towards detailed documention or examples on how this should be done? Additional guidance would be appreciated.

  • 0
    aaron created
    Support Team

    Detailed documentation with an example on how to begin a unit of work explicitly with IUnitOfWorkManager: https://aspnetboilerplate.com/Pages/Documents/Unit-Of-Work#iunitofworkmanager

  • 0
    tusksoft created

    I've read that documentation. What it doesn't provide is guidance around how to build dependencies that themselves depend on repositories if we want to leverage ABP's implicit UoW management. Are there best practices to follow around this beyond "don't use the repo in construcors"?

  • 0
    aaron created
    Support Team

    If you insist.. Try to call a protected virtual method with [UnitOfWork] attribute:

    public StatusProvider(IRepository<StatusForStaffMember> statusRepo, IRepository<StaffMember> staffMemberRepo, IAbpSession abpSession)
    {
        SetStatusForStaffMember(statusRepo, staffMemberRepo, abpSession);
    }
    
    [UnitOfWork]
    protected virtual void SetStatusForStaffMember(IRepository<StatusForStaffMember> statusRepo, IRepository<StaffMember> staffMemberRepo, IAbpSession abpSession)
    {
        _statusForStaffMember = statusRepo.GetAllIncluding(s => s.User).FirstOrDefault(s => s.User.Id == abpSession.GetUserId());
        if (_statusForStaffMember == null)
        {
            var staffMember = staffMemberRepo.Get(abpSession.GetUserId());
            _statusForStaffMember = new StatusForStaffMember(staffMember);
        }
    }
    
  • 0
    ismcagdas created
    Support Team

    This issue is closed because of no recent activity. Please create a new issue if you are still having this problem.

  • 0
    ISTeam created

    Really helpful post while implementing a background service also in .NET Core 3.1 template. UnitOfWork is important to get resolve depencies to access the database!