OSS. Core is based on the concept and implementation of the Dapper encapsulation (expression parsing + Emit) warehousing layer, and oss. coredapper
Recently, I am not busy thinking about building an open-source project. I will explain the reasons and the entire project framework later. Since a complete project is required, data warehouse access is essential. This article mainly introduces this new project (OSS. in Core), my simple thinking and implementation process on the storage layer (the current project is still in the construction phase) are mainly focused on the following aspects:
1. Requirements of the Data Warehouse Layer
2. Select an ORM framework
3. OSS. Core storage layer design and implementation
4. Call example
The implementation section below may require you to have a basic understanding of. NET generics, delegation, extensions, expressions, and so on. It is precisely because of these language features that we can easily unify the extraction of common operations.
I. Data Warehousing layer requirements
Since it is a complete project, data access is the most basic part. At the same time, data access is also the most prone to bottlenecks in the entire project. In my division, my role is responsible for the input and output of the entire data, not only for a single database (sometimes even multiple databases), but also for the implementation of level-1 cache, it provides the most basic data support for the logic layer.
The business is always changing, so projects must also be able to quickly evolve. Therefore, I hope that the data layer can be kept relatively simple and minimize complex coupling queries in terms of structure, minimize unnecessary consumption in terms of performance, such as the large amount of reflection. At the same time, the basic CRUD unified encapsulation implementation at the database level is completed for each business object. If necessary, you can add cache updates with minimal changes. (For how to implement different cache storage policies for different modules, such as Redis and Memcached, we will introduce it later)
At the same time, for a slightly larger project, the fastest way to solve database access is to achieve read/write splitting. Therefore, I hope this framework can support read/write splitting at the underlying level from the very beginning, so as to avoid a lot of modifications to the Business Code in the future.
Ii. ORM framework Selection
Of course, for simplicity and performance, ADO. NET connections are more efficient in theory, but this will cause a large number of repeated logical code operations, as well as code disorder and increase maintenance complexity. As a technician, we not only need to solve business problems and improve efficiency, but also improve our own efficiency. So I will select an ORM framework to complete some basic work.
Currently in. under the. NET system, there are many open-source ORM frameworks, such as Entityframework, nhib.pdf, and iBATIS. NET, Dapper, and so on. Based on what I have mentioned above, both simplicity and performance consumption can be minimized while ensuring efficiency.. net standard. After comparison, I chose Dapper, a semi-automated ORM, as the basic framework of the storage layer. The reasons are as follows:
1. Its structure is simple, and the entire package is mainly concentrated in Dapper. cs files, which are small in size.
2. Simple and powerful encapsulation and flexible support for native SQL
This is almost better than other frameworks, without any additional settings, and basically you can call all native ADO. NET function, SQL statements are completely controlled by themselves, but there is no need to care about the value assignment of command parameters, as well as result entity conversion.
3. Performance Efficiency
Many ORM ing objects are completed through Reflection. In this regard, Dapper once again shows its charm and uses Reflection in key modules such as Commond parameter assignment and entity conversion. the Emit function indirectly implements value assignment at the MSIL compilation level. The reason for this is that its own code also requires the compiler to generate the IL code. You can dynamically create a value assignment delegate Method Based on the Type attribute during running.
Iii. OSS. Core storage layer design and implementation
Dapper can be used to implement simple encapsulation at the database access layer. However, I still need to manually write a lot of SQL statements and perform parameterized processing, including data read/write splitting. The implementation of these functions will be completed in OSS. Core. RepDapper. To facilitate understanding, first paste a simple encapsulated method call transmission process:
In this example, a simple method call process is displayed. I will introduce the following parts of the figure:
1. Interface Design
Because I want this to be a complete example project, I want to be compatible with different databases later. Therefore, external warehouse access is based on interface calls. Of course, if your project does not require database switching at all, I suggest removing this step and implementing the singleton mode directly in the base class. The business logic layer calls it directly.
The figure shows that the interface layer is independent of the implementation part. I put the specific business entity model and interface separately in OSS. core. in the DomainMos class library, one is to share the entity model in each module, and the other is to decouple the dependency between the business logic layer (Services) and the warehouse layer (Reps.
At the same time, most of the database access code in a project will be dominated by CRUD, so here I have defined a basic interface (IBaseRep). The methods contained mainly include (the expressions are described later ):
The specific business data interface inherits from the basic interface, and the expression is encapsulated by myself, which will be briefly introduced later.
2. BaseRep)
First, we implement two extensions of read/write splitting. In fact, they will eventually go through the Excute method. Here we will show the specific implementation of the method below:
We can see that this method provides a delegate for IDbConnection, and provides the call layer to freely use the Dapper method. At the same time, it unifies the data access method entry, facilitating log recording and troubleshooting.
Second, users and orders in different databases may occur in many projects. Because database sharding is involved, sub-classes must have the ability to modify connection strings, here I provide two void parameters in the form of constructor:
As you can see, if the subclass defines its own connection string, the subclass is defined as the primary connection string. Otherwise, the default connection information is used.
Finally, we have implemented the specific implementation of basic interface methods. For example:
In addition, to ensure that the sub-classes can be added with cache processing, the virtual method (virtual) is used to ensure that the sub-classes can be overwritten.
3. Connection-based extension
This part is mainly divided into two parts, a. Expression parsing, and parameterized processing B. Extended Connection Insert, Update... and Other Dapper non-extended methods:
A. a friend familiar with Expression expressions should be familiar with it. The Expression itself is a tree interface, and its subexpressions can be constantly parsed Based on Different types until it is not possible to continue parsing. Therefore, it is easy to implement recursive iteration. Different SQL elements can be assembled based on different NodeType. Because the code is long, you can refer to SqlExpressionVisitor under github. cs class, in which the parameter value is not reflected, but reflection emission is used. For details about the code, see SqlParameterEmit. cs
B. With the extension of the expression, you can obtain the corresponding SQL and parameters, and use this to extend the Connection method. For the code, see ConnoctionExtention. cs.
Iv. Call example
1. We define a simple UserInfoMo entity (including mobile and other attributes)
2. Define the interface IUserInfoRep: IBaseRep
3. Define implementation class UserInfoRep: BaseRep, IUserInfoRep
We can complete the following call without adding other code:
The current project is still under construction. If you are interested, you are welcome to participate. For more information about the code, see OSS. Core at Github.
==================================
If you have other questions, follow the Public Account (OSSCoder)