Autofac。通过实体属性解析通用IRepository

问题描述 投票:1回答:2

我有2个存储库-SqlRepository和MongoRepository。这些存储库实现IRepository<T>

我的应用程序中有多个可能的配置。

示例1:我有2个数据库。当T有礼节SqlRepository<T>时,我想使用[SmallDataAttribute],否则我要使用MongoRepository

示例2:我有1个数据库-但我的应用程序不知道工作的数据库。启动config Autofac时,我可以检查此数据库的连接。

某些代码:

#region Registration providers

builder.RegisterInstance(new MongoClient("connectString")).As<IMongoClient>();
builder.RegisterInstance(new Context("MySQL", "connectString")).As<DbContext>();

#endregion

builder.Register(x => x.Resolve<IMongoClient>().GetDatabase("test")).As<IMongoDatabase>().SingleInstance();

#region Registration Repositories

builder.RegisterGeneric(typeof(MongoRepository<>))
    .WithParameter((info, context) => info.Name == "database",
        (info, context) => context.Resolve<IMongoDatabase>());

builder.RegisterGeneric(typeof(SqlRepository<>))
    .WithParameter((info, context) => info.Name == "context",
        (info, context) => context.Resolve<Context>());

#endregion

builder.RegisterInstance(new UnitOfWork()).As<IUnitOfWork>();
builder.Register(x => Container).AsSelf();
builder.RegisterBuildCallback(x => Container = x.Resolve<IContainer>());

P.S。上下文和数据库具有方法bool IsCanConnect();

我该怎么办?

另外,我需要做一些事情:

public IRepository<T> Repository<T>() where T : BaseEntity
        {
            if (_repositories == null)
                _repositories = new Dictionary<string, object>();

            var type = typeof(T).Name;

            if (_repositories.ContainsKey(type)) 
                return (IRepository<T>) _repositories[type];

            IRepository<T> repository;
            ITransaction transaction;

            if (((DatabaseEntityAttribute) typeof(T).GetCustomAttributes(typeof(DatabaseEntityAttribute), false).First()
                ).ProviderName == "MySQL" && AutofacModule.Scope.Resolve<DbContext>().Database.CanConnect())
            {
                transaction = new Transaction.Transaction(AutofacModule.Scope.Resolve<DbContext>().Database);
                repository = AutofacModule.Scope.Resolve<SqlRepository<T>>();
            }
            else
            {
                transaction = new Transaction.Transaction(AutofacModule.Scope.Resolve<IMongoDatabase>().Client.StartSession());
                repository = AutofacModule.Scope.Resolve<MongoRepository<T>>();
            }

            transaction.Begin();
            _transactions.Add(transaction);
            _repositories.Add(type, repository);
            return (IRepository<T>)_repositories[type];
        }

仅用于示例1.在这种情况下,我可以添加连接检查。

c# autofac
2个回答
0
投票

您可以使用IRegistrationSource执行所需的操作。

第一步是将所有存储库注册为命名服务。

builder.RegisterGeneric(typeof(MongoRepository<>))
       .WithParameter((info, context) => info.Name == "database",
            (info, context) => context.Resolve<IMongoDatabase>())
       .Named("sql", typeof(IRepository<>));

builder.RegisterGeneric(typeof(SqlRepository<>))
       .WithParameter((info, context) => info.Name == "context",
            (info, context) => context.Resolve<Context>())
       .Named("mongo", typeof(IRepository<>));

然后使用注册源根据具体类型使用正确的注册源

public class RepositoryRegistrationSource : IRegistrationSource
{
    public bool IsAdapterForIndividualComponents => false;

    public IEnumerable<IComponentRegistration> RegistrationsFor(
        Service service,
        Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
    {
        var typedService = service as IServiceWithType;

        if (typedService != null
            && typedService.ServiceType.IsGenericType
            && typedService.ServiceType.GetGenericTypeDefinition() == typeof(IRepository<>))
        {
            var t = typedService.ServiceType.GetGenericArguments()[0];

            var key = "mongo"; // get key using t.GetCustomAttributes();

            var r = RegistrationBuilder.ForDelegate(typedService.ServiceType, (c, p) => c.ResolveNamed(key, typedService.ServiceType, p))
                                       .CreateRegistration();

            yield return r;
        }
        yield break;
    }
}

然后像这样注册您的注册源:

builder.RegisterSource<RepositoryRegistrationSource>();

0
投票

Autofac不能只是“暗示”您想要做什么并生成这样的工厂。您也不必“为开放的通用名称注册工厂”。但是,您具有该工厂方法所需的逻辑,并且可以make use of the log4net integration module example来构建应该起作用的东西。

这是一个有效的代码段。我简化了您的操作,因此更易于阅读,但是您可以根据需要重新添加所有业务逻辑和连接检查以及所有其他内容。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Autofac;
using Autofac.Core;
using Autofac.Core.Registration;

namespace AutofacDemo
{
    public class Program
    {
        public static void Main()
        {
            var builder = new ContainerBuilder();
            builder.RegisterGeneric(typeof(MongoRepository<>)).AsSelf();
            builder.RegisterGeneric(typeof(SqlRepository<>)).AsSelf();
            builder.RegisterType<SqlConsumer>();
            builder.RegisterType<MongoConsumer>();
            builder.RegisterType<RepositoryFactory>().SingleInstance();
            builder.RegisterModule<RepositoryFactoryModule>();
            var container = builder.Build();

            var repo1 = container.Resolve<SqlConsumer>().Repository;
            Console.WriteLine("SqlEntity gets repo {0}", repo1.GetType());
            var repo2 = container.Resolve<MongoConsumer>().Repository;
            Console.WriteLine("MongoEntity gets repo {0}", repo2.GetType());
        }
    }

    // This is just a simplified version of the factory from the question.
    // It can do more complex stuff, but for the purposes of the example
    // it doesn't really need to.
    public class RepositoryFactory
    {
        private readonly Dictionary<string, object> _repositories = new Dictionary<string, object>();

        public IRepository<T> Repository<T>(IComponentContext context)
        {
            var type = typeof(T).Name;
            if (this._repositories.ContainsKey(type))
            {
                return (IRepository<T>)this._repositories[type];
            }

            IRepository<T> repository;
            var attribute = (DatabaseEntityAttribute)typeof(T).GetCustomAttributes(typeof(DatabaseEntityAttribute), false).FirstOrDefault();
            if (attribute?.ProviderName == "MySQL")
            {
                repository = context.Resolve<SqlRepository<T>>();
            }
            else
            {
                repository = context.Resolve<MongoRepository<T>>();
            }

            this._repositories[type] = repository;
            return repository;
        }
    }

    // Strongly based on the log4net integration module example.
    // https://autofaccn.readthedocs.io/en/latest/examples/log4net.html
    // You can't register a "factory for an open generic" so we use a
    // module to tie the factory to the resolution. Note this means
    // you CAN'T do
    // container.Resolve<IRepository<T>>()
    // directly - it will only inject stuff into consuming classes!
    public class RepositoryFactoryModule : Autofac.Module
    {
        // This handles injecting properties if property injection is enabled.
        private static void InjectRepositoryProperties(object instance, IComponentContext context)
        {
            var instanceType = instance.GetType();
            var properties = instanceType
              .GetProperties(BindingFlags.Public | BindingFlags.Instance)
              .Where(p => IsRepositoryType(p.PropertyType) && p.CanWrite && p.GetIndexParameters().Length == 0);

            // Set the properties located.
            var factory = context.Resolve<RepositoryFactory>();
            foreach (var propToSet in properties)
            {
                var entityType = EntityTypeFromRepositoryType(propToSet.PropertyType);
                propToSet.SetValue(instance, CallFactoryMethod(entityType, factory, context), null);
            }
        }

        // This handles injecting constructor parameters.
        private static void OnComponentPreparing(object sender, PreparingEventArgs e)
        {
            e.Parameters = e.Parameters.Union(
                new[]
                {
                    new ResolvedParameter(
                        (p, i) => IsRepositoryType(p.ParameterType),
                        (p, i) => CallFactoryMethod(EntityTypeFromRepositoryType(p.ParameterType), i.Resolve<RepositoryFactory>(), i)
                    ),
                });
        }

        // Convenience method for determining if we can inject the repository.
        private static bool IsRepositoryType(Type type)
        {
            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IRepository<>);
        }

        // The factory method is an open generic so we need to make a closed generic
        // before we can call it.
        private static object CallFactoryMethod(Type entityType, RepositoryFactory factory, IComponentContext context)
        {
            var method = typeof(RepositoryFactory).GetMethod("Repository").MakeGenericMethod(entityType);
            return method.Invoke(factory, new object[] { context });
        }

        // We have IRepository<T>, we need the T part for constructing the factory method signature.
        private static Type EntityTypeFromRepositoryType(Type repositoryType)
        {
            return repositoryType.GetGenericArguments().First();
        }

        // This is what actually wires up the above methods to handle resolution ops.
        protected override void AttachToComponentRegistration(IComponentRegistryBuilder componentRegistryBuilder, IComponentRegistration registration)
        {
            // Handle constructor parameters.
            registration.Preparing += OnComponentPreparing;

            // Handle properties.
            registration.Activated += (sender, e) => InjectRepositoryProperties(e.Instance, e.Context);
        }
    }

    // The rest is just placeholder stuff to make it compile and basically
    // act like the thing in the example. Additional stuff isn't really
    // important - adding IsCanConnect() or whatever is really just adding
    // to the existing skeleton here; it doesn't impact the overall pattern.

    public interface IRepository<T>
    {
    }

    public class MongoRepository<T> : IRepository<T>
    {
    }

    public class SqlRepository<T> : IRepository<T>
    {
    }

    [AttributeUsage(AttributeTargets.Class)]
    public class DatabaseEntityAttribute : Attribute
    {
        public DatabaseEntityAttribute(string providerName)
        {
            this.ProviderName = providerName;
        }

        public string ProviderName { get; }
    }

    [DatabaseEntity("Mongo")]
    public class MongoEntity
    {
    }

    [DatabaseEntity("MySQL")]
    public class SqlEntity
    {
    }

    public class MongoConsumer
    {
        public MongoConsumer(IRepository<MongoEntity> repository)
        {
            this.Repository = repository;
        }

        public IRepository<MongoEntity> Repository { get; }
    }

    public class SqlConsumer
    {
        public SqlConsumer(IRepository<SqlEntity> repository)
        {
            this.Repository = repository;
        }

        public IRepository<SqlEntity> Repository { get; }
    }
}

这里的前提是:

  • 您有一个工厂可以查看属性(或其他属性),并决定要解决的问题。
  • Autofac模块可以查看要解决的问题,并确定是否存在IRepository<T>参数(在构造函数参数中,或者如果启用了属性,则可以使用属性),并调用工厂方法。

[这里有一个警告,这是您将无法直接解析存储库。也就是说,如果您调用container.Resolve<IRepository<SomeEntity>>,它会not遍历模块。但是,我猜想,每当需要存储库时,它都是构造函数参数,因此您应该很好。 (手动解决的问题称为“服务位置”,无论如何,通常应该避免这种情况。)

这里有很多需要消化的东西,因为您要问的是相当高级的内容,但是如果您运行该代码段,您将看到它起作用-您将基于构造函数参数和实体属性获取正确的存储库。] >

我将添加一些其他注释,只是有些相关:

  • 确保存储库工厂为单例,否则缓存字典将无法正常工作。
  • 存储库的字典不是线程安全的,因此,如果某个Web应用程序中有多个尝试都试图获取存储库的请求,则缓存将无法按您想要的方式工作。您需要对此进行一些锁定。
  • 您可以通过创建包含typeof(RepositoryFactory).GetMethod("Repository")之类的静态变量来优化某些反射性能。这样,您不必每次都查找它。
  • 示例可能有点草率。像CallFactoryMethod方法一样,它接受工厂实例和组件上下文,以便工厂可以解析适当的后备仓库。但是,在每次调用CallFactoryMethod之前,您都会看到我们必须解析该工厂实例。仅通过解决该方法中的出厂权利,就可以优化CallFactoryMethod签名。另一方面,通过传入工厂,它使我们可以在属性解析函数中执行foreach循环,而无需一遍又一遍地解决工厂问题。无论如何,可能会有重构和优化。
  • 希望这能解除您的封锁。如果是这样,请接受此作为答案,这样我会为您提供帮助而获得好评!

© www.soinside.com 2019 - 2024. All rights reserved.