编程知识 cdmana.com

Step by step introduction to the development framework based on sqlsugar (1) -- Design and use of basic classes of the framework

In actual project development , We may encounter various project environments , Some projects need a large and comprehensive overall framework to support development , Some small and medium-sized projects need some simple and convenient system framework for flexible development . At present, a larger framework , May adopt ABP perhaps ABP VNext Framework , The overall idea of the two is similar to the basic design , however ABP Focus on an independent and complete project framework , Unified integration during development ; and ABP VNext It is based on the micro service architecture , Each module is developed independently , It can be integrated into one project , It can also be published separately as a micro service , And communicate through gateway processing . No matter ABP perhaps ABP VNext frame , It's all assembled .NET CORE Many fields and technologies are integrated , And basic class design , Perplexing , More relationships , Therefore, there is a certain threshold for development learning , There are some difficulties in the application of small and medium-sized projects . This series of essays introduces the use of SqlSugar To do it ORM Data access module , Design a simple and convenient framework , This article starts from the basics and introduces some framework contents , Refer to some ABP/ABP VNext Some class libraries in deal with , To carry similar conditional paging information , Query condition processing and other processing details .

1、 be based on SqlSugar Architecture design of development framework

The main design module scenarios are as follows .

 

To avoid being like ABP VNext Dozens of projects scattered like a framework , We try our best to aggregate the content and put it in one project .

1) Some of the commonly used class libraries , as well as SqlSugar The base class of the framework is placed in the common module of the framework .

2)Winform Develop relevant basic interfaces and general component contents , Put on the foundation Winform Interface library BaseUIDx In the project .

3) Basic core data module SugarProjectCore, It is mainly the project of developing data processing and business logic required by the business , For convenience , We distinguish between Interface、Modal、Service Three directories to place different contents , among Modal yes SqlSugar Mapping entity of ,Interface Is to define the access interface ,Service Is to provide specific data operation implementation . among Service There are some framework base classes and interface definitions , Unification is also placed in the public class library .

4)Winform Application module , Mainly for business development WInform Interface application , and WInform Development for convenience , Some basic components and base classes will also be placed in the BaseUIDx Of Winform Inside the special interface library .

5)WebAPI The project is based on .net Core6 Project development , By calling SugarProjectCore Implement the relevant controller API Release , And integrate Swagger Publishing interface , For other front-end interface applications to call .

6) Pure front end through API To call Web API The interface of , Pure front-end modules can contain Vue3&Element project , And based on EelectronJS application , Publish a cross platform browser based application interface , And other things App Or applet integration Web API Processing or displaying business data requires .

Such as back-end development , We can do it in VS2022 Ongoing management , Manage development Winform project 、Web API Items, etc. .

Winform Interface , Based on .net Framework Develop or .net core6 For development , Because of our SugarProjectCore The project is to adopt .net Standard Pattern development , Compatible with both . Here, the permission module is used for demonstration and integration .

 

  And pure front-end projects , We can base it on VSCode perhaps  HBuilderX And other tools for project management and development .

 

2、 Definition and processing of framework basic classes

When developing an easy-to-use framework , The main purpose is to reduce code development , And as far as possible through base classes and generic constraints , Improve the universality of the interface , And through the combination of code generation tools , To improve the development efficiency of standard projects .

So here we are based on SqlSugar Of ORM Handle , To realize routine operations such as addition, deletion, modification and query of conventional data , How do we encapsulate these interfaces .

for example , For a simple customer information table , As shown below .

  Then it generates SqlSugar The entity class is as follows .

    /// <summary>
    ///  Customer information 
    ///  Inherited from Entity, Have Id Primary key properties 
    /// </summary>
    [SugarTable("T_Customer")]
    public class CustomerInfo : Entity<string>
    {
        /// <summary>
        ///  Default constructor ( You need to initialize the property here )
        /// </summary>
        public CustomerInfo()
        {
            this.CreateTime = System.DateTime.Now;
        }

        #region Property Members

        /// <summary>
        ///  full name 
        /// </summary>
        public virtual string Name { get; set; }

        /// <summary>
        ///  Age 
        /// </summary>
        public virtual int Age { get; set; }

        /// <summary>
        ///  founder 
        /// </summary>
        public virtual string Creator { get; set; }

        /// <summary>
        ///  Creation time 
        /// </summary>
        public virtual DateTime CreateTime { get; set; }

        #endregion
    }

among Entity<string> Is that we define a base class entity object as needed , The main thing is to define a Id To handle , After all, the processing of general table objects ,SqlSugar need Id The primary key definition of ( Non intermediate table processing ).

    [Serializable]
    public abstract class Entity<TPrimaryKey> : IEntity<TPrimaryKey>
    {
        /// <summary>
        ///  Unique primary key of entity class 
        /// </summary>
        [SqlSugar.SugarColumn(IsPrimaryKey = true, ColumnDescription = " Primary key ")]
        public virtual TPrimaryKey Id { get; set; }
    }

and IEntity<T> An interface is defined

    public interface IEntity<TPrimaryKey>
    {
        /// <summary>
        ///  Unique primary key of entity class 
        /// </summary>
        TPrimaryKey Id { get; set; }
    }

The above is the handling of entity classes , We usually search for information , It is often processed through some conditions , Then we need to define a general paging query object , For us to accurately handle the conditions .

Generate a to ***PageDto Object class of , As shown below .

    /// <summary>
    ///  Used for paging query according to conditions ,DTO object 
    /// </summary>
    public class CustomerPagedDto : PagedAndSortedInputDto, IPagedAndSortedResultRequest
    {
        /// <summary>
        ///  Default constructor 
        /// </summary>
        public CustomerPagedDto() : base() { }

        /// <summary>
        ///  Parameterized constructor 
        /// </summary>
        /// <param name="skipCount"> The number of jumps </param>
        /// <param name="resultCount"> Maximum number of result sets </param>
        public CustomerPagedDto(int skipCount, int resultCount) : base(skipCount, resultCount)
        {
        }

        /// <summary>
        ///  Initialize with paging information SkipCount  and  MaxResultCount
        /// </summary>
        /// <param name="pagerInfo"> Paging information </param>
        public CustomerPagedDto(PagerInfo pagerInfo) : base(pagerInfo)
        {
        }

        #region Property Members

        /// <summary>
        ///  Of objects that are not included ID, Used to exclude corresponding records during query 
        /// </summary>
        public virtual string ExcludeId { get; set; }

        /// <summary>
        ///  full name 
        /// </summary>
        public virtual string Name { get; set; }

        /// <summary>
        ///  Age - Start 
        /// </summary>
        public virtual int? AgeStart { get; set; }
        /// <summary>
        ///  Age - end 
        /// </summary>
        public virtual int? AgeEnd { get; set; }

        /// <summary>
        ///  Creation time - Start 
        /// </summary>
        public DateTime? CreateTimeStart { get; set; }
        /// <summary>
        ///  Creation time - end 
        /// </summary>
        public DateTime? CreateTimeEnd { get; set; }

        #endregion
    }

among PagedAndSortedInputDto, IPagedAndSortedResultRequest All references come from ABP/ABP VNext Treatment mode , In this way, we can facilitate the query processing operation of the data access base class .

Then we define a base class MyCrudService, And pass related generic constraints , As shown below

    /// <summary>
    ///  be based on SqlSugar Base class object for database access operations 
    /// </summary>
    /// <typeparam name="TEntity"> Define the mapped entity class </typeparam>
    /// <typeparam name="TKey"> The type of primary key , Such as int,string etc. </typeparam>
    /// <typeparam name="TGetListInput"> Or the condition object of paging information </typeparam>
    public abstract class MyCrudService<TEntity, TKey, TGetListInput> : 
        IMyCrudService<TEntity, TKey, TGetListInput>
        where TEntity : class, IEntity<TKey>, new()
        where TGetListInput : IPagedAndSortedResultRequest

Let's ignore the implementation details of the base class interface first , Let's see about this MyCrudService and  IMyCrudService How should we use it .

First, we define an application layer interface ICustomerService As shown below .

    /// <summary>
    ///  Customer information service interface 
    /// </summary>
    public interface ICustomerService : IMyCrudService<CustomerInfo, string, CustomerPagedDto>, ITransientDependency
    {

    }

Then implement in CustomerService The interface that implements it in .

    /// <summary>
    ///  Application layer service interface implementation 
    /// </summary>
    public class CustomerService : MyCrudService<CustomerInfo, string, CustomerPagedDto>, ICustomerService

In this way, we are specific to Customer The interface of is ICustomer In the definition of , The standard interface can directly call the base class .

Base class MyCrudService Provide two important interfaces , Let subclasses override , To facilitate accurate condition processing and sorting processing , As shown in the following code .

    /// <summary>
    ///  be based on SqlSugar Base class object for database access operations 
    /// </summary>
    /// <typeparam name="TEntity"> Define the mapped entity class </typeparam>
    /// <typeparam name="TKey"> The type of primary key , Such as int,string etc. </typeparam>
    /// <typeparam name="TGetListInput"> Or the condition object of paging information </typeparam>
    public abstract class MyCrudService<TEntity, TKey, TGetListInput> : 
        IMyCrudService<TEntity, TKey, TGetListInput>
        where TEntity : class, IEntity<TKey>, new()
        where TGetListInput : IPagedAndSortedResultRequest
    {
        /// <summary>
        ///  Leave it to subclasses to implement the processing of filtering conditions 
        /// </summary>
        /// <returns></returns>
        protected virtual ISugarQueryable<TEntity> CreateFilteredQueryAsync(TGetListInput input)
        {
            return EntityDb.AsQueryable();
        }
        /// <summary>
        ///  Default sort , adopt ID Sort 
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        protected virtual ISugarQueryable<TEntity> ApplyDefaultSorting(ISugarQueryable<TEntity> query)
        {
            if (typeof(TEntity).IsAssignableTo<IEntity<TKey>>())
            {
                return query.OrderBy(e => e.Id);
            }
            else
            {
                return query.OrderBy("Id");
            }
        }        
    }

about Customer For specific business objects , We need to implement specific query details and sorting conditions , After all, when our parent class has no constraint to determine what attributes the entity class has , It's best to leave these to subclasses .

    /// <summary>
    ///  Application layer service interface implementation 
    /// </summary>
    public class CustomerService : MyCrudService<CustomerInfo, string, CustomerPagedDto>, ICustomerService
    {
        /// <summary>
        ///  Custom condition handling 
        /// </summary>
        /// <param name="input"> Query criteria Dto</param>
        /// <returns></returns>
        protected override ISugarQueryable<CustomerInfo> CreateFilteredQueryAsync(CustomerPagedDto input)
        {
            var query = base.CreateFilteredQueryAsync(input);

            query = query
                .WhereIF(!input.ExcludeId.IsNullOrWhiteSpace(), t => t.Id != input.ExcludeId) // Excluding ID
                .WhereIF(!input.Name.IsNullOrWhiteSpace(), t => t.Name.Contains(input.Name)) // If exact matching is needed, use Equals
                                                                                             // Age range query 
                .WhereIF(input.AgeStart.HasValue, s => s.Age >= input.AgeStart.Value)
                .WhereIF(input.AgeEnd.HasValue, s => s.Age <= input.AgeEnd.Value)

                // Create date interval query 
                .WhereIF(input.CreateTimeStart.HasValue, s => s.CreateTime >= input.CreateTimeStart.Value)
                .WhereIF(input.CreateTimeEnd.HasValue, s => s.CreateTime <= input.CreateTimeEnd.Value)
                ;

            return query;
        }

        /// <summary>
        ///  Custom sort processing 
        /// </summary>
        /// <param name="query"> Can query LINQ</param>
        /// <returns></returns>
        protected override ISugarQueryable<CustomerInfo> ApplyDefaultSorting(ISugarQueryable<CustomerInfo> query)
        {
            return query.OrderBy(t => t.CreateTime, OrderByType.Desc);

            // First, sort by the first field , Then sort by the second field 
            //return base.ApplySorting(query, input).OrderBy(s=>s.Customer_ID).OrderBy(s => s.Seq);
        }
    }

adopt  CreateFilteredQueryAsync The exact condition processing , We can specify the query condition processing of the entity class , So for CustomerPagedDto Come on , That is, a client can pass in , The base class of the service backend is processed .

Such as the paging condition query function of the base class GetListAsync It's based on this , Its implementation code is as follows .

        /// <summary>
        ///  Get the list according to the condition 
        /// </summary>
        /// <param name="input"> Paging query criteria </param>
        /// <returns></returns>
        public virtual async Task<PagedResultDto<TEntity>> GetListAsync(TGetListInput input)
        {
            var query = CreateFilteredQueryAsync(input);
            var totalCount = await query.CountAsync();

            query = ApplySorting(query, input);
            query = ApplyPaging(query, input);

            var list = await query.ToListAsync();

            return new PagedResultDto<TEntity>(
               totalCount,
               list
           );
        }

And one of  ApplySorting It determines whether to select the default sorting implemented by the subclass according to the conditions .

        /// <summary>
        ///  Record sorting processing 
        /// </summary>
        /// <returns></returns>
        protected virtual ISugarQueryable<TEntity> ApplySorting(ISugarQueryable<TEntity> query, TGetListInput input)
        {
            //Try to sort query if available
            if (input is ISortedResultRequest sortInput)
            {
                if (!sortInput.Sorting.IsNullOrWhiteSpace())
                {
                    return query.OrderBy(sortInput.Sorting);
                }
            }

            //IQueryable.Task requires sorting, so we should sort if Take will be used.
            if (input is ILimitedResultRequest)
            {
                return ApplyDefaultSorting(query);
            }

            //No sorting
            return query;
        }

For getting a single object , We usually provide one ID Just get the primary key .

        /// <summary>
        ///  according to ID Get a single object 
        /// </summary>
        /// <param name="id"> Primary key ID</param>
        /// <returns></returns>
        public virtual async Task<TEntity> GetAsync(TKey id)
        {
            return await EntityDb.GetByIdAsync(id);
        }

It can also be based on the user's Express Deal with conditions , In the base class, we define many such Express Conditional processing , Calls that facilitate subclass conditional processing . For example, delete , You can specify ID, You can also specify conditions to delete .

        /// <summary>
        ///  Delete the specified ID The object of 
        /// </summary>
        /// <param name="id"> Record ID</param>
        /// <returns></returns>
        public virtual async Task<bool> DeleteAsync(TKey id)
        {
            return await EntityDb.DeleteByIdAsync(id);
        }
/// <summary>
        ///  According to specified conditions , Delete the collection 
        /// </summary>
        /// <param name="input"> Expression conditions </param>
        /// <returns></returns>
        public virtual async Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> input)
        {
            var result = await EntityDb.DeleteAsync(input);
            return result;
        }

If it is judged whether it exists, it is also handled in the same way

        /// <summary>
        ///  Determine whether there are records with specified conditions 
        /// </summary>
        /// <param name="id">ID  Primary key </param>
        /// <returns></returns>
        public virtual async Task<bool> IsExistAsync(TKey id)
        {
            var info = await EntityDb.GetByIdAsync(id);
            var result = (info != null);
            return result;
        }

        /// <summary>
        ///  Determine whether there are records with specified conditions 
        /// </summary>
        /// <param name="input"> Expression conditions </param>
        /// <returns></returns>
        public virtual async Task<bool> IsExistAsync(Expression<Func<TEntity, bool>> input)
        {
            var result = await EntityDb.IsAnyAsync(input);
            return result;
        }

About Web API To deal with , I'm writing essays 《 be based on SqlSugar Encapsulation of database access processing , stay .net6 Framework of the Web API Develop applications on 》 There is also an introduction to , The main thing is to do it first .net6 Development environment of , Then we can carry out relevant project development .

According to the needs of the project , We define some base classes of controllers , For different functions .

 

  among ControllerBase yes .net core Web API Standard controller base class in , We derive a LoginController Used for login authorization , and BaseApiController Then process the user identity information of the general interface , and BusinessController It is the encapsulation of basic interfaces such as standard addition, deletion, modification and query , When we actually develop , Just develop and write something like CustomerController Base class .

BaseApiController There is nothing to introduce , Is to encapsulate and obtain the user's identity information .

You can get the interface user's information through the following code Id

        /// <summary>
        ///  Current user identity ID
        /// </summary>
        protected virtual string? CurrentUserId => HttpContext.User.FindFirst(JwtClaimTypes.Id)?.Value;

and BusinessController The controller inherits this BaseApiController that will do . Pass in relevant object information through generic constraints .

    /// <summary>
    ///  This controller base class is specially designed for accessing data business objects 
    /// </summary>
    /// <typeparam name="TEntity"> Define the mapped entity class </typeparam>
    /// <typeparam name="TKey"> The type of primary key , Such as int,string etc. </typeparam>
    /// <typeparam name="TGetListInput"> Or the condition object of paging information </typeparam>
    [Route("[controller]")]
    [Authorize] // You need to authorize login access 
    public class BusinessController<TEntity, TKey, TGetListInput> : BaseApiController
        where TEntity : class, IEntity<TKey>, new()
        where TGetListInput : IPagedAndSortedResultRequest
    {
        /// <summary>
        ///  General basic operation interface 
        /// </summary>
        protected IMyCrudService<TEntity, TKey, TGetListInput> _service { get; set; }

        /// <summary>
        ///  Constructors , Initialize the basic interface 
        /// </summary>
        /// <param name="service"> General basic operation interface </param>
        public BusinessController(IMyCrudService<TEntity, TKey, TGetListInput> service)
        {
            this._service = service;
        }

....

This base class receives an object that conforms to the definition of the base class interface as the interface object of processing methods such as addition, deletion, modification and query of the base class . In concrete CustomerController The definition processing in is as follows .

    /// <summary>
    ///  Controller object of customer information 
    /// </summary>
    public class CustomerController : BusinessController<CustomerInfo, string, CustomerPagedDto>
    {
        private ICustomerService _customerService;

        /// <summary>
        ///  Constructors , And inject the basic interface object 
        /// </summary>
        /// <param name="customerService"></param>
        public CustomerController(ICustomerService customerService) :base(customerService)
        {
            this._customerService = customerService;
        }
    }

In this way, the basic related operations can be realized . If you need special interface implementation , Then define the method implementation .

The controller processing code in a similar dictionary project is as follows . Well defined HTTP Method , Routing information, etc .

        /// <summary>
        ///  According to the dictionary type ID Get all dictionary list sets of this type (Key As the name ,Value Value )
        /// </summary>
        /// <param name="dictTypeId"> Dictionary type ID</param>
        /// <returns></returns>
        [HttpGet]
        [Route("by-typeid/{dictTypeId}")]
        public async Task<Dictionary<string, string>> GetDictByTypeID(string dictTypeId)
        {
            return await _dictDataService.GetDictByTypeID(dictTypeId);
        }

        /// <summary>
        ///  Get all the dictionary list sets of this type according to the dictionary type name (Key As the name ,Value Value )
        /// </summary>
        /// <param name="dictTypeName"> Dictionary type name </param>
        /// <returns></returns>
        [HttpGet]
        [Route("by-typename/{dictTypeName}")]
        public async Task<Dictionary<string, string>> GetDictByDictType(string dictTypeName)
        {
            return await _dictDataService.GetDictByDictType(dictTypeName);
        }

 

版权声明
本文为[Wu Huacong]所创,转载请带上原文链接,感谢
https://cdmana.com/2022/134/202205141317533256.html

Scroll to Top