Подтвердить что ты не робот

Продолжительность жизни инъекции заимствования

Я переписываю весь этот вопрос, потому что я понимаю причину, но все равно нуждаюсь в решении:

У меня есть повторяющаяся работа в Hangfire, которая работает каждую минуту и ​​проверяет базу данных, возможно, обновляет некоторые вещи, а затем выходит.

Я ввожу свой dbcontext в класс, содержащий метод задания. Я регистрирую этот dbcontext, чтобы получить инъекцию, используя следующие

builder.RegisterType<ApplicationDbContext>().As<ApplicationDbContext>().InstancePerLifetimeScope();

Однако кажется, что Hangfire не создает отдельную область жизни каждый раз, когда выполняется задание, потому что конструктор получает только один раз, хотя метод задания вызывается каждую минуту.

Это вызывает проблемы для меня. Если пользователь обновляет некоторые значения в базе данных (dbcontext получает инъекцию в другом месте и используется для обновления значений), контекст, который все еще используется, Hangfire начинает возвращать устаревшие значения, которые уже были изменены.

4b9b3361

Ответ 1

В настоящее время Hangfire использует общий экземпляр JobActivator для каждого Рабочего, которые используют следующий метод для разрешения зависимости:

    public override object ActivateJob(Type jobType)

Планируется добавить JobActivationContext к этому методу для Milestone 2.0.0.

Пока нет способа сказать, для какой работы определяется зависимость. Единственный способ, с помощью которого я могу решить эту проблему, - это использовать тот факт, что задания выполняются последовательно на разных потоках (я не знаю AutoFac, поэтому я использую Unity в качестве примера).

Вы можете создать JobActivator, который может хранить отдельные области для потока:

public class UnityJobActivator : JobActivator
{
    [ThreadStatic]
    private static IUnityContainer childContainer;

    public UnityJobActivator(IUnityContainer container)
    {
        // Register dependencies
        container.RegisterType<MyService>(new HierarchicalLifetimeManager());

        Container = container;
    }

    public IUnityContainer Container { get; set; }

    public override object ActivateJob(Type jobType)
    {
        return childContainer.Resolve(jobType);
    }

    public void CreateChildContainer()
    {
        childContainer = Container.CreateChildContainer();
    }

    public void DisposeChildContainer()
    {
        childContainer.Dispose();
        childContainer = null;
    }
}

Используйте JobFilter с IServerFilter для установки этой области для каждого задания (потока):

public class ChildContainerPerJobFilterAttribute : JobFilterAttribute, IServerFilter
{
    public ChildContainerPerJobFilterAttribute(UnityJobActivator unityJobActivator)
    {
        UnityJobActivator = unityJobActivator;
    }

    public UnityJobActivator UnityJobActivator { get; set; }

    public void OnPerformed(PerformedContext filterContext)
    {
        UnityJobActivator.DisposeChildContainer();
    }

    public void OnPerforming(PerformingContext filterContext)
    {
        UnityJobActivator.CreateChildContainer();
    }
}

И, наконец, настройте свой DI:

UnityJobActivator unityJobActivator = new UnityJobActivator(new UnityContainer());
JobActivator.Current = unityJobActivator;

GlobalJobFilters.Filters.Add(new ChildContainerPerJobFilterAttribute(unityJobActivator));

Ответ 2

Мы создали новый запрос на перенос в Hangfire.Autofac с работой, описанной Dresel. Надеюсь, он объединяется в основную ветку:

https://github.com/HangfireIO/Hangfire.Autofac/pull/4

Ответ 3

Изменить: С помощью Autofac,.NET 4.5 и Hangfire >= 1.5.0 используйте Hangfire.Autofac пакет nuget (github).

Работа с .NET 4.0 (Autofac 3.5.2 и Hangfire 1.1.1), мы установили решение Dresel с Autofac. Единственное отличие заключается в JobActivator:

using System;
using Autofac;
using Hangfire;

namespace MyApp.DependencyInjection
{
    public class ContainerJobActivator : JobActivator
    {
        [ThreadStatic]
        private static ILifetimeScope _jobScope;
        private readonly IContainer _container;

        public ContainerJobActivator(IContainer container)
        {
            _container = container;
        }

        public void BeginJobScope()
        {
            _jobScope = _container.BeginLifetimeScope();
        }

        public void DisposeJobScope()
        {
            _jobScope.Dispose();
            _jobScope = null;
        }

        public override object ActivateJob(Type type)
        {
            return _jobScope.Resolve(type);
        }
    }
}

Ответ 4

Чтобы обойти эту проблему, я создал одноразовый класс JobContext с ILifetimeScope, который будет удален, когда Hangfire завершит работу. Реальная работа вызвана отражением.

public class JobContext<T> : IDisposable
{
    public ILifetimeScope Scope { get; set; }

    public void Execute(string methodName, params object[] args)
    {
        var instance = Scope.Resolve<T>();
        var methodInfo = typeof(T).GetMethod(methodName);
        ConvertParameters(methodInfo, args);
        methodInfo.Invoke(instance, args);
    }

    private void ConvertParameters(MethodInfo targetMethod, object[] args)
    {
        var methodParams = targetMethod.GetParameters();

        for (int i = 0; i < methodParams.Length && i < args.Length; i++)
        {
            if (args[i] == null) continue;
            if (!methodParams[i].ParameterType.IsInstanceOfType(args[i]))
            {
                // try convert 
                args[i] = args[i].ConvertType(methodParams[i].ParameterType);
            }
        }
    }

    void IDisposable.Dispose()
    {
        if (Scope != null)
            Scope.Dispose();
        Scope = null;
    }
}

Существует JobActivator, который будет проверять действие и при необходимости создавать LifetimeScope.

public class ContainerJobActivator : JobActivator
{
    private readonly IContainer _container;
    private static readonly string JobContextGenericTypeName = typeof(JobContext<>).ToString();

    public ContainerJobActivator(IContainer container)
    {
        _container = container;
    }

    public override object ActivateJob(Type type)
    {
        if (type.IsGenericType && type.GetGenericTypeDefinition().ToString() == JobContextGenericTypeName)
        {
            var scope = _container.BeginLifetimeScope();
            var context = Activator.CreateInstance(type);
            var propertyInfo = type.GetProperty("Scope");
            propertyInfo.SetValue(context, scope);
            return context;
        }
        return _container.Resolve(type);
    }
}

Чтобы помочь в создании заданий, без использования строковых параметров есть еще один класс с некоторыми расширениями.

public static class JobHelper
{
    public static object ConvertType(this object value, Type destinationType)
    {
        var sourceType = value.GetType();

        TypeConverter converter = TypeDescriptor.GetConverter(sourceType);
        if (converter.CanConvertTo(destinationType))
        {
            return converter.ConvertTo(value, destinationType);
        }
        converter = TypeDescriptor.GetConverter(destinationType);
        if (converter.CanConvertFrom(sourceType))
        {
            return converter.ConvertFrom(value);
        }
        throw new Exception(string.Format("Cant convert value '{0}' or type {1} to destination type {2}", value, sourceType.Name, destinationType.Name));
    }

    public static Job CreateJob<T>(Expression<Action<T>> expression, params object[] args)
    {
        MethodCallExpression outermostExpression = expression.Body as MethodCallExpression;
        var methodName = outermostExpression.Method.Name;
        return Job.FromExpression<JobContext<T>>(ctx => ctx.Execute(methodName, args));
    }
}

Итак, чтобы поставить в очередь задание, например. со следующей подписью:

public class ResidentUploadService
{
    public void Load(string fileName)
    {
       //...
    }

Код для создания задания выглядит как

    var localFileName = "Somefile.txt";
    var job = ContainerJobActivator
                 .CreateJob<ResidentUploadService>(service => service.Load(localFileName), localFileName);
    var state = new EnqueuedState("queuename");
    var client = new BackgroundJobClient();
    client.Create(job,state);

Ответ 5

Решение поддерживается из коробки, поскольку hangfire.autofac 2.2.0.

В вашей ситуации, когда ваша зависимость регистрируется на всю жизнь, вы можете использовать немаркированные области, когда установка hangfire.autofac. По ссылке:

GlobalConfiguration.Configuration.UseAutofacActivator(builder.Build(), false);