. NET5 ORM framework bottom principle actual combat Find < T >

. 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

Write another Insert next time

Keywords: .NET microsoft

Added by shinichi_nguyen on Fri, 05 Nov 2021 04:46:42 +0200