0. 前言通過前兩篇,我們創(chuàng)建了一個項目,并規(guī)定了一個基本的數(shù)據(jù)層訪問接口。這一篇,我們將以EF Core為例演示一下數(shù)據(jù)層訪問接口如何實現(xiàn),以及實現(xiàn)中需要注意的地方。 1. 添加EF Core先在數(shù)據(jù)層實現(xiàn)層引入 EF Core: cd Domain.Implements dotnet add package Microsoft.EntityFrameworkCore 當前項目以SqlLite為例,所以再添加一個SqlLite數(shù)據(jù)庫驅(qū)動: dotnet add package Microsoft.EntityFrameworkCore.SQLite 刪除 Domain.Implements 里默認的Class1.cs 文件,然后添加Insfrastructure目錄,創(chuàng)建一個 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);//如果需要別的數(shù)據(jù)庫,在這里進行修改
}
}
}2. EF Core 批量加載模型通常情況下,在使用ORM的時候,我們不希望過度的使用特性來標注實體類。因為如果后期需要變更ORM或者出現(xiàn)其他變動的時候,使用特性來標注實體類的話,會導(dǎo)致遷移變得復(fù)雜。而且大部分ORM框架的特性都依賴于框架本身,并非是統(tǒng)一的特性結(jié)構(gòu),這樣就會造成一個后果:本來應(yīng)該是對調(diào)用方隱藏的實現(xiàn)就會被公開,而且在項目引用關(guān)系中容易出現(xiàn)循環(huán)引用。 所以,我在開發(fā)中會尋找是否支持配置類,如果使用配置類或者在ORM框架中設(shè)置映射關(guān)系,那么就可以保證數(shù)據(jù)層的純凈,也能實現(xiàn)對調(diào)用方隱藏實現(xiàn)。 EF Core的配置類我們在《C# 數(shù)據(jù)訪問系列》中關(guān)于EF的文章中介紹過,這里就不做過多介紹了(沒來得及看的小伙伴們不著急,后續(xù)會有一個簡單版的介紹)。 通常情況下,配置類我也會放在Domain.Implements項目中?,F(xiàn)在我給大家介紹一下如何快速批量加載配置類: protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetAssembly(this.GetType()),
t => t.GetInterfaces().Any(i => t.Name.Contains("IEntityTypeConfiguration")));
}現(xiàn)在版本的EF Core支持通過Assembly加載配置類,可以指定加載當前上下文類所在的Assembly,然后篩選實現(xiàn)接口中包含IEntityTypeConfiguration的類即可。 3. 使用EF Core實現(xiàn)數(shù)據(jù)操作我們已經(jīng)創(chuàng)建好了一個EF Context,那么現(xiàn)在就帶領(lǐng)大家一起看一下,如何使用EF來實現(xiàn) 上一篇《「asp.net core」7 實戰(zhàn)之 數(shù)據(jù)訪問層定義》中介紹的數(shù)據(jù)訪問接口: 新建一個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;
}
}
}先創(chuàng)建以上內(nèi)容,這里給Repository傳參的時候,使用的是EFCore的默認Context類不是我們自己定義的。這是我個人習慣,實際上并沒有其他影響。主要是為了對實現(xiàn)類隱藏具體的EF 上下文實現(xiàn)類。 在實現(xiàn)各接口方法之前,創(chuàng)建如下屬性: public DbSet<T> Set { get => Context.Set<T>(); }這是EF操作數(shù)據(jù)的核心所在。 3.1 實現(xiàn)IModifyRepository接口先實現(xiàn)修改接口: 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);
}在修改接口里,我預(yù)留了幾個方法沒有實現(xiàn),因為這幾個方法使用EF Core自身可以實現(xiàn),但實現(xiàn)會比較麻煩,所以這里借助一個EF Core的插件: dotnet add package Z.EntityFramework.Plus.EFCore 這是一個免費開源的插件,可以直接使用。在Domain.Implements 中添加后,在BaseRepository 中添加如下引用: using System.Linq; using System.Linq.Expressions; 實現(xiàn)方法: 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);
}
}這里根據(jù)主鍵刪除的方法有個問題,我們無法根據(jù)條件進行刪除,實際上如果約定泛型T是BaseEntity的子類,我們可以獲取到主鍵,但是這樣又會引入另一個泛型,為了避免引入多個泛型根據(jù)主鍵的刪除就采用了這種方式。 3.2 實現(xiàn)ISearchRepository 接口獲取數(shù)據(jù)以及基礎(chǔ)統(tǒng)計接口: 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);
}這里有一個需要關(guān)注的地方,在使用條件查詢單個數(shù)據(jù)的時候,我使用了SingleOrDefault而不是FirstOrDefault。這是因為我在這里做了規(guī)定,如果使用條件查詢,調(diào)用方應(yīng)該能預(yù)期所使用條件是能查詢出最多一條數(shù)據(jù)的。不過,這里可以根據(jù)實際業(yè)務(wù)需要修改方法:
實現(xiàn)查詢方法: 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();
}這里我盡量通過調(diào)用了參數(shù)最多的方法來實現(xiàn)查詢功能,這樣有一個好處,小伙伴們可以想一下哈。當然了,這是我自己覺得這樣會好一點。 實現(xiàn)分頁: 在實現(xiàn)分頁之前,我們知道當時我們定義的分頁參數(shù)類的排序字段用的是字符串,而不是lambda表達式,而Linq To EF需要一個Lambda表示才可以進行排序。這里就有兩種方案,可以自己寫一個方法,實現(xiàn)字符串到Lambda表達式的轉(zhuǎn)換;第二種就是借用三方庫來實現(xiàn),正好我們之前引用的EF Core增強插件里有這個功能: var list = context.Customers.OrderByDescendingDynamic(x => "x.Name").ToList(); 這是它給出的示例。 我們可以先依此來寫一份實現(xiàn)方法: 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項目創(chuàng)建以下目錄: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);
}
}
}暫時不用關(guān)心為什么這樣寫,后續(xù)會為大家分析的。 然后回過頭來再實現(xiàn)我們的分頁,先添加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; 在做分頁的時候,因為前臺傳入的參數(shù)大多都是字符串的排序字段,所以到后端需要進程字符串到字段的處理。這里的處理利用了C# Expression的一個技術(shù),這里就不做過多介紹了。后續(xù)在.net core高級篇中會有介紹。 4. 總結(jié)到目前為止,看起來我們已經(jīng)成功實現(xiàn)了利用EF Core為我們達成 數(shù)據(jù)操作和查詢的目的。但是,別忘了EF Core需要手動調(diào)用一個SaveChanges方法。下一篇,我們將為大家介紹如何優(yōu)雅的執(zhí)行SaveChanges方法。 這一篇介紹到這里,雖然說明不是很多,但是這也是我在開發(fā)中總結(jié)的經(jīng)驗。 |
|
|