[Asp.net core series] 8 actual combat using EF Core to complete the realization of the data operation layer

0. Preface

Through the first two articles, we created a project and specified a basic data layer access interface. In this article, we will take EF Core as an example to demonstrate how the data layer access interface is implemented, and the points that need to be paid attention to in the implementation.

image

1. Add EF Core

First introduce EF Core in the data layer implementation layer:

cd Domain.Implements
dotnet add package Microsoft.EntityFrameworkCore

The current project takes SqlLite as an example, so add another SqlLite database driver:

dotnet add package Microsoft.EntityFrameworkCore.SQLite

Delete the default Class1.cs file in Domain.Implements, then add the Infrastructure directory, and create a DefaultContext:

using Microsoft.EntityFrameworkCore;

namespace Domain.Implements.Insfrastructure
{
   public class DefaultContext : DbContext
   {
       private string ConnectStr { get; }
       public DefaultContext(string connectStr)
       {
           ConnectStr = connectStr;
       }

       protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
       {
           optionsBuilder.UseSqlite(ConnectStr);//如果需要别的数据库,在这里进行修改
       }
   }
}

2. EF Core batch load model

Under normal circumstances, when using ORM, we do not want to overuse features to annotate entity classes. Because if you need to change the ORM or other changes later, using features to annotate entity classes will cause the migration to become complicated. And most of the features of ORM frameworks are dependent on the framework itself, not a unified feature structure, this will cause a consequence: the implementation that should be hidden from the caller will be made public, and the project reference relationship is prone to loops. Reference.

Therefore, in the development, I will look for whether to support the configuration class. If the configuration class is used or the mapping relationship is set in the ORM framework, then the purity of the data layer can be guaranteed, and the implementation can be hidden from the caller.

The configuration class of EF Core was introduced in the article about EF in the "C# Data Access Series", so I won't introduce too much here (the friends who haven't had time to read it, don't worry, there will be a simple introduction in the follow-up) .

Under normal circumstances, I also put the configuration class in the Domain.Implements project. Now let me introduce to you how to quickly load configuration classes in batches:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetAssembly(this.GetType()),
       t => t.GetInterfaces().Any(i => t.Name.Contains("IEntityTypeConfiguration")));
}

The current version of EF Core supports loading configuration classes through Assembly. You can specify the Assembly where the current context class is loaded, and then filter the classes that include IEntityTypeConfiguration in the implementation interface.

3. Use EF Core to implement data manipulation

We have created an EF Context, so now let us take a look at how to use EF to implement the data access interface introduced in the previous article "Asp.net core" 7 actual combat data access layer definition:

Create a new BaseRepository class, under the Infrastructure directory of the Domain.Implements project:

using Domain.Infrastructure;
using Microsoft.EntityFrameworkCore;

namespace Domain.Implements.Insfrastructure
{
   public abstract class BaseRepository<T> : ISearchRepository<T>, IModifyRepository<T> where T : class
   {
       public DbContext Context { get; }
       protected BaseRepository(DbContext context)
       {
           Context = context;
       }
   }
}

Create the above content first. When passing parameters to the Repository, we use the default Context class of EFCore, which is not defined by ourselves. This is my personal habit, and it has no other influence in fact. The main purpose is to hide the specific EF context implementation class from the implementation class.

Before implementing each interface method, create the following attributes:

public DbSet<T> Set { get => Context.Set<T>(); }

This is the core of EF operating data.

3.1 Implement the IModifyRepository interface

First realize the modification interface:

public T Insert(T entity)
{
   return Set.Add(entity).Entity;
}

public void Insert(params T[] entities)
{
   Set.AddRange(entities);
}

public void Insert(IEnumerable<T> entities)
{
   Set.AddRange(entities);
}
public void Update(T entity)
{
   Set.Update(entity);
}

public void Update(params T[] entities)
{
   Set.UpdateRange(entities);
}

public void Delete(T entity)
{
   Set.Remove(entity);
}

public void Delete(params T[] entities)
{
   Set.RemoveRange(entities);
}

In the modification interface, I reserved a few methods that have not been implemented, because these methods can be implemented using EF Core itself, but the implementation will be more troublesome, so here is an EF Core plugin:

dotnet add package Z.EntityFramework.Plus.EFCore

This is a free and open source plug-in that can be used directly. After adding in Domain.Implements, add the following references in BaseRepository:

using System.Linq;
using System.Linq.Expressions;

Implementation:

public void Update(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> updator)
{
   Set.Where(predicate).UpdateFromQuery(updator);
}

public void Delete(Expression<Func<T, bool>> predicate)
{
   Set.Where(predicate).DeleteFromQuery();
}

public void DeleteByKey(object key)
{
   Delete(Set.Find(key));
}

public void DeleteByKeys(params object[] keys)
{
   foreach (var k in keys)
   {
       DeleteByKey(k);
   }
}

There is a problem with the method of deleting the primary key here. We can’t delete it according to the conditions. In fact, if we agree that the generic T is a subclass of BaseEntity, we can get the primary key, but this will introduce another generic, in order to avoid introducing too many A generic type uses this method based on the deletion of the primary key.

3.2 Implement the ISearchRepository interface

Access to data and basic statistics interface:

public T Get(object key)
{
   return Set.Find(key);
}

public T Get(Expression<Func<T, bool>> predicate)
{
   return Set.SingleOrDefault(predicate);
}

public int Count()
{
   return Set.Count();
}

public long LongCount()
{
   return Set.LongCount();
}

public int Count(Expression<Func<T, bool>> predicate)
{
   return Set.Count(predicate);
}

public long LongCount(Expression<Func<T, bool>> predicate)
{
   return Set.LongCount(predicate);
}

public bool IsExists(Expression<Func<T, bool>> predicate)
{
   return Set.Any(predicate);
}

Here is a point to pay attention to. When querying single data using conditions, I used SingleOrDefault instead of FirstOrDefault. This is because I have stipulated here that if a conditional query is used, the caller should be able to expect that the condition used can query at most one piece of data. However, the method can be modified according to actual business needs:

  • Single returns a single data, if the data is greater than 1 or equal to 0, an exception is thrown

  • SingleOrDefault returns a single data, if the result set has no data, it returns null, if it is more than 1, an exception is thrown

  • First returns the first element of the result set, if there is no data in the result set, an exception is thrown

  • FirstOrDefault returns the first element of the result set, or null if there is no element

    Implement query method:

public List<T> Search()
{
   return Query().ToList();
}

public List<T> Search(Expression<Func<T, bool>> predicate)
{
   return Query(predicate).ToList();
}

public IEnumerable<T> Query()
{
   return Set;
}

public IEnumerable<T> Query(Expression<Func<T, bool>> predicate)
{
   return Set.Where(predicate);
}

public List<T> Search<P>(Expression<Func<T, bool>> predicate, Expression<Func<T, P>> order)
{
   return Search(predicate, order, false);
}

public List<T> Search<P>(Expression<Func<T, bool>> predicate, Expression<Func<T, P>> order, bool isDesc)
{
   var source = Set.Where(predicate);
   if (isDesc)
   {
       source = source.OrderByDescending(order);
   }
   else
   {
       source = source.OrderBy(order);
   }
   return source.ToList();
}

Here I try to implement the query function by calling the method with the most parameters. This has an advantage, friends can think about it. Of course, this is what I think would be better this way.

Realize paging:

Before implementing paging, we knew that the sort field of the paging parameter class we defined at that time used a string instead of a lambda expression, and Linq To EF needed a Lambda representation to be able to sort. There are two solutions here. You can write a method yourself to realize the conversion from string to Lambda expression; the second is to borrow a third-party library to achieve this, which happens to be in the EF Core enhanced plug-in we quoted earlier:

var list = context.Customers.OrderByDescendingDynamic(x => "x.Name").ToList();

This is the example it gives.

We can first write an implementation method based on this:

public PageModel<T> Search(PageCondition<T> condition)
{
   var result = new PageModel<T>
   {
       TotalCount = LongCount(condition.Predicate),
       CurrentPage = condition.CurrentPage,
       PerpageSize = condition.PerpageSize,
   };
   var source = Query(condition.Predicate);
   if (condition.Sort.ToUpper().StartsWith("a")) // asc
   {
       source = source.OrderByDynamic(t => $"t.{condition.OrderProperty}");
   }
   else // desc
   {
       source = source.OrderByDescendingDynamic(t => $"t.{condition.OrderProperty}");
   }
   var items = source.Skip((condition.CurrentPage -1)* condition.PerpageSize).Take(condition.PerpageSize);
   result.Items = items.ToList();
   return result;
}

Back to the first option:

We need to manually write a string processing method, first create the following directory in the Utils project: Extend>Lambda, and add an ExtLinq class to the directory, the code is as follows:

using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;

namespace Utils.Extend.Lambda
{
   public static class ExtLinq
   {
       public static IQueryable<T> CreateOrderExpression<T>(this IQueryable<T> source, string orderBy, string orderAsc)
       {
           if (string.IsNullOrEmpty(orderBy)|| string.IsNullOrEmpty(orderAsc)) return source;
           var isAsc = orderAsc.ToLower() == "asc";
           var _order = orderBy.Split(',');
           MethodCallExpression resultExp = null;
           foreach (var item in _order)
           {
               var orderPart = item;
               orderPart = Regex.Replace(orderPart, @"\s+", " ");
               var orderArry = orderPart.Split(' ');
               var orderField = orderArry[0];
               if (orderArry.Length == 2)
               {
                   isAsc = orderArry[1].ToUpper() == "ASC";
               }
               var parameter = Expression.Parameter(typeof(T), "t");
               var property = typeof(T).GetProperty(orderField);
               var propertyAccess = Expression.MakeMemberAccess(parameter, property);
               var orderByExp = Expression.Lambda(propertyAccess, parameter);
               resultExp = Expression.Call(typeof(Queryable), isAsc ? "OrderBy" : "OrderByDescending",
                   new[] {typeof(T), property.PropertyType},
                   source.Expression, Expression.Quote(orderByExp));
           }

           return resultExp == null
               ? source
               : source.Provider.CreateQuery<T>(resultExp);
       }
   }
}

Don't worry about why it is written this way for the time being, I will analyze it for everyone in the follow-up.

Then come back and implement our paging, first add Utils to the Domain.Implements project

cd ../Domain.Implements # 进入Domain.Implements 项目目录
dotnet add reference ../Utils
public PageModel<T> Search(PageCondition<T> condition)
{
   var result = new PageModel<T>
   {
       TotalCount = LongCount(condition.Predicate),
       CurrentPage = condition.CurrentPage,
       PerpageSize = condition.PerpageSize,
   };
   var source = Set.Where(condition.Predicate).CreateOrderExpression(condition.OrderProperty, condition.Sort);
   var items = source.Skip((condition.CurrentPage -1)* condition.PerpageSize).Take(condition.PerpageSize);
   result.Items = items.ToList();
   return result;
}

Remember to add a quote:

using Utils.Extend.Lambda;

When doing paging, because most of the parameters passed in the foreground are sorted fields of strings, the processing of strings to fields is required to the backend. The processing here uses a technique of C# Expression, so I won't introduce it too much here. The follow-up will be introduced in the advanced part of .net core.

4. Summary

So far, it seems that we have successfully achieved the purpose of using EF Core to achieve data manipulation and query for us. However, don't forget that EF Core needs to manually call a SaveChanges method. In the next article, we will introduce how to elegantly execute the SaveChanges method.

This article is introduced here. Although there are not many explanations, this is also the experience I summarized in the development.


Guess you like

Origin blog.51cto.com/15060511/2639813
Recommended