【asp.net core 系列】8 实战之 利用 EF Core 完成数据操作层的实现

简介:

【asp.net core 系列】8 实战之 利用 EF Core 完成数据操作层的实现

  1. 前言
    通过前两篇,我们创建了一个项目,并规定了一个基本的数据层访问接口。这一篇,我们将以EF Core为例演示一下数据层访问接口如何实现,以及实现中需要注意的地方。
  2. 添加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);//如果需要别的数据库,在这里进行修改
    }
}

}

  1. 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的类即可。

  1. 使用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 Set { get => Context.Set(); }
这是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 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> predicate, Expression> updator)
{

Set.Where(predicate).UpdateFromQuery(updator);

}

public void Delete(Expression> 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> predicate)
{

return Set.SingleOrDefault(predicate);

}

public int Count()
{

return Set.Count();

}

public long LongCount()
{

return Set.LongCount();

}

public int Count(Expression> predicate)
{

return Set.Count(predicate);

}

public long LongCount(Expression> predicate)
{

return Set.LongCount(predicate);

}

public bool IsExists(Expression> predicate)
{

return Set.Any(predicate);

}
这里有一个需要关注的地方,在使用条件查询单个数据的时候,我使用了SingleOrDefault而不是FirstOrDefault。这是因为我在这里做了规定,如果使用条件查询,调用方应该能预期所使用条件是能查询出最多一条数据的。不过,这里可以根据实际业务需要修改方法:

Single 返回单个数据,如果数据大于1或者等于0,则抛出异常
SingleOrDefault 返回单个数据,如果结果集没有数据,则返回null,如果多于1,则抛出异常
First 返回结果集的第一个元素,如果结果集没有数据,则抛出异常
FirstOrDefault 返回结果集的第一个元素,如果没有元素则返回null
实现查询方法:

public List Search()
{

return Query().ToList();

}

public List Search(Expression> predicate)
{

return Query(predicate).ToList();

}

public IEnumerable Query()
{

return Set;

}

public IEnumerable Query(Expression> predicate)
{

return Set.Where(predicate);

}

public List Search

(Expression> predicate, Expression> order)
{

return Search(predicate, order, false);

}

public List Search

(Expression> predicate, Expression> 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 Search(PageCondition 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 Search(PageCondition 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高级篇中会有介绍。

  1. 总结
    到目前为止,看起来我们已经成功实现了利用EF Core为我们达成 数据操作和查询的目的。但是,别忘了EF Core需要手动调用一个SaveChanges方法。下一篇,我们将为大家介绍如何优雅的执行SaveChanges方法。

这一篇介绍到这里,虽然说明不是很多,但是这也是我在开发中总结的经验。

原文地址https://www.cnblogs.com/c7jie/p/13081316.html

相关文章
|
1月前
|
SQL 开发框架 数据库
".NET开发者的超能力:AgileEAS.NET ORM带你穿越数据库的迷宫,让数据操作变得轻松又神奇!"
【8月更文挑战第16天】AgileEAS.NET是面向.NET平台的企业应用开发框架,核心功能包括数据关系映射(ORM),允许以面向对象方式操作数据库,无需编写复杂SQL。通过继承`AgileEAS.Data.Entity`创建实体类对应数据库表,利用ORM简化数据访问层编码。支持基本的CRUD操作及复杂查询如条件筛选、排序和分页,并可通过导航属性实现多表关联。此外,提供了事务管理功能确保数据一致性。AgileEAS.NET的ORM简化了数据库操作,提升了开发效率和代码可维护性。
44 5
|
14天前
|
SQL 关系型数据库 数据库
七天.NET 8操作SQLite入门到实战详细教程(选型、开发、发布、部署)
七天.NET 8操作SQLite入门到实战详细教程(选型、开发、发布、部署)
|
24天前
|
测试技术 API 开发者
.NET单元测试框架大比拼:MSTest、xUnit与NUnit的实战较量与选择指南
【8月更文挑战第28天】单元测试是软件开发中不可或缺的一环,它能够确保代码的质量和稳定性。在.NET生态系统中,MSTest、xUnit和NUnit是最为流行的单元测试框架。本文将对这三种测试框架进行全面解析,并通过示例代码展示它们的基本用法和特点。
41 7
|
22天前
|
开发框架 缓存 前端开发
实战.NET Framework 迁移到 .NET 5/6
从.NET Framework 迁移到.NET 5/6 是一次重要的技术革新,涵盖开发环境与应用架构的全面升级。本文通过具体案例详细解析迁移流程,包括评估现有应用、利用.NET Portability Analyzer 工具识别可移植代码、创建新项目、逐步迁移代码及处理依赖项更新等关键步骤。特别关注命名空间调整、JSON 序列化工具更换及数据库访问层重构等内容,旨在帮助开发者掌握最佳实践,确保迁移过程平稳高效,同时提升应用性能与可维护性。
52 2
|
1月前
|
开发框架 .NET 数据库连接
闲话 Asp.Net Core 数据校验(三)EF Core 集成 FluentValidation 校验数据例子
闲话 Asp.Net Core 数据校验(三)EF Core 集成 FluentValidation 校验数据例子
|
20天前
|
API 开发者 Java
API 版本控制不再难!Spring 框架带你玩转多样化的版本管理策略,轻松应对升级挑战!
【8月更文挑战第31天】在开发RESTful服务时,为解决向后兼容性问题,常需进行API版本控制。本文以Spring框架为例,探讨四种版本控制策略:URL版本控制、请求头版本控制、查询参数版本控制及媒体类型版本控制,并提供示例代码。此外,还介绍了通过自定义注解与过滤器实现更灵活的版本控制方案,帮助开发者根据项目需求选择最适合的方法,确保API演化的管理和客户端使用的稳定与兼容。
56 0
|
24天前
|
监控 Cloud Native 开发者
云端精英的.NET微服务秘籍:Azure上的创新实战演练
【8月更文挑战第28天】在现代软件开发中,微服务架构通过分解应用程序提升可维护性和扩展性。结合Azure与.NET框架,开发者能轻松打造高效且易管理的云原生微服务。首先,使用Docker容器化.NET应用,并借助Azure Kubernetes Service(AKS)或Azure Container Instances(ACI)部署。为确保高可用性和伸缩性,可利用Azure Traffic Manager负载均衡及Azure Autoscale动态调整实例数。
22 0
|
24天前
|
Kubernetes Linux 开发者
【实战秘籍】从零开始:用.NET与Docker打造现代化容器化应用之旅
【8月更文挑战第28天】本文详细介绍如何使用 .NET 框架构建并部署 Docker 容器化应用程序,涵盖环境搭建、项目创建、Dockerfile 编写等关键步骤。首先安装必要软件,如 Visual Studio 2022 及 Docker Desktop。接着创建 .NET Core 控制台应用,并在项目根目录编写 Dockerfile 文件。使用 .NET 运行时基础镜像,复制二进制文件,指定入口点。运行命令构建镜像并测试容器。为实现通信,映射端口。最后,标签化镜像并推送到 Docker Hub,为生产环境部署做好准备。掌握这些步骤,即可轻松应对从小型项目到大规模应用的各种需求。
48 0
|
1月前
|
存储 开发框架 .NET
ASP.NET Web Api 使用 EF 6,DateTime 字段如何取数据库服务器当前时间
ASP.NET Web Api 使用 EF 6,DateTime 字段如何取数据库服务器当前时间