天天看點

【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;

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);           

(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.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

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