前言
在项目开发中,我们很多时候都会设计 软删除、所属用户 等等一系列字段 来方便我们在业务查询的时候进行各种过滤
然后引申的问题就是:
在业务查询的时候,我们要如何加上这些条件?或者动态禁用某些查询条件呢?
EF Core自带的全局过滤查询功能
EF Core提供了一个HasQueryFilter 供我们在查询的时候进行预置部分筛选条件
例如:
builder.HasQueryFilter(x => !x.IsDelete);
这样查询的时候 EF Core 会自动帮我们实现过滤
然后如果不想使用的时候可以全部忽略
DbSet.IgnoreQueryFilters();
咋一看 很完美
然后我们实际操作的时候
1.我是不是每个Entity里面是不是都要配置一次呢?
2.我只想禁用部分筛选条件呢?
3.我的查询条件的某些参数要动态呢?
例如和用户相关的数据等等
(有些人可能会说 我想办法把User的信息注入到DbContext里面不就可以了 假如我还要别的信息呢 还是接着注入?)
这就是理论和实践之间的差距
然后再网上找好久,找到了 EntityFramework-Plus (开源免费)
https://github.com/zzzprojects/EntityFramework-Plus
官网地址: http://entityframework-plus.net/
内置了很多功能 本篇只针对查询过滤做说嘛
EntityFramework-Plus 查询过滤功能
1.QueryFilterManager
QueryFilterManager 主要用来预设全局过滤
例如:
QueryFilterManager.Filter<Customer>(q => q.Where(x => x.IsActive));
var ctx = new EntitiesContext();
QueryFilterManager.InitilizeGlobalFilter(ctx);
这样即可。。。
但是需要提前注意的是 QueryFilterManager 预设后是无法更改的
无法更改这是在 谷歌的时候 作者正好回复的别人的时候看到的
就和我们之前第三点 动态 冲突了
然后只能再看别的方式了
2.Filter
Z.EntityFramework.Plus 提供了 通过DbContext 的扩展方式来进行注入筛选条件的方式
例如:
var ctx = new EntitiesContext();
ctx.Filter<IAnimal>(MyEnum.EnumValue, q => q.Where(x => x.IsDomestic))
//禁用指定键值查询条件
ctx.Filter(MyEnum.EnumValue).Disable();
var dogs = ctx.Dogs.ToList();
//启用指定键值查询条件
ctx.Filter(MyEnum.EnumValue).Enable();
// SELECT * FROM Dog WHERE IsDomestic = true
var dogs = ctx.Dogs.ToList();
这样好像符合我们的需求
3.AsNoFilter
禁用条件
例如:
var ctx = new EntitiesContext();
this.Filter<Customer>(q => q.Where(x => x.IsActive));
// SELECT * FROM Customer WHERE IsActive = true
var list = ctx.Customers.ToList();
// SELECT * FROM Customer
var list = ctx.Customers.AsNoFilter().ToList();
AsNoFilter()后如何启用 指定查询条件 作者好像没有做相应扩展 ,后面会给出对应扩展方法
-----------------------------------------------------------------------------------------------------------------------------------------------------------
说了这么多 理论补完了 实际操作的时候呢?
1.这些条件如何注入进来呢?
2.如何可以让我任意扩展呢?
3.假如我们操作时通过仓储 ,而不是 直接通过DbContext 呢?
如何封装
这边演示通过我自己的开源项目做为事例:
github : https://github.com/wulaiwei/WorkData.Core
主要依赖的框架
1.AutoFac
2.EF Core
3.Z.EntityFramework.Plus
-----------------------------------------------------------------------------------------------------------------------------------------
对于我们来说 我们无论使用多少个数据筛选器 返回的都应该是同一个返回值 ,我们去看 DbContext.Filter(....) 会发现他的返回值都是 BaseQueryFilter
针对这个 我们可以得到两条信息 我们需要 传入 DbContext 和 一个返回值为 BaseQueryFilter 的方法
所以 我们定义如下接口 IDynamicFilter
1 public interface IDynamicFilter2 {3 BaseQueryFilter InitFilter(DbContext dbContext);4 }
这样我们这边就得到了一个标准
例如 我们我们需要一个 所属用户和 软删除 的数据筛选器 我们只需要继承他即可
我们如何区分他们呢?
我们在之前使用 Z.EntityFramework.Plus 是看到了 可以设置筛选器的Key
所以 我们也同样扩展个属性 DynamicFilterAttribute 来作为他们的名字
1 public class DynamicFilterAttribute: Attribute2 {3 /// <summary>4 /// Name5 /// </summary>6 public string Name { get; set; }7 8 }
然后我们定义我们的 所属用户和 软删除 的数据筛选器 并为他们设置名称
CreateDynamicFilter
1 /// <summary> 2 /// CreateDynamicFilter 3 /// </summary> 4 [DynamicFilter(Name = "CreateUserId")] 5 public class CreateDynamicFilter : IDynamicFilter 6 { 7 /// <summary> 8 /// InitFilter 9 /// </summary>10 /// <param name="dbContext"></param>11 /// <returns></returns>12 public BaseQueryFilter InitFilter(DbContext dbContext)13 {14 var workdataSession = IocManager.Instance.Resolve<IWorkDataSession>();15 if (workdataSession == null)16 return dbContext17 .Filter<ICreate>("CreateUserId", x => x.Where(w => w.CreateUserId == string.Empty ));18 19 return dbContext20 .Filter<ICreate>("CreateUserId", x => x.Where(w => w.CreateUserId == workdataSession.UserId || w.CreateUserId == ""));21 }22 }
说明:
var workdataSession = IocManager.Instance.Resolve<IWorkDataSession>();
用来获取你所需要的 传参
IocManager.Instance.Resolve 是WorkData 关于Ioc的封装 源码可以参见git 或者上一篇博客
SoftDeleteDynamicFilter
1 /// <summary> 2 /// SoftDeleteDynamicFilter 3 /// </summary> 4 [DynamicFilter(Name = "SoftDelete")] 5 public class SoftDeleteDynamicFilter: IDynamicFilter 6 { 7 public BaseQueryFilter InitFilter(DbContext dbContext) 8 { 9 return dbContext10 .Filter<IsSoftDelete>("SoftDelete", x => x.Where(w => !w.IsDelete));11 }12 }
这样 我们所有接口 和实现定义好了 如何管理呢?
1.将继承 IDynamicFilter 的注入到Ioc里面
1 #region 动态审计注入 2 var filterTypes = _typeFinder.FindClassesOfType<IDynamicFilter>(); 3 4 foreach (var filterType in filterTypes) 5 { 6 var dynamicFilterAttribute = filterType.GetCustomAttribute(typeof(DynamicFilterAttribute)) as DynamicFilterAttribute; 7 if (dynamicFilterAttribute == null) 8 continue; 9 10 builder.RegisterType(filterType).Named<IDynamicFilter>(dynamicFilterAttribute.Name);11 }12 #endregion
说明:
1.ITypeFinder 是从 nopcommerce 抽离出来的反射方法 已集成到WorkData 百度即可查询到相应说明文档
2.通过 GetCustomAttribute 获取 DynamicFilterAttribute 的属性名称 作为注册到Ioc名称
2.如何设置一个启用数据筛选器呢?我们这边定义个配置文件 通过 .net core 提供的程序进行配置文件注入
1 /// <summary>2 /// 动态拦截器配置3 /// </summary>4 public class DynamicFilterConfig5 {6 public List<string> DynamicFilterList{ get; set; }7 }
"DynamicFilterConfig": {
"DynamicFilterList": [ "CreateUserId", "SoftDelete" ]
}
如何注入配置文件 可以通过百度或者查看workdata源码 即可 这不做说明
3.如何管理呢?什么时候统一添加到 DbContext呢?
我们这边定义一个DynamicFilterManager 提供一个 字典集合 来暂存所以的 IDynamicFilter,同时提供一个方法来进行初始化值
1 public static class DynamicFilterManager 2 { 3 static DynamicFilterManager() 4 { 5 CacheGenericDynamicFilter = new Dictionary<string, IDynamicFilter>(); 6 } 7 8 /// <summary> 9 /// CacheGenericDynamicFilter10 /// </summary>11 public static Dictionary<string, IDynamicFilter> CacheGenericDynamicFilter { get; set; }12 13 /// <summary>14 /// AddDynamicFilter15 /// </summary>16 /// <param name="dbContext"></param>17 /// <returns></returns>18 public static void AddDynamicFilter(this DbContext dbContext)19 {20 if (dbContext == null) return;21 foreach (var dynamicFilter in CacheGenericDynamicFilter) dynamicFilter.Value.InitFilter(dbContext);22 }23 24 /// <summary>25 /// AsWorkDataNoFilter26 /// </summary>27 /// <typeparam name="T"></typeparam>28 /// <param name="query"></param>29 /// <param name="context"></param>30 /// <param name="filterStrings"></param>31 /// <returns></returns>32 public static IQueryable<T> AsWorkDataNoFilter<T>(this DbSet<T> query, DbContext context,33 params object[] filterStrings) where T : class34 {35 var asNoFilterQueryable = query.AsNoFilter();36 37 object query1 = asNoFilterQueryable;38 var items = CacheGenericDynamicFilter.Where(x => filterStrings.Contains(x.Key));39 40 query1 = items.Select(key => context.Filter(key.Key)).Where(item => item != null)41 .Aggregate(query1, (current, item) => (IQueryable) item.ApplyFilter<T>(current));42 return (IQueryable<T>) query1;43 }44 45 /// <summary>46 /// SetCacheGenericDynamicFilter47 /// </summary>48 public static void SetCacheGenericDynamicFilter()49 {50 var dynamicFilterConfig = IocManager.Instance.ResolveServiceValue<DynamicFilterConfig>();51 52 foreach (var item in dynamicFilterConfig.DynamicFilterList)53 {54 var dynamicFilter = IocManager.Instance.ResolveName<IDynamicFilter>(item);55 CacheGenericDynamicFilter.Add(item, dynamicFilter);56 }57 }58 }
然后我们在DbContext里面的 OnModelCreating 进行初始化
1 /// <summary> 2 /// 重写模型创建函数 3 /// </summary> 4 /// <param name="modelBuilder"></param> 5 protected override void OnModelCreating(ModelBuilder modelBuilder) 6 { 7 base.OnModelCreating(modelBuilder); 8 9 //初始化对象10 DynamicFilterManager.SetCacheGenericDynamicFilter();11 }
初始化完成后如何将条件付给 DbContext 呢?
在DynamicFilterManager 中我们提供了一个扩展方法 AddDynamicFilter 你可以在你创建 DbContext 的时候调用
1 /// <summary> 2 /// AddDynamicFilter 3 /// </summary> 4 /// <param name="dbContext"></param> 5 /// <returns></returns> 6 public static void AddDynamicFilter(this DbContext dbContext) 7 { 8 if (dbContext == null) return; 9 foreach (var dynamicFilter in CacheGenericDynamicFilter) dynamicFilter.Value.InitFilter(dbContext);10 }
在WorkData中 我们则需要在EfContextFactory 进行调用
dbContext = _resolver.Resolve<TDbContext>();
//初始化拦截器
dbContext.AddDynamicFilter();
1 /// <summary> 2 /// EfContextFactory 3 /// </summary> 4 public class EfContextFactory : IEfContextFactory 5 { 6 private readonly IResolver _resolver; 7 8 public EfContextFactory(IResolver resolver) 9 {10 _resolver = resolver;11 }12 13 /// <summary>14 /// default current context15 /// </summary>16 /// <param name="dic"></param>17 /// <param name="tranDic"></param>18 /// <returns></returns>19 public TDbContext GetCurrentDbContext<TDbContext>(Dictionary<string, DbContext> dic, Dictionary<DbContext, IDbContextTransaction> tranDic)20 where TDbContext : DbContext21 {22 return GetCurrentDbContext<TDbContext>(dic, tranDic, string.Empty);23 }24 25 /// <summary>26 ///GetCurrentDbContext27 /// </summary>28 /// <typeparam name="TDbContext"></typeparam>29 /// <param name="dic"></param>30 /// <param name="tranDic"></param>31 /// <param name="conString"></param>32 /// <returns></returns>33 public TDbContext GetCurrentDbContext<TDbContext>(Dictionary<string, DbContext> dic, Dictionary<DbContext, IDbContextTransaction> tranDic, string conString)34 where TDbContext : DbContext35 {36 conString = typeof(TDbContext).ToString();37 var dbContext = dic.ContainsKey(conString + "DbContext") ? dic[conString + "DbContext"] : null;38 try39 {40 if (dbContext != null)41 {42 return (TDbContext)dbContext;43 }44 }45 catch (Exception)46 {47 dic.Remove(conString + "DbContext");48 }49 dbContext = _resolver.Resolve<TDbContext>();50 51 //初始化拦截器52 dbContext.AddDynamicFilter();53 54 //我们在创建一个,放到数据槽中去55 dic.Add(conString + "DbContext", dbContext);56 57 //开始事务58 var tran = dbContext.Database.BeginTransaction();59 tranDic.Add(dbContext, tran);60 61 return (TDbContext)dbContext;62 }63 }
这样我们的筛选器已经全部注入完成了
还剩下一个我们之前说的
AsNoFilter()后如何启用 指定查询条件 作者好像没有做相应扩展 ,后面会给出对应扩展方法
通过查看源码后
1 /// <summary> 2 /// Filter the query using context filters associated with specified keys. 3 /// </summary> 4 /// <typeparam name="T">The type of elements of the query.</typeparam> 5 /// <param name="query">The query to filter using context filters associated with specified keys.</param> 6 /// <param name="keys"> 7 /// A variable-length parameters list containing keys associated to context filters to use to filter the 8 /// query. 9 /// </param>10 /// <returns>The query filtered using context filters associated with specified keys.</returns>11 public static IQueryable<T> Filter<T>(this DbSet<T> query, params object[] keys) where T : class12 {13 BaseQueryFilterQueryable filterQueryable = QueryFilterManager.GetFilterQueryable((IQueryable) query);14 IQueryable<T> query1 = filterQueryable != null ? (IQueryable<T>) filterQueryable.OriginalQuery : (IQueryable<T>) query;15 return QueryFilterManager.AddOrGetFilterContext(filterQueryable != null ? filterQueryable.Context : InternalExtensions.GetDbContext<T>(query)).ApplyFilter<T>(query1, keys);16 }
Z.EntityFramework.Plus 提供了一个 ApplyFilter 所以 我们基于这个 做个扩展
1 /// <summary> 2 /// AsWorkDataNoFilter 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 /// <param name="query"></param> 6 /// <param name="context"></param> 7 /// <param name="filterStrings"></param> 8 /// <returns></returns> 9 public static IQueryable<T> AsWorkDataNoFilter<T>(this DbSet<T> query, DbContext context,10 params object[] filterStrings) where T : class11 {12 var asNoFilterQueryable = query.AsNoFilter();13 14 object query1 = asNoFilterQueryable;15 var items = CacheGenericDynamicFilter.Where(x => filterStrings.Contains(x.Key));16 17 query1 = items.Select(key => context.Filter(key.Key)).Where(item => item != null)18 .Aggregate(query1, (current, item) => (IQueryable) item.ApplyFilter<T>(current));19 return (IQueryable<T>) query1;20 }
这样 我们可以传入指定的筛选器名称 启用自己想要的
最终我们的仓储就变成了这样:
1 /// <summary> 2 /// EfBaseRepository 3 /// </summary> 4 /// <typeparam name="TEntity"></typeparam> 5 /// <typeparam name="TPrimaryKey"></typeparam> 6 /// <typeparam name="TDbContext"></typeparam> 7 public class EfBaseRepository<TDbContext, TEntity, TPrimaryKey> : 8 BaseRepository<TEntity, TPrimaryKey>, 9 IRepositoryDbConntext where TEntity : class, IAggregateRoot, IEntity<TPrimaryKey> 10 where TDbContext : DbContext 11 { 12 //public IQueryable<EntityType> EntityTypes => Context.Model.EntityTypes.Where(t => t.Something == true); 13 14 private readonly IDbContextProvider<TDbContext> _dbContextProvider; 15 private readonly IPredicateGroup<TEntity> _predicateGroup; 16 17 public EfBaseRepository( 18 IDbContextProvider<TDbContext> dbContextProvider, 19 IPredicateGroup<TEntity> predicateGroup) 20 { 21 _dbContextProvider = dbContextProvider; 22 _predicateGroup = predicateGroup; 23 } 24 25 /// <summary> 26 /// Gets EF DbContext object. 27 /// </summary> 28 public TDbContext Context => _dbContextProvider.GetContent(); 29 30 /// <summary> 31 /// Gets DbSet for given entity. 32 /// </summary> 33 public virtual DbSet<TEntity> DbSet => Context.Set<TEntity>(); 34 35 #region DbContext 36 37 /// <summary> 38 /// GetDbContext 39 /// </summary> 40 /// <returns></returns> 41 public DbContext GetDbContext() 42 { 43 return Context; 44 } 45 46 #endregion 47 48 #region Query 49 50 51 52 /// <summary> 53 /// FindBy 54 /// </summary> 55 /// <param name="primaryKey"></param> 56 /// <returns></returns> 57 public override TEntity FindBy(TPrimaryKey primaryKey) 58 { 59 var entity = DbSet.Find(primaryKey); 60 return entity; 61 } 62 63 /// <summary> 64 /// FindBy 65 /// </summary> 66 /// <param name="primaryKey"></param> 67 /// <param name="includeNames"></param> 68 /// <returns></returns> 69 public override TEntity FindBy(TPrimaryKey primaryKey, string[] includeNames) 70 { 71 var query = DbSet; 72 foreach (var includeName in includeNames) 73 { 74 query.Include(includeName); 75 } 76 var entity = query.Find(primaryKey); 77 return entity; 78 } 79 80 /// <summary> 81 /// AsNoFilterFindBy 82 /// </summary> 83 /// <param name="primaryKey"></param> 84 /// <returns></returns> 85 public override TEntity AsNoFilterFindBy(TPrimaryKey primaryKey) 86 { 87 var entity = DbSet.AsNoFilter() 88 .SingleOrDefault(x => x.Id.Equals(primaryKey)); 89 return entity; 90 } 91 92 /// <summary> 93 /// AsNoFilterFindBy 94 /// </summary> 95 /// <param name="primaryKey"></param> 96 /// <param name="includeNames"></param> 97 /// <returns></returns> 98 public override TEntity AsNoFilterFindBy(TPrimaryKey primaryKey, string[] includeNames) 99 {100 101 var query = DbSet.AsNoFilter();102 foreach (var includeName in includeNames)103 {104 query.Include(includeName);105 }106 var entity = query.SingleOrDefault(x => x.Id.Equals(primaryKey));107 108 return entity;109 }110 111 112 /// <summary>113 /// FindBy114 /// </summary>115 /// <param name="primaryKey"></param>116 /// <param name="filterStrings"></param>117 /// <returns></returns>118 public override TEntity FindBy(TPrimaryKey primaryKey, params object[] filterStrings)119 {120 var entity = DbSet.AsWorkDataNoFilter(Context, filterStrings)121 .SingleOrDefault(x => x.Id.Equals(primaryKey));122 return entity;123 }124 125 /// <summary>126 /// FindBy127 /// </summary>128 /// <param name="primaryKey"></param>129 /// <param name="includeNames"></param>130 /// <param name="filterStrings"></param>131 /// <returns></returns>132 public override TEntity FindBy(TPrimaryKey primaryKey, string[] includeNames, params object[] filterStrings)133 {134 var query = DbSet.AsWorkDataNoFilter(Context, filterStrings);135 foreach (var includeName in includeNames)136 {137 query.Include(includeName);138 }139 var entity = query.SingleOrDefault(x => x.Id.Equals(primaryKey));140 141 return entity;142 }143 144 145 /// <summary>146 /// GetAll147 /// </summary>148 /// <returns></returns>149 public override IQueryable<TEntity> GetAll()150 {151 return DbSet;152 }153 154 155 /// <summary>156 /// GetAll157 /// </summary>158 /// <param name="includeNames"></param>159 /// <returns></returns>160 public override IQueryable<TEntity> GetAll(string[] includeNames)161 {162 var query = DbSet;163 foreach (var includeName in includeNames)164 {165 query.Include(includeName);166 }167 return query;168 }169 170 /// <summary>171 /// GetAll172 /// </summary>173 /// <param name="filterStrings"></param>174 /// <returns></returns>175 public override IQueryable<TEntity> GetAll(params object[] filterStrings)176 {177 return DbSet.AsWorkDataNoFilter(Context, filterStrings);178 }179 180 /// <summary>181 /// GetAll182 /// </summary>183 /// <param name="includeNames"></param>184 /// <param name="filterStrings"></param>185 /// <returns></returns>186 public override IQueryable<TEntity> GetAll(string[] includeNames, params object[] filterStrings)187 {188 var query = DbSet.AsWorkDataNoFilter(Context, filterStrings);189 190 foreach (var includeName in includeNames)191 {192 query.Include(includeName);193 }194 return query;195 }196 197 /// <summary>198 /// AsNoFilterGetAll199 /// </summary>200 /// <returns></returns>201 public override IQueryable<TEntity> AsNoFilterGetAll()202 {203 return DbSet.AsNoFilter();204 }205 206 /// <summary>207 /// AsNoFilterGetAll208 /// </summary>209 /// <param name="includeNames"></param>210 /// <returns></returns>211 public override IQueryable<TEntity> AsNoFilterGetAll(string[] includeNames)212 {213 var query = DbSet.AsNoFilter();214 215 foreach (var includeName in includeNames)216 {217 query.Include(includeName);218 }219 return query;220 }221 #endregion222 223 #region Insert224 225 /// <summary>226 /// Insert227 /// </summary>228 /// <typeparam name="TEntity"></typeparam>229 /// <param name="model"></param>230 public override TEntity Insert(TEntity model)231 {232 return DbSet.Add(model).Entity;233 }234 235 /// <summary>236 /// InsertGetId237 /// </summary>238 /// <param name="model"></param>239 /// <returns></returns>240 public override TPrimaryKey InsertGetId(TEntity model)241 {242 model = Insert(model);243 244 Context.SaveChanges();245 246 return model.Id;247 }248 249 /// <summary>250 /// Insert251 /// </summary>252 /// <param name="entities"></param>253 public override void Insert(IEnumerable<TEntity> entities)254 {255 if (entities == null)256 throw new ArgumentNullException(nameof(entities));257 258 DbSet.AddRange(entities);259 260 Context.SaveChanges();261 }262 263 #endregion264 265 #region Delete266 267 /// <summary>268 /// Delete269 /// </summary>270 /// <param name="entity"></param>271 public override void Delete(TEntity entity)272 {273 DbSet.Remove(entity);274 Context.SaveChanges();275 }276 277 /// <summary>278 /// Delete279 /// </summary>280 /// <param name="entities"></param>281 public override void Delete(IEnumerable<TEntity> entities)282 {283 if (entities == null)284 throw new ArgumentNullException(nameof(entities));285 286 DbSet.RemoveRange(entities);287 288 Context.SaveChanges();289 }290 291 #endregion292 293 #region Update294 295 /// <summary>296 /// Update297 /// </summary>298 /// <param name="entity"></param>299 public override void Update(TEntity entity)300 {301 DbSet.Update(entity);302 Context.SaveChanges();303 }304 305 /// <summary>306 /// Update307 /// </summary>308 /// <param name="entities"></param>309 public override void Update(IEnumerable<TEntity> entities)310 {311 if (entities == null)312 throw new ArgumentNullException(nameof(entities));313 314 DbSet.UpdateRange(entities);315 316 Context.SaveChanges();317 }318 319 #endregion320 }
说明:仓储的设计理念是从 ABP中抽离出来的
最后附测试
启用的筛选器为 "CreateUserId", "SoftDelete"
1 /// <summary> 2 /// Index 3 /// </summary> 4 /// <returns></returns> 5 public IActionResult Index() 6 { 7 _baseRepository.GetAll().ToList(); 8 _baseRepository.GetAll("CreateUserId","xxx假定不存在的筛选器").ToList(); 9 _baseRepository.AsNoFilterGetAll().ToList();10 11 _baseRepository.FindBy("1");12 _baseRepository.FindBy("1", "CreateUserId", "xxx假定不存在的筛选器");13 _baseRepository.AsNoFilterFindBy("1");14 return View();15 }
原文出处:https://www.cnblogs.com/wulaiwei/p/9561830.html