經過前兩篇,咱們建立了一個項目,並規定了一個基本的數據層訪問接口。這一篇,咱們將以EF Core爲例演示一下數據層訪問接口如何實現,以及實現中須要注意的地方。數據庫
先在數據層實現層引入 EF Core:後端
cd Domain.Implements
dotnet add package Microsoft.EntityFrameworkCore
當前項目以SqlLite爲例,因此再添加一個SqlLite數據庫驅動:app
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);//若是須要別的數據庫,在這裏進行修改
}
}
}
一般狀況下,在使用ORM的時候,咱們不但願過分的使用特性來標註實體類。由於若是後期須要變動ORM或者出現其餘變更的時候,使用特性來標註實體類的話,會致使遷移變得複雜。並且大部分ORM框架的特性都依賴於框架自己,並不是是統一的特性結構,這樣就會形成一個後果:原本應該是對調用方隱藏的實現就會被公開,並且在項目引用關係中容易出現循環引用。asp.net
因此,我在開發中會尋找是否支持配置類,若是使用配置類或者在ORM框架中設置映射關係,那麼就能夠保證數據層的純淨,也能實現對調用方隱藏實現。ide
EF Core的配置類咱們在《C# 數據訪問系列》中關於EF的文章中介紹過,這裏就不作過多介紹了(沒來得及看的小夥伴們不着急,後續會有一個簡單版的介紹)。ui
一般狀況下,配置類我也會放在Domain.Implements項目中。如今我給你們介紹一下如何快速批量加載配置類:this
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的類便可。spa
咱們已經建立好了一個EF Context,那麼如今就帶領你們一塊兒看一下,如何使用EF來實現 上一篇《「asp.net core」7 實戰之 數據訪問層定義》中介紹的數據訪問接口:.net
新建一個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操做數據的核心所在。
先實現修改接口:
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的子類,咱們能夠獲取到主鍵,可是這樣又會引入另外一個泛型,爲了不引入多個泛型根據主鍵的刪除就採用了這種方式。
獲取數據以及基礎統計接口:
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高級篇中會有介紹。
到目前爲止,看起來咱們已經成功實現了利用EF Core爲咱們達成 數據操做和查詢的目的。可是,別忘了EF Core須要手動調用一個SaveChanges方法。下一篇,咱們將爲你們介紹如何優雅的執行SaveChanges方法。
這一篇介紹到這裏,雖說明不是不少,可是這也是我在開發中總結的經驗。