主頁 > .NET開發 > 【asp.net core 系列】8 實戰之 利用 EF Core 完成資料操作層的實作

【asp.net core 系列】8 實戰之 利用 EF Core 完成資料操作層的實作

2020-09-13 05:21:47 .NET開發

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方法,

這一篇介紹到這里,雖然說明不是很多,但是這也是我在開發中總結的經驗,

更多內容煩請關注我的博客《高先生小屋》

file

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/20477.html

標籤:C#

上一篇:《C# 敏捷開發實踐》 - 學習筆記

下一篇:C# Windows Service 用代碼實作安裝、運行和卸載服務的方法

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more