DDD CRUD database operation log unit test

Write a framework of domain model from scratch

Each article will be marked with a corresponding tag

Github Warehouse address

 

Complete a business module (single table addition, deletion, modification and query, application layer, domain layer, storage layer and work unit)

Create a unit test (use the unit test to debug the code before you start writing the front-end code)

Record the log of CRUD operations of EF operation database (write the log to the text file temporarily, and then transfer it to Redis or message queue to be saved to mongoDB)

 

domain model

Application layer and domain layer

Whether a business code is placed in the business layer or the domain layer depends on the specific situation of each step of the business.

Obtain user data according to query criteria

The query parameters are directly built in the application layer, and the domain objects of users can be obtained through the storage layer

Get a user's Department, mobile number, friend, customer and leader

Get the user's information through the storage layer and return a UserDomain

The attributes of name, age and mobile phone are the basic information of the user, which can be obtained directly through the Get method of the domain model

Personal department, friends and customers are also user attributes. Call the method in UserDomain to obtain these attributes

    UserDomain.GetDept() returns DempDomain

    UserDomain.GetFriends() returns list < frienddomain >

To obtain the data with specified conditions, you need to use the storage layer to obtain it

    UserRepository. Find (parameter, specified friend) returns list < frienddomain >

Other complex operations

Send SMS and email to the user

Call interface

Domain object to Dto

As long as it feels that it cannot be divided into a domain object and is not a data operation, it will be put into the business layer

 

Comparison between obtaining data through domain object and executing SQL

SQL obtains the data of employees and their departments, and one SQL is finished

The domain model should first obtain the employee information, then convert the UserEntity into a UserDomain, and then obtain the DeptDomain through the UserDomain

 

SQL can be written at will

The domain model can only obtain one or more pieces of data according to the Id each time

 

After the three-tier SQL Select, only the specified fields need to be returned

The domain model executes SQL Select, followed by all the fields of the table

 

Advantages and disadvantages of domain model

Low development and maintenance cost

The operation efficiency is relatively low

give an example:

A new business has been added within the company, and every employee can participate. After the employee completes the performance, he / she sends a message to the leader of the direct Department...... And a series of operations

Domain model

Application layer write a new method

Call warehouse to get a UserDomain

Get the UserDomain of the direct leader through the UserDomain

The action of calling UserDomain to send message

Get department information through UserDomain DeptDomain

Department performance improvement

After the execution is completed, the whole transaction is committed

 

When writing the whole logic, it is written in the dimension of business. An application layer method is a business. If the business involves those fields, it will call the methods in those fields

Whether old employees or new employees, as long as everyone writes according to this specification, it will not be too bad, and the whole project will not be too chaotic after several years of operation, and there will not be too much technical debt.

 

 

unit testing

Create a unit test project

Create a base class to initialize the project

TestClassBase.cs  Unit test base class, initialization, configuration information, initialization IoC Container, initialize project global Global class

using Autofac;
using Core2022.Framework;
using Core2022.Framework.Commons.Autofac;
using Microsoft.Extensions.Configuration;

namespace Core2022.Application.Services.Test
{
    public class TestClassBase
    {
        ContainerBuilder builder = new ContainerBuilder();
        IContainer rootContainer;

        public TestClassBase()
        {
            // initialization appsettings
            IConfiguration configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .Build();
            Global.InitAppSettings(configuration);

            // initialization Autofac container
            // Autofac injection Orm object
            builder.AutofacInjectionOrmModel();
            // Autofac Inject dependencies between layers
            builder.AutofacInjectionServices();

            rootContainer = builder.Build();

            Global.AppAutofacContainer(rootContainer);
        }
    }
}

 

UserAppServiceTest.cs complete CreateUser Unit test of method

using Core2022.Application.Services.DTO.User;
using Core2022.Application.Services.Interface;
using Core2022.Framework;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Core2022.Application.Services.Test
{
    [TestClass]
    public class UserAppServiceTest : TestClassBase
    {

        IUserAppService userAppService;
        public UserAppServiceTest()
        {
            userAppService = Global.GetT<IUserAppService>();
        }

        [TestMethod]
        public void CreateUser()
        {
            var respDto = userAppService.CreateUser(new UserRequestDto()
            {
                UserName = "222333",
                PassWord = "111111"
            });
        }
    }
}

 

EF operation log

using Core2022.Framework.Entity;
using Core2022.Framework.Settings;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;

namespace Core2022.Framework.UnitOfWork
{
    public class AppUnitOfWork : DbContext, IUnitOfWork
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {

            optionsBuilder.UseSqlServer(Global.ConnectionString);
            base.OnConfiguring(optionsBuilder);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            Global.OrmModelInit.ForEach(t =>
            {
                modelBuilder.Model.AddEntityType(t);
            });
            base.OnModelCreating(modelBuilder);
        }


        public DbSet<OrmEntity> CreateSet<OrmEntity>()
          where OrmEntity : class
        {
            return base.Set<OrmEntity>();
        }


        public new int SaveChanges()
        {
            EFLog();
            return base.SaveChanges();
        }

        #region EF Log
        private void EFLog()
        {
            IEnumerable<EntityEntry> list = this.ChangeTracker.Entries();
            foreach (var item in list)
            {
                //Corresponding table name
                string tableName = "";

                #region Get table name
                Type type = item.Entity.GetType();
                Type patientMngAttrType = typeof(TableAttribute);
                TableAttribute attribute = null;
                if (type.IsDefined(patientMngAttrType, true))
                {
                    attribute = type.GetCustomAttributes(patientMngAttrType, true).FirstOrDefault() as TableAttribute;
                    if (attribute != null)
                    {
                        tableName = attribute.Name;
                    }
                }

                if (string.IsNullOrEmpty(tableName))
                {
                    tableName = type.Name;
                }
                #endregion

                BaseOrmModel model = item.Entity as BaseOrmModel;
                #region EntityState
                switch (item.State)
                {
                    //case EntityState.Detached:
                    //case EntityState.Unchanged:
                    //case EntityState.Deleted:
                    case EntityState.Modified:
                        model.UpdateTime = DateTime.Now;
                        model.Version = ++model.Version;
                        WriteEFUpdateLog(item, tableName);
                        break;
                    case EntityState.Added:
                        model.UpdateTime = DateTime.Now;
                        model.CreateTime = DateTime.Now;
                        model.IsDelete = false;
                        model.Version = 0;
                        WriteEFCreateLog(item, tableName);
                        break;
                }
                #endregion

            }
        }
        private void WriteEFCreateLog(EntityEntry entry, string tableName)
        {
            var propertyList = entry.CurrentValues.Properties;
            string userName = "System user";
            PropertyEntry keyEntity = entry.Property("KeyId");
            Dictionary<string, object> dic = new Dictionary<string, object>();
            foreach (var prop in propertyList)
            {
                PropertyEntry entity = entry.Property(prop.Name);
                dic.Add(prop.Name, entity.CurrentValue);
            }
            DBLogCreateModel createLog = new DBLogCreateModel()
            {
                CreateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                TableName = tableName,
                OperatorUserName = userName,
                PrimaryKeyId = Guid.Parse(keyEntity.CurrentValue.ToString()),
                CreateValue = JsonConvert.SerializeObject(dic)
            };
            WriteLog(createLog);
        }

        /// <summary>
        /// record EF Modify operation log
        /// </summary>
        /// <param name="entry"></param>
        /// <param name="tableName"></param>
        private void WriteEFUpdateLog(EntityEntry entry, string tableName)
        {
            var propertyList = entry.CurrentValues.Properties.Where(i => entry.Property(i.Name).IsModified);
            string userName = "System user";

            PropertyEntry keyEntity = entry.Property("KeyId");
            foreach (var prop in propertyList)
            {
                PropertyEntry entity = entry.Property(prop.Name);

                DBLogUpdateModel updateLog = new DBLogUpdateModel()
                {
                    CreateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                    CulumnName = prop.Name,
                    TableName = tableName,
                    OperatorUserName = userName,
                    CurrentValue = entity.CurrentValue,
                    OriginalValue = entity.OriginalValue,
                    PrimaryKeyId = Guid.Parse(keyEntity.CurrentValue.ToString()),

                };

                if (entity.OriginalValue == null || entity.CurrentValue == null)
                {
                    if (entity.OriginalValue != entity.CurrentValue)
                    {
                        WriteLog(updateLog);
                    }
                    continue;
                }
                if (!entity.OriginalValue.Equals(entity.CurrentValue))
                {
                    WriteLog(updateLog);
                }
            }
        }

        private void WriteLog(DBLogBaseModel updateLog)
        {
            updateLog.OperatorKeyId = "";
            DBLog.WriteLog(updateLog);
        }
        #endregion

    }
}

Add and record according to each line of data (operator)

Modify, delete and record in units of each field (original value, modified value, modified time, modified person and other information)

Query, query criteria, time consumption and other information (completed in the next version)

 

The operator information is written dead due to the lack of global verification

 

Keywords: .NET

Added by choppsta on Sat, 05 Feb 2022 14:23:23 +0200