【asp.net core 系列】8 实战之 利用 EF Core 完成数据操作层的实现
0. 前言
通过前两篇,我们创建了一个项目,并规定了一个基本的数据层访问接口。这一篇,我们将以EF Core为例演示一下数据层访问接口如何实现,以及实现中需要注意的地方。
1. 添加EF Core
先在数据层实现层引入 EF Core:
cd Domain.Implements
dotnet add package Microsoft.EntityFrameworkCore
当前项目以SqlLite为例,所以再添加一个SqlLite数据库驱动:
dotnet add package Microsoft.EntityFrameworkCore.SQLite
删除 Domain.Implements 里默认的Class1.cs 文件,然后添加Insfrastructure目录,创建一个 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 批量加载模型
通常情况下,在使用ORM的时候,我们不希望过度的使用特性来标注实体类。因为如果后期需要变更ORM或者出现其他变动的时候,使用特性来标注实体类的话,会导致迁移变得复杂。而且大部分ORM框架的特性都依赖于框架本身,并非是统一的特性结构,这样就会造成一个后果:本来应该是对调用方隐藏的实现就会被公开,而且在项目引用关系中容易出现循环引用。
所以,我在开发中会寻找是否支持配置类,如果使用配置类或者在ORM框架中设置映射关系,那么就可以保证数据层的纯净,也能实现对调用方隐藏实现。
EF Core的配置类我们在《C# 数据访问系列》中关于EF的文章中介绍过,这里就不做过多介绍了(没来得及看的小伙伴们不着急,后续会有一个简单版的介绍)。
通常情况下,配置类我也会放在Domain.Implements项目中。现在我给大家介绍一下如何快速批量加载配置类:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetAssembly(this.GetType()),
t => t.GetInterfaces().Any(i => t.Name.Contains("IEntityTypeConfiguration")));
}
现在版本的EF Core支持通过Assembly加载配置类,可以指定加载当前上下文类所在的Assembly,然后筛选实现接口中包含IEntityTypeConfiguration的类即可。
3. 使用EF Core实现数据操作
我们已经创建好了一个EF Context,那么现在就带领大家一起看一下,如何使用EF来实现 上一篇《「asp.net core」7 实战之 数据访问层定义》中介绍的数据访问接口:
新建一个BaseRepository类,在Domain.Implements项目的Insfrastructure 目录下:
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;
}
}
}
先创建以上内容,这里给Repository传参的时候,使用的是EFCore的默认Context类不是我们自己定义的。这是我个人习惯,实际上并没有其他影响。主要是为了对实现类隐藏具体的EF 上下文实现类。
在实现各接口方法之前,创建如下属性:
public DbSet<T> Set { get => Context.Set<T>(); }
这是EF操作数据的核心所在。
3.1 实现IModifyRepository接口
先实现修改接口:
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);
}
在修改接口里,我预留了几个方法没有实现,因为这几个方法使用EF Core自身可以实现,但实现会比较麻烦,所以这里借助一个EF Core的插件:
dotnet add package Z.EntityFramework.Plus.EFCore
这是一个免费开源的插件,可以直接使用。在Domain.Implements 中添加后,在BaseRepository 中添加如下引用:
using System.Linq;
using System.Linq.Expressions;
实现方法:
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);
}
}
这里根据主键删除的方法有个问题,我们无法根据条件进行删除,实际上如果约定泛型T是BaseEntity的子类,我们可以获取到主键,但是这样又会引入另一个泛型,为了避免引入多个泛型根据主键的删除就采用了这种方式。
3.2 实现ISearchRepository 接口
获取数据以及基础统计接口:
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);
}
这里有一个需要关注的地方,在使用条件查询单个数据的时候,我使用了SingleOrDefault而不是FirstOrDefault。这是因为我在这里做了规定,如果使用条件查询,调用方应该能预期所使用条件是能查询出最多一条数据的。不过,这里可以根据实际业务需要修改方法:
- Single 返回单个数据,如果数据大于1或者等于0,则抛出异常
- SingleOrDefault 返回单个数据,如果结果集没有数据,则返回null,如果多于1,则抛出异常
- First 返回结果集的第一个元素,如果结果集没有数据,则抛出异常
- FirstOrDefault 返回结果集的第一个元素,如果没有元素则返回null
实现查询方法:
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();
}
这里我尽量通过调用了参数最多的方法来实现查询功能,这样有一个好处,小伙伴们可以想一下哈。当然了,这是我自己觉得这样会好一点。
实现分页:
在实现分页之前,我们知道当时我们定义的分页参数类的排序字段用的是字符串,而不是lambda表达式,而Linq To EF需要一个Lambda表示才可以进行排序。这里就有两种方案,可以自己写一个方法,实现字符串到Lambda表达式的转换;第二种就是借用三方库来实现,正好我们之前引用的EF Core增强插件里有这个功能:
var list = context.Customers.OrderByDescendingDynamic(x => "x.Name").ToList();
这是它给出的示例。
我们可以先依此来写一份实现方法:
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;
}
回到第一种方案:
我们需要手动写一个字符串的处理方法,先在Utils项目创建以下目录:Extend>Lambda,并在目录中添加一个ExtLinq类,代码如下:
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);
}
}
}
暂时不用关心为什么这样写,后续会为大家分析的。
然后回过头来再实现我们的分页,先添加Utils 到Domain.Implements项目中
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;
}
记得添加引用:
using Utils.Extend.Lambda;
在做分页的时候,因为前台传入的参数大多都是字符串的排序字段,所以到后端需要进程字符串到字段的处理。这里的处理利用了C# Expression的一个技术,这里就不做过多介绍了。后续在.net core高级篇中会有介绍。
4. 总结
到目前为止,看起来我们已经成功实现了利用EF Core为我们达成 数据操作和查询的目的。但是,别忘了EF Core需要手动调用一个SaveChanges方法。下一篇,我们将为大家介绍如何优雅的执行SaveChanges方法。
这一篇介绍到这里,虽然说明不是很多,但是这也是我在开发中总结的经验。
更多内容烦请关注我的博客《高先生小屋》
【asp.net core 系列】8 实战之 利用 EF Core 完成数据操作层的实现的更多相关文章
- Asp.net core下利用EF core实现从数据实现多租户(1)
前言 随着互联网的的高速发展,大多数的公司由于一开始使用的传统的硬件/软件架构,导致在业务不断发展的同时,系统也逐渐地逼近传统结构的极限. 于是,系统也急需进行结构上的升级换代. 在服务端,系统的I/ ...
- Asp.net core下利用EF core实现从数据实现多租户(3): 按Schema分离 附加:EF Migration 操作
前言 前段时间写了EF core实现多租户的文章,实现了根据数据库,数据表进行多租户数据隔离. 今天开始写按照Schema分离的文章. 其实还有一种,是通过在数据表内添加一个字段做多租户的,但是这种模 ...
- ASP.NET CORE系列【六】Entity Framework Core 之数据迁移
原文:ASP.NET CORE系列[六]Entity Framework Core 之数据迁移 前言 最近打算用.NET Core写一份简单的后台系统,来练练手 然后又用到了Entity Framew ...
- Cookies 初识 Dotnetspider EF 6.x、EF Core实现dynamic动态查询和EF Core注入多个上下文实例池你知道有什么问题? EntityFramework Core 运行dotnet ef命令迁移背后本质是什么?(EF Core迁移原理)
Cookies 1.创建HttpCookies Cookie=new HttpCookies("CookieName");2.添加内容Cookie.Values.Add(&qu ...
- MySQL官方.NET Core驱动已出,支持EF Core
千呼万唤始出来MySQL官方.NET Core驱动已出,支持EF Core. 昨天MySQL官方已经发布了.NET Core 驱动,目前还是预览版,不过功能已经可用. NuGet 地址:https:/ ...
- NET Core驱动已出,支持EF Core
NET Core驱动已出,支持EF Core 千呼万唤始出来MySQL官方.NET Core驱动已出,支持EF Core. 昨天MySQL官方已经发布了.NET Core 驱动,目前还是预览版,不过功 ...
- [翻译 EF Core in Action 1.9] 掀开EF Core的引擎盖看看EF Core内部是如何工作的
Entity Framework Core in Action Entityframework Core in action是 Jon P smith 所著的关于Entityframework Cor ...
- Entity Framework Core系列之实战(ASP.NET Core MVC应用程序)
本示例演示在ASP.NET 应用程序中使用EF CORE创建数据库并对其做基本的增删改查操作.当然我们默认你的机器上已经安装了.NET CORE SDK以及合适的IDE.本例使用的是Visual St ...
- ASP.NET CORE系列【六】Entity Framework Core 之数据库迁移
前言 最近打算用.NET Core写一份简单的后台系统,来练练手 然后又用到了Entity Framework Core 发现园子里有些文章讲得不是那么细节,对于新手小白来说,可能会有点懵. 特意整理 ...
随机推荐
- Zookeeper 如何保证分布式系统数据一致性
写在前面 分布式架构出现后,越来越多的分布式系统会面临数据一致性的问题.目前,ZooKeeper 是在解决分布式数据一致性上最成熟稳定且被大规模应用的工业级解决方案. ZooKeeper 保证 分布式 ...
- React-Router 4 两个常用路由变量
讲真我个人不太喜欢4.x版本,虽然作者自信动态路由的形式符合React组件化的哲学,但是路由和一般组件耦合太深,而且后期组件分片也麻烦,以后需要重构的话怕是会一番折腾.同学公司用的还是3.x版本. 不 ...
- 如何覆盖elementUI样式
question: 在某个组件里面更改element-Ui的样式,而不影响全局. solution: 在需要更改的组件里新增一个style标签[重点:不要加scoped],然后直接获取class设置样 ...
- etcd分布式锁及事务
前言 分布式锁是控制分布式系统之间同步访问共享资源的一种方式.在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互 ...
- Mac node.js express-generator脚手架安装
前言 由于本人在学习NodeJs的express框架时,在Mac电脑上安装express遇到了一个深痛的坑点,特写此文来记录.该坑点的解决方案我在国内的度娘没有找到,问别人也没有方案,最后通过goog ...
- mysql单记录也能造成的死锁
最近在开发的时候,在mysql Innodb 引擎下,一条记录记录也能引起锁的事件. 场景描述 在项目压测的是,突然发现有类似以下的异常发生: com.mysql.jdbc.exceptions.jd ...
- 【python爬虫】scrapy入门4--添加cookies
(1) settings.py 取消注释:COOKIES_ENABLED = True (2)爬虫xx.py def parse(self, response): c_dic = {自己抓包} # 获 ...
- channelartlist标签的使用
用来获取当前频道的下级栏目的内容列表标签 . type=“top”表示顶级栏目 ,typeid='top' 限制上级栏目ID:如果只要调用其中几个频道的内容可以用{dede:channelartlis ...
- 格雷码 CSP(NOIP??)2019 洛谷 P5657
洛谷AC通道! 多年过后,重新来看这道D1T1,20min不到AC,再回忆起当初考场三小时的抓耳挠腮,不禁感慨万千啊!! 发篇题解记录一下. 思路:直接dfs模拟即可(二进制找规律是不可能的, 这辈子 ...
- SD.Team主题形象小人偶
W e ♥ S D ♫ ♪ 咔咔咔~可能源码冲突会造成小人偶光头 :)