. NET5 ORM framework underlying principle practice (1)
Technology used?
1. Generics 2. Reflection 3. Properties
What is ORM?
Object Relational Mapping, the most important of which is this Mapping. Generally speaking, the set of directly operating the database is directly moved to use the object-oriented idea. We indirectly achieve the purpose of operating the database by directly operating the object. The bottom layer is ado.net
ORM bottom combat (crud)
Find(int id) (query) (basic version)
You can define the query parameters according to which fields
public T Find<T>(int id) where T : BaseEntity { //General purpose is achieved through generics + reflection Type type = typeof(T);//f reflection class //Splice each field select field from table name string column = string.Join(",", type.GetProperties().Select(prop => prop.Name)); //Splice base string string sqlString = $"select {column} from {type.Name} where id={id}"; //Connect to database using (SqlConnection conn = new SqlConnection("Data Source=PC04E0010016\\MSSQLSERVER01;Initial Catalog=ZHIBO; User Id=niubi;Password=123456")) { SqlCommand command = new SqlCommand(sqlString,conn); conn.Open();//Open database var reader=command.ExecuteReader(); if (reader.Read()) { T t = (T) Activator.CreateInstance(type);//Create objects with reflections //Reflection object get field foreach (var prop in type.GetProperties()) { prop.SetValue(t, reader[prop.Name]); } return t; } else { return default(T); } }
public interface BaseEntity { }
public class Program { public static void Main(string[] args) { DateHelper helper = new DateHelper(); var entity=helper.Find<ClientUser>(1); } }
If you run directly in this way, or report an error, the reason is that the queried field is null, but it is DbNull mapped to the class, so you need to add DbNull filtering here
prop.SetValue(t, reader[prop.Name] is DBNull? null : reader[prop.Name]);
Find(int id) (query) (upgraded version)
Add functions on the basis that the above codes remain unchanged, because in the actual development, we will encounter the problem that the class name is inconsistent with the database, and the attribute names of fields and classes are inconsistent. In many orm frameworks, a feature is often added to solve this problem. We will also implement this.
Here we need to use features
Create a new attribute tablename
[AttributeUsage(AttributeTargets.Class)]//Act on class public class TableNameAttribute:Attribute { private string name { get; set; } public TableName(string name) { this.name = name; } /// <summary> ///Provide a method to get the name when reflecting the feature /// </summary> /// <returns></returns> public string GetName() { return this.name; } }
In the basic version, we just need to judge whether there is a tag feature when we get the class name and field name. In some cases, it's good to directly get the name in the feature
Let's do it
Add an extension method to type to get the name in the feature
public static class ORMExtension { public static string GetTableName(this Type type) { if (type.IsDefined(typeof(TableNameAttribute), true))//Determine if there is this feature { //Get this feature var attribute= type.GetCustomAttribute<TableNameAttribute>(); return attribute.GetName(); } else {//Returns the class name directly without a tag return type.Name; } } }
In the top version, just change one sentence
Change type.Name to type.GetTableName()
//Splice base string string sqlString = $"select {column} from {type.GetTableName()} where id={id}";
The same is true for inconsistent attribute names
From the above two versions, we can see why some people say that the performance of orm framework is not as good as that of native ado.net. In fact, we can see from the code that there are a lot of reflections, and once there is a problem with the features, it will keep reflecting, so it leads to the lack of performance, so find a way to solve it
From the above, we can see that reflection is actually the performance consumption, and it is all repetitive work. Can we only do the repetitive work once? Here we use the generic cache.
You can create a generic type of a static class to implement it
This is where performance is consumed. Cache it
//General purpose is achieved through generics + reflection Type type = typeof(T);//f reflection class //Splice each field select field from table name string column = string.Join(",", type.GetProperties().Select(prop => prop.Name)); //Splice base string string sqlString = $"select {column} from {type.GetTableName()} where id={id}";
Specific implementation method
public class SqlBuilder<T> where T : BaseEntity { private static string _finSql = null; static SqlBuilder() { Type type = typeof(T); { string columnsString = string.Empty; _finSql = $"select {columnsString} from [{type.GetTableName()}] where id= "; } } /// <summary> ///Ending with Id =, you can add parameters directly /// </summary> /// <returns></returns> public static string GetFindSql() { return _finSql; } }
Put the implementation in the static constructor. It will be implemented automatically every time it returns, and it will not be executed again next time, including when T changes
Subsequent operations such as transactions will be added