Categories
Uncategorized

net core ideas Series: generic storage and declarative things to achieve the most elegant crud operations

Series catalog

1.net core ideas Series: Native DI + AOP achieve spring boot programming notes

Ha ha ha ha, Hello, everyone, I just like the high-yielding sow triple, long time, I have been thinking, how to achieve efficient and compact storage mode (DDD is not in the warehouse, more accurate to say that mapper database table ), realize that the use of annotations in spring boot operation to achieve things, Riyousuosi, eventually resulting, this article concentrates the essence of my work unit for storage mode and mode of understanding, hoping to help you, if you say where wrong, I hope you let me know. Because ef core itself realizes these two modes, and then on the basis of ef core of the package would be meaningless learning, so this is used ORM program is dapper + dapper.contrib, these two libraries are from the door stackexchange, which is famous explosion stack it, they produced the library there StackExchange.Redis, so the quality Needless to say, before the start of the text, to install these two libraries on nuget. BTW, dynamic proxy, annotations programming, AOP throughout this series has always been, no bb, the text begins.

1. The definition of classes used

Last time, drag racing, this time we talk, go to the gas station, refueling the process of it, there is one thing to operate, that is, the gas station must give me enough oil, I give him to pay a bit like bank transfers, then come out in table 2, motor oil gauge (oilQuantity) and cash balances (cashBalance), corresponding to the following table structure and entity class, are relatively simple, in addition to the primary key id, oilQuantity table has only a quantity of oil field, cashBalance table only a balance balance fields, database using mysql, namespace annotation TableAttribute entity classes using System.ComponentModel.DataAnnotations.Schema.

CREATE TABLE test.oilQuantity (
    id INT NOT NULL AUTO_INCREMENT,
    quantity DECIMAL NULL,
    CONSTRAINT caroil_pk PRIMARY KEY (id)
)
ENGINE=InnoDB
DEFAULT CHARSET=utf8
COLLATE=utf8_general_ci;
CREATE TABLE test.cashBalance (
    id INT NOT NULL AUTO_INCREMENT,
    balance DECIMAL NOT NULL,
    CONSTRAINT cashbalance_pk PRIMARY KEY (id)
)
ENGINE=InnoDB
DEFAULT CHARSET=utf8
COLLATE=utf8_general_ci;

 

 [Table("OilQuantity")]
    public class OilQuantity
    {
        [Key]
        public int Id { set; get; }
        /// 
        /// 油量
        /// 
        public decimal Quantity { set; get; }
    }
 [Table("CashBalance")]
    public class CashBalance
    {
        [Key]
        public int Id { set; get; }
        /// 
        /// 余额
        /// 
        public decimal Balance { set; get; }
    }

Define the database link farms, and his class interface IDbFactory implementation class DbFactory, is mainly responsible for this class to create a database link, the link is divided into two kinds, one is the short link, when things do not turn use, throw-destroyed, CPA are new link, the other is a long link with the operation of things, DbFactory itself registered as a scope level, after long links created will be saved in DbFactory of properties, so in disguise to achieve a level of scope, empathy, long Links transaction will be saved after opening, to achieve things in warehousing operations.

 public interface IDbFactory:IDisposable
    {
        /// 
        /// 长链接
        /// 
        IDbConnection LongDbConnection { get; }

        /// 
        /// 长链接的事物
        /// 
        IDbTransaction LongDbTransaction { get; }

        /// 
        /// 短链接
        /// 
        IDbConnection ShortDbConnection { get; }

        /// 
        /// 开启事务
        /// 
        void BeginTransaction();
    }
  /// 
    /// 负责生成和销毁数据库链接
    /// 
    public class DbFactory:IDbFactory
    {
        [Value("MysqlConnectionStr")]
        public string MysqlConnectionStr { set; get; }

        /// 
        /// 长连接
        /// 
        public IDbConnection LongDbConnection { private set; get; }

        /// 
        /// 长连接的事物
        /// 
        public IDbTransaction LongDbTransaction { private set; get; }

        /// 
        /// 短链接
        /// 
        public IDbConnection ShortDbConnection
        {
            get
            {
                var dbConnection = new MySqlConnection(MysqlConnectionStr);
                dbConnection.Open();
                return dbConnection;
            }
        }

        /// 
        /// 开启事务
        /// 
        /// 
        public void BeginTransaction()
        {
            if (LongDbConnection == null)
            {
                LongDbConnection = new MySqlConnection(MysqlConnectionStr);
                LongDbConnection.Open();
                LongDbTransaction = LongDbConnection.BeginTransaction();
            }
        }

        public void Dispose()
        {
            LongDbTransaction?.Dispose();
            if (LongDbConnection?.State != ConnectionState.Closed)
            {
                LongDbConnection?.Close();
            }
            LongDbConnection?.Dispose();
            LongDbTransaction = null;
            LongDbConnection = null;
        }
    }

And define his work unit interface implementation class IUnitOfWork the UnitOfWork, can be seen, there is a property IUnitOfWork ActiveNumber reference number, the main effect of this property is that if a marked [the Transactional] nested in another way marked [transactional] way, we can be confirmed by the count, who is the outermost layer of concrete ways to achieve inner method is not open in another thing, and do not commit the transaction before the end of the inner method results . At the same time it, UnitOfWork only responsible for operations associated with the transaction, the other to create a link, create things and other operations are done by the injection of IDbFactory.

 public interface IUnitOfWork : IDisposable
    {
        /// 
        /// 引用次数,开启一次事物加+1,当次数为0时提交,主要是为了防止事物嵌套
        /// 
        int ActiveNumber { get; set; }

        /// 
        /// 开启事务
        /// 
        void BeginTransaction();
        
        /// 
        /// 提交
        /// 
        void Commit();

        /// 
        /// 事物回滚
        /// 
        void RollBack();
    }
 public class UnitOfWork : IUnitOfWork
    {
        /// 
        /// 工作单元引用次数,当次数为0时提交,主要为了防止事物嵌套
        /// 
        public int ActiveNumber { get; set; } = 0;

        [Autowired]
        public IDbFactory DbFactory { set; get; }

        public void BeginTransaction()
        {
            if (this.ActiveNumber == 0)
            {
                DbFactory.BeginTransaction();
                Console.WriteLine("

Open affairs

"); } this.ActiveNumber++; } public void Commit() { this.ActiveNumber--; if (this.ActiveNumber == 0) { if (DbFactory.LongDbConnection != null) { try { DbFactory.LongDbTransaction.Commit(); } catch (Exception e) { DbFactory.LongDbTransaction.Rollback(); Console.WriteLine(e); throw; } finally { this.Dispose(); } } Console.WriteLine("

Commit the transaction

"); } } public void Dispose() { DbFactory.Dispose(); } public void RollBack() { if (this.ActiveNumber > 0 && DbFactory.LongDbTransaction != null) { try { DbFactory.LongDbTransaction.Rollback(); } catch (Exception e) { Console.WriteLine(e); throw; } } Console.WriteLine("

Roll back the transaction

"); } }

Generic storage interfaces IRepository class and realize his BaseRepository , for lazy, just write a sync portion, asynchronous Similarly, if the use of asynchronous, the interceptor must use asynchronous interceptors. BaseRepository injected through the IUnitOfWork property and IDbFactory, IUnitOfWork responsible for warehousing told, the use of long or short connecting link, IDbFactory responsible for providing specific links and things, but more details of crud operation, is completed by the dapper and dapper.contrib of. Look at the code var dbcon = Uow.ActiveNumber == 0 DbFactory.ShortDbConnection: DbFactory.LongDbConnection; Analyzing can be seen by reference counting ActiveNumber uow is used to determine the long link or the short link, and if ActiveNumber == 0, then, in the? Links will be freed after the database operations.

 public interface IRepository where T : class
    {
        IList GetAll();
        T Get(object id);

        T Insert(T t);
        IList Insert(IList t);

        void Update(T t);
        void Update(IList t);

        void Delete(IList t);
        void Delete(T t);
    }

 public class BaseRepository : IRepository where T : class
    {
        [Autowired]
        public IUnitOfWork Uow { set; get; }

        [Autowired]
        public IDbFactory DbFactory { set; get; }

        public IList GetAll()
        {
            var dbcon = DbFactory.ShortDbConnection;

            var result = dbcon.GetAll().ToList();

            dbcon.Close();
            dbcon.Dispose();

            return result;
        }

        public T Get(object id)
        {
            var dbcon = DbFactory.ShortDbConnection;

            var result = dbcon.Get(id);

            dbcon.Close();
            dbcon.Dispose();

            return result;
        }

        public T Insert(T t)
        {
            var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;
            dbcon.Insert(t, DbFactory.LongDbTransaction);

            if (Uow.ActiveNumber == 0)
            {
                dbcon.Close();
                dbcon.Dispose();
            }

            return t;
        }

        public IList Insert(IList t)
        {
            var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;
            dbcon.Insert(t, DbFactory.LongDbTransaction);

            if (Uow.ActiveNumber == 0)
            {
                dbcon.Close();
                dbcon.Dispose();
            }

            return t;
        }

        public void Delete(T t)
        {
            var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;
            dbcon.Delete(t, DbFactory.LongDbTransaction);

            if (Uow.ActiveNumber == 0)
            {
                dbcon.Close();
                dbcon.Dispose();
            }

        }

        public void Delete(IList t)
        {
            var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;
            dbcon.Delete(t, DbFactory.LongDbTransaction);

            if (Uow.ActiveNumber == 0)
            {
                dbcon.Close();
                dbcon.Dispose();
            }

        }

        public void Update(T t)
        {
            var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;
            dbcon.Update(t, DbFactory.LongDbTransaction);

            if (Uow.ActiveNumber == 0)
            {
                dbcon.Close();
                dbcon.Dispose();
            }

        }

        public void Update(IList t)
        {
            var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;
            dbcon.Update(t, DbFactory.LongDbTransaction);
            if (Uow.ActiveNumber == 0)
            {
                dbcon.Close();
                dbcon.Dispose();
            }

        }
    }

View Code

Things interceptor TransactionalInterceptor, before the start of the method, if the intercepted method [TransactionalAttribute] notes, is opened by uow Affairs, at the end of the method, if the intercepted method [TransactionalAttribute] notes, through uow end of the transaction.

/// 
    /// 事物拦截器
    /// 
    public class TransactionalInterceptor : StandardInterceptor
    {
        private IUnitOfWork Uow { set; get; }

        public TransactionalInterceptor(IUnitOfWork uow)
        {
            Uow = uow;
        }

        protected override void PreProceed(IInvocation invocation)
        {
            Console.WriteLine("

{0} before interception

", invocation.Method.Name); var method = invocation.MethodInvocationTarget; if (method != null && method.GetCustomAttribute() != null) { Uow.BeginTransaction(); } } protected override void PerformProceed(IInvocation invocation) { invocation.Proceed(); } protected override void PostProceed(IInvocation invocation) { Console.WriteLine("

Intercepting {0}, {1} is the return value

", invocation.Method.Name, invocation.ReturnValue); var method = invocation.MethodInvocationTarget; if (method != null && method.GetCustomAttribute() != null) { Uow.Commit(); } } }

Static IServiceCollection extended by SummerBootExtentions.cs, and on a comparison, mainly added AddSbRepositoryService method, which is mainly obtained by the entity class denoted [TableAttribute] by reflection, and add the respective interface and the storage in the corresponding storage IServiceCollection implementation class, why not services.AddScoped (typeof (IRepository <>), typeof (BaseRepository <>)); this method is injected generics warehousing it? Because the net core native DI does not support the generic injection factory delegate creation, it can not be achieved dynamic proxy, so the use of alternative methods, the common generic interface, turn into specific generic interface, the other SummerBootExtentions.cs ProxyGenerator change is to register as a singleton, so you can use caching to improve performance create a dynamic proxy, SummerBootExtentions.cs code is as follows:

  public static class SummerBootExtentions
    {
        /// 
        /// 瞬时
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        public static IServiceCollection AddSbTransient(this IServiceCollection services, params Type[] interceptorTypes)
        {
            return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Transient, interceptorTypes);
        }

        /// 
        /// 瞬时
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        public static IServiceCollection AddSbTransient(this IServiceCollection services, Type serviceType,
            Type implementationType, params Type[] interceptorTypes)
        {
            return services.AddSbService(serviceType, implementationType, ServiceLifetime.Transient, interceptorTypes);
        }

        /// 
        /// 请求级别
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        public static IServiceCollection AddSbScoped(this IServiceCollection services, params Type[] interceptorTypes)
        {
            return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Scoped, interceptorTypes);
        }

        /// 
        /// 请求级别
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        public static IServiceCollection AddSbScoped(this IServiceCollection services, Type serviceType,
            Type implementationType, params Type[] interceptorTypes)
        {
            return services.AddSbService(serviceType, implementationType, ServiceLifetime.Scoped, interceptorTypes);
        }

        /// 
        /// 单例
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        public static IServiceCollection AddSbSingleton(this IServiceCollection services, params Type[] interceptorTypes)
        {
            return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Singleton, interceptorTypes);
        }

        /// 
        /// 单例
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        public static IServiceCollection AddSbSingleton(this IServiceCollection services, Type serviceType,
            Type implementationType, params Type[] interceptorTypes)
        {
            return services.AddSbService(serviceType, implementationType, ServiceLifetime.Singleton, interceptorTypes);
        }

        public static IServiceCollection AddSbService(this IServiceCollection services, Type serviceType, Type implementationType,
            ServiceLifetime lifetime, params Type[] interceptorTypes)
        {
            services.Add(new ServiceDescriptor(implementationType, implementationType, lifetime));

            object Factory(IServiceProvider provider)
            {
                var target = provider.GetService(implementationType);
                var properties = implementationType.GetTypeInfo().DeclaredProperties;

                foreach (PropertyInfo info in properties)
                {
                    //

Properties injection

if (info.GetCustomAttribute() != null) { var propertyType = info.PropertyType; var impl = provider.GetService(propertyType); if (impl != null) { info.SetValue(target, impl); } } //

Injection configuration values

if (info.GetCustomAttribute() is ValueAttribute valueAttribute) { var value = valueAttribute.Value; if (provider.GetService(typeof(IConfiguration)) is IConfiguration configService) { var pathValue = configService.GetSection(value).Value; if (pathValue != null) { var pathV = Convert.ChangeType(pathValue, info.PropertyType); info.SetValue(target, pathV); } } } } List interceptors = interceptorTypes.ToList() .ConvertAll(interceptorType => provider.GetService(interceptorType) as IInterceptor); var proxyGenerator = provider.GetService(); var proxy = proxyGenerator.CreateInterfaceProxyWithTarget(serviceType, target, interceptors.ToArray()); return proxy; }; var serviceDescriptor = new ServiceDescriptor(serviceType, Factory, lifetime); services.Add(serviceDescriptor); return services; } /// /// 瞬时 /// /// /// /// /// public static IServiceCollection AddSbTransient(this IServiceCollection services, params Type[] interceptorTypes) { return services.AddSbService(typeof(TService), ServiceLifetime.Transient, interceptorTypes); } /// /// 瞬时 /// /// /// /// /// public static IServiceCollection AddSbTransient(this IServiceCollection services, Type serviceType, params Type[] interceptorTypes) { return services.AddSbService(serviceType, ServiceLifetime.Transient, interceptorTypes); } /// /// 请求 /// /// /// /// /// public static IServiceCollection AddSbScoped(this IServiceCollection services, params Type[] interceptorTypes) { return services.AddSbService(typeof(TService), ServiceLifetime.Scoped, interceptorTypes); } /// /// 请求 /// /// /// /// /// public static IServiceCollection AddSbScoped(this IServiceCollection services, Type serviceType, params Type[] interceptorTypes) { return services.AddSbService(serviceType, ServiceLifetime.Scoped, interceptorTypes); } /// /// 单例 /// /// /// /// /// public static IServiceCollection AddSbSingleton(this IServiceCollection services, params Type[] interceptorTypes) { return services.AddSbService(typeof(TService), ServiceLifetime.Singleton, interceptorTypes); } /// /// 单例 /// /// /// /// /// public static IServiceCollection AddSbSingleton(this IServiceCollection services, Type serviceType, params Type[] interceptorTypes) { return services.AddSbService(serviceType, ServiceLifetime.Singleton, interceptorTypes); } public static IServiceCollection AddSbService(this IServiceCollection services, Type serviceType, ServiceLifetime lifetime, params Type[] interceptorTypes) { if (services == null) throw new ArgumentNullException(nameof(services)); if (serviceType == (Type)null) throw new ArgumentNullException(nameof(serviceType)); object Factory(IServiceProvider provider) { List interceptors = interceptorTypes.ToList() .ConvertAll(interceptorType => provider.GetService(interceptorType) as IInterceptor); var proxyGenerator = provider.GetService(); var proxy = proxyGenerator.CreateClassProxy(serviceType, interceptors.ToArray()); var properties = serviceType.GetTypeInfo().DeclaredProperties; foreach (PropertyInfo info in properties) { //

Properties injection

if (info.GetCustomAttribute() != null) { var propertyType = info.PropertyType; var impl = provider.GetService(propertyType); if (impl != null) { info.SetValue(proxy, impl); } } //

Injection configuration values

if (info.GetCustomAttribute() is ValueAttribute valueAttribute) { var value = valueAttribute.Value; if (provider.GetService(typeof(IConfiguration)) is IConfiguration configService) { var pathValue = configService.GetSection(value).Value; if (pathValue != null) { var pathV = Convert.ChangeType(pathValue, info.PropertyType); info.SetValue(proxy, pathV); } } } } return proxy; }; var serviceDescriptor = new ServiceDescriptor(serviceType, Factory, lifetime); services.Add(serviceDescriptor); return services; } /// /// 添加summer boot扩展 /// /// /// public static IMvcBuilder AddSB(this IMvcBuilder builder) { if (builder == null) throw new ArgumentNullException(nameof(builder)); ControllerFeature feature = new ControllerFeature(); builder.PartManager.PopulateFeature(feature); foreach (Type type in feature.Controllers.Select((Func)(c => c.AsType()))) builder.Services.TryAddTransient(type, type); builder.Services.Replace(ServiceDescriptor.Transient()); return builder; } public static IServiceCollection AddSbRepositoryService(this IServiceCollection services, params Type[] interceptorTypes) { var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(it => it.GetTypes()); var tableType = types.Where(it => it.GetCustomAttribute() != null); foreach (var type in tableType) { var injectServiceType = typeof(IRepository<>).MakeGenericType(type); var injectImplType = typeof(BaseRepository<>).MakeGenericType(type); services.AddSbScoped(injectServiceType, injectImplType, interceptorTypes); } return services; } }

View Code

Define a refueling implementation class AddOilService IAddOilService interfaces and interface service class, you can see from the code, we add through the property into a CashBalanceRepository and OilQuantityRepository, by [Transactional] marked AddOil method, making it a thing of the operation, mainly AddOil initialization and amount of oil, then add or subtract operation, last updated.

   public interface IAddOilService
    {
        void AddOil();
    }
public class AddOilService : IAddOilService
    {
        [Autowired]
        public IRepository CashBalanceRepository { set; get; }

        [Autowired]
        public IRepository OilQuantityRepository { set; get; }

        [Transactional]
        public void AddOil()
        {
            //

Initialization amount

var cashBalance = CashBalanceRepository.Insert(new CashBalance() { Balance = 100 }); //

Initialization oil

var oilQuantity = OilQuantityRepository.Insert(new OilQuantity() { Quantity = 5 }); cashBalance.Balance -= 95; oilQuantity.Quantity += 50; CashBalanceRepository.Update(cashBalance); //

throw new Exception ( "active error");

OilQuantityRepository.Update(oilQuantity); } }

A method of modifying Startup.cs ConfigureServices code is as follows:

public void ConfigureServices(IServiceCollection services)
        {
            services.Configure(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });


            services.AddMvc()
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
                .AddSB();

            services.AddSingleton();

            services.AddSbScoped(typeof(TransactionalInterceptor));

            services.AddSbScoped();
            services.AddScoped(typeof(TransactionalInterceptor));

            services.AddSbScoped(typeof(TransactionalInterceptor));

            services.AddSbScoped();
            services.AddSbRepositoryService(typeof(TransactionalInterceptor));
            services.AddSbScoped(typeof(TransactionalInterceptor));
        }

Controller HomeController

    public class HomeController : Controller
    {
        [Autowired]
        public ICar Car { set; get; }

        [Autowired]
        public IDistributedCache Cache { set; get; }

        [Value("description")]
        public string Description { set; get; }

        [Autowired]
        public IAddOilService AddOilService { set; get; }

        public IActionResult Index()
        {

            var car = Car;

            AddOilService.AddOil();

            Car.Fire();

            Console.WriteLine(Description);

            return View();
        }
    }

2. renderings

2 2.1 Clear table of data, at the end AddOil break point.

Although the front of the insert to perform the operation, but we query the two tables, and found that there was no new data, because things have not yet submitted, in line with expectations. Proceed from the breakpoint, and then query the database.

 

After executing things, the data is correct, in line with expectations.

2.2 clearing the data of table 2, commented AddOil method [the Transactional] annotations in AddOil end break point.

View the database, because the transaction did not open, so the data has been properly inserted into the table, and because oilQuantity storage is not updated, so the value is correct, proceed from the breakpoint

 

oilQuantity warehouse update, correct value, in line with expectations.

2 2.3 Clear the data table, the method of opening AddOil [the Transactional] annotation, and the active method throws an error.

Table does not add data, because things did not commit, rollback, and in line with expectations.

BTW, open things, except that [the Transactional] annotation, but also by injection UOW, manual opening and submission.

3. Written in the last

Only need to comment on the database entity class [Table ( “table”)] you can use the warehouse directly, it is not very simple and elegant it? Here achieve storage are common, if there are special needs of storage, you need to customize the interface and implementation class, interface inheritance IRepository , to achieve class inheritance BaseRepository , and then inject their own special storage on the line.

If this article help you, we might point a praise slightly.

Leave a Reply