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

来源:https://www.cnblogs.com/c7jie/archive/2020/06/09/13081316.html
-Advertisement-
Play Games

0. 前言 通過前兩篇,我們創建了一個項目,並規定了一個基本的數據層訪問介面。這一篇,我們將以EF Core為例演示一下數據層訪問介面如何實現,以及實現中需要註意的地方。 1. 添加EF Core 先在數據層實現層引入 EF Core: cd Domain.Implements dotnet add ...


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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 使用工具:Free Spire.PDF for Java (免費版) Jar**文件獲取及導入: 方法1:通過官網下載獲取jar包。解壓後將lib文件夾下的Spire.Pdf.jar文件導入Java程式。(如下圖) 方法2:通過maven倉庫安裝導入。具體安裝詳解參見此網頁。 【示例1】提取PDF中 ...
  • 1.詳細文檔及源代碼獲取 文檔地址:https://www.sunnyblog.top/detail.html?id=1270334641173168128 2.頁面效果 任務列表分頁查詢 任務創建 任務編輯 任務刪除 任務啟動 任務停止 詳細開發技術文檔盡在 點擊這裡查看技術文檔 ;更多技術文章: ...
  • 項目簡介 項目來源於:https://github.com/1462656075/car 本系統是基於JSP+SSH+Mysql+DBCP實現的租車系統。在當代開發中,SSH的使用已經逐漸被SSM取代,但不代表我們不需要學習SSH,該系統簡單,但功能齊全可以作為SSH框架初學者的入門項目。 難度等級 ...
  • B樹和B+樹的插入、刪除圖文詳解 本文摘自:https://www.cnblogs.com/nullzx/p/8729425.html 感謝大佬nullzx的總結與分享。 另外為驗證本人的正確性,通過一個小工具驗證了下文中的拆入結果,感興趣的伙伴們 可以自己動手驗證。 B-Trees 簡介:本文主要 ...
  • 前言 本文的文字及圖片來源於網路,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯繫我們以作處理。 作者:CDA數據分析師 地攤經濟火了!微博微熱點數據 我們先看到微博微熱點的數據:全網熱度指數趨勢 從全網熱度指數的變化趨勢來看,地攤經濟的熱度在6月3日起逐步升溫,6月4日 ...
  • QT讀JSON文件步驟,這裡把過程記錄一下,網上大多都是怎麼寫json的,對於讀的,記錄的不多 首先JSON文件格式必須為UTF-8(非UTF-8 with BOM),UTF-8 with BOM 即為UTF-8 前加了BOM標識,會導致解析失敗,報錯內容非法,這時候就將文件保存為UTF-8就好了。 ...
  • 使用vs2019創建ASP.Net Core Web應用程式: 右側高級選項中有一項啟用Docker支持,勾選後vs會自動幫我們創建Dockerfile: 看一下Dockerfile的內容: #See https://aka.ms/containerfastmode to understand ho ...
  • SunnyUI為了避免視覺傳達差異,使用一套特定的調色板來規定顏色,為你所搭建的產品提供一致的外觀視覺感受。 主色 SunnyUI主要品牌顏色是鮮艷、友好的藍色。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...