在项目开发中,我们很多时候都会设计 软删除、所属用户 等等一系列字段 来方便我们在业务查询的时候进行各种过滤
然后引申的问题就是:
在业务查询的时候,我们要如何加上这些条件?或者动态禁用某些查询条件呢?
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
var ctx = new EntitiesContext();
QueryFilterManager.InitilizeGlobalFilter(ctx);
这样即可。。。
但是需要提前注意的是 QueryFilterManager 预设后是无法更改的
无法更改这是在 谷歌的时候 作者正好回复的别人的时候看到的
就和我们之前第三点 动态 冲突了
然后只能再看别的方式了
2.Filter
Z.Entityframework.Plus 提供了 通过DbContext 的扩展方式来进行注入筛选条件的方式
例如:
var ctx = new EntitiesContext();
ctx.Filter
//禁用指定键值查询条件
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
// 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 /// 4 /// Name5 /// 6 public string Name { get; set; }7 8 }然后我们定义我们的 所属用户和 软删除 的数据筛选器 并为他们设置名称
CreateDynamicFilter
1 ///2 /// CreateDynamicFilter 3 /// 4 [DynamicFilter(Name = "CreateUserId")] 5 public class CreateDynamicFilter : IDynamicFilter 6 { 7 ///8 /// InitFilter 9 /// 10 /// 11 ///12 public baseQueryFilter InitFilter(DbContext dbContext)13 {14 var workdataSession = IocManager.Instance.Resolve ();15 if (workdataSession == null)16 return dbContext17 .Filter ("CreateUserId", x => x.Where(w => w.CreateUserId == string.Empty ));18 19 return dbContext20 .Filter ("CreateUserId", x => x.Where(w => w.CreateUserId == workdataSession.UserId || w.CreateUserId == ""));21 }22 }
说明:
var workdataSession = IocManager.Instance.Resolve
用来获取你所需要的 传参
IocManager.Instance.Resolve 是WorkData 关于Ioc的封装 源码可以参见git 或者上一篇博客
SoftDeleteDynamicFilter
1 ///2 /// SoftDeleteDynamicFilter 3 /// 4 [DynamicFilter(Name = "SoftDelete")] 5 public class SoftDeleteDynamicFilter: IDynamicFilter 6 { 7 public baseQueryFilter InitFilter(DbContext dbContext) 8 { 9 return dbContext10 .Filter("SoftDelete", x => x.Where(w => !w.IsDelete));11 }12 }
这样 我们所有接口 和实现定义好了 如何管理呢?
1.将继承 IDynamicFilter 的注入到Ioc里面
1 #region 动态审计注入 2 var filterTypes = _typeFinder.FindClassesOfType(); 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 (dynamicFilterAttribute.Name);11 }12 #endregion
说明:
1.ITypeFinder 是从 nopcommerce 抽离出来的反射方法 已集成到WorkData 百度即可查询到相应说明文档
2.通过 GetCustomAttribute 获取 DynamicFilterAttribute 的属性名称 作为注册到Ioc名称
2.如何设置一个启用数据筛选器呢?我们这边定义个配置文件 通过 .net core 提供的程序进行配置文件注入
1 ///2 /// 动态拦截器配置3 /// 4 public class DynamicFilterConfig5 {6 public ListDynamicFilterList{ 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(); 6 } 7 8 /// 9 /// CacheGenericDynamicFilter10 /// 11 public static Dictionary CacheGenericDynamicFilter { get; set; }12 13 /// 14 /// AddDynamicFilter15 /// 16 /// 17 /// 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 /// 25 /// AsWorkDataNoFilter26 /// 27 /// 28 /// 29 /// 30 /// 31 /// 32 public static IQueryable AsWorkDataNoFilter(this DbSet 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(current));42 return (IQueryable) query1;43 }44 45 /// 46 /// SetCacheGenericDynamicFilter47 /// 48 public static void SetCacheGenericDynamicFilter()49 {50 var dynamicFilterConfig = IocManager.Instance.ResolveServicevalue();51 52 foreach (var item in dynamicFilterConfig.DynamicFilterList)53 {54 var dynamicFilter = IocManager.Instance.ResolveName(item);55 CacheGenericDynamicFilter.Add(item, dynamicFilter);56 }57 }58 } 然后我们在DbContext里面的 OnModelCreating 进行初始化
1 ///2 /// 重写模型创建函数 3 /// 4 /// 5 protected override void onModelCreating(ModelBuilder modelBuilder) 6 { 7 base.onModelCreating(modelBuilder); 8 9 //初始化对象10 DynamicFilterManager.SetCacheGenericDynamicFilter();11 }
初始化完成后如何将条件付给 DbContext 呢?
在DynamicFilterManager 中我们提供了一个扩展方法 AddDynamicFilter 你可以在你创建 DbContext 的时候调用
1 ///2 /// AddDynamicFilter 3 /// 4 /// 5 ///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
//初始化拦截器
dbContext.AddDynamicFilter();
1 ///2 /// EfContextFactory 3 /// 4 public class EfContextFactory : IEfContextFactory 5 { 6 private readonly IResolver _resolver; 7 8 public EfContextFactory(IResolver resolver) 9 {10 _resolver = resolver;11 }12 13 ///14 /// default current context15 /// 16 /// 17 /// 18 ///19 public TDbContext GetCurrentDbContext (Dictionary dic, Dictionary tranDic)20 where TDbContext : DbContext21 {22 return GetCurrentDbContext (dic, tranDic, string.Empty);23 }24 25 /// 26 ///GetCurrentDbContext27 /// 28 ///29 /// 30 /// 31 /// 32 /// 33 public TDbContext GetCurrentDbContext (Dictionary dic, Dictionary 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 ();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 ///2 /// Filter the query using context filters associated with specified keys. 3 /// 4 ///The type of elements of the query. 5 /// The query to filter using context filters associated with specified keys. 6 /// 7 /// A variable-length parameters list containing keys associated to context filters to use to filter the 8 /// query. 9 /// 10 ///The query filtered using context filters associated with specified keys. 11 public static IQueryableFilter (this DbSet query, params object[] keys) where T : class12 {13 baseQueryFilterQueryable filterQueryable = QueryFilterManager.GetFilterQueryable((IQueryable) query);14 IQueryable query1 = filterQueryable != null ? (IQueryable ) filterQueryable.OriginalQuery : (IQueryable ) query;15 return QueryFilterManager.AddOrGetFilterContext(filterQueryable != null ? filterQueryable.Context : InternalExtensions.GetDbContext (query)).ApplyFilter (query1, keys);16 }
Z.Entityframework.Plus 提供了一个 ApplyFilter 所以 我们基于这个 做个扩展
1 ///2 /// AsWorkDataNoFilter 3 /// 4 ///5 /// 6 /// 7 /// 8 /// 9 public static IQueryable AsWorkDataNoFilter (this DbSet 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 (current));19 return (IQueryable ) query1;20 }
这样 我们可以传入指定的筛选器名称 启用自己想要的
最终我们的仓储就变成了这样:
1 ///2 /// EfbaseRepository 3 /// 4 ///5 /// 6 /// 7 public class EfbaseRepository : 8 baseRepository , 9 IRepositoryDbConntext where TEntity : class, IAggregateRoot, IEntity 10 where TDbContext : DbContext 11 { 12 //public IQueryable EntityTypes => Context.Model.EntityTypes.Where(t => t.Something == true); 13 14 private readonly IDbContextProvider _dbContextProvider; 15 private readonly IPredicateGroup _predicateGroup; 16 17 public EfbaseRepository( 18 IDbContextProvider dbContextProvider, 19 IPredicateGroup predicateGroup) 20 { 21 _dbContextProvider = dbContextProvider; 22 _predicateGroup = predicateGroup; 23 } 24 25 /// 26 /// Gets EF DbContext object. 27 /// 28 public TDbContext Context => _dbContextProvider.GetContent(); 29 30 ///31 /// Gets DbSet for given entity. 32 /// 33 public virtual DbSetDbSet => Context.Set (); 34 35 #region DbContext 36 37 /// 38 /// GetDbContext 39 /// 40 ///41 public DbContext GetDbContext() 42 { 43 return Context; 44 } 45 46 #endregion 47 48 #region Query 49 50 51 52 /// 53 /// FindBy 54 /// 55 /// 56 ///57 public override TEntity FindBy(TPrimaryKey primaryKey) 58 { 59 var entity = DbSet.Find(primaryKey); 60 return entity; 61 } 62 63 /// 64 /// FindBy 65 /// 66 /// 67 /// 68 ///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 /// 81 /// AsNoFilterFindBy 82 /// 83 /// 84 ///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 /// 93 /// AsNoFilterFindBy 94 /// 95 /// 96 /// 97 ///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 /// 113 /// FindBy114 /// 115 /// 116 /// 117 ///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 /// 126 /// FindBy127 /// 128 /// 129 /// 130 /// 131 ///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 /// 146 /// GetAll147 /// 148 ///149 public override IQueryable GetAll()150 {151 return DbSet;152 }153 154 155 /// 156 /// GetAll157 /// 158 /// 159 ///160 public override IQueryable 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 /// 171 /// GetAll172 /// 173 /// 174 ///175 public override IQueryable GetAll(params object[] filterStrings)176 {177 return DbSet.AsWorkDataNoFilter(Context, filterStrings);178 }179 180 /// 181 /// GetAll182 /// 183 /// 184 /// 185 ///186 public override IQueryable 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 /// 198 /// AsNoFilterGetAll199 /// 200 ///201 public override IQueryable AsNoFilterGetAll()202 {203 return DbSet.AsNoFilter();204 }205 206 /// 207 /// AsNoFilterGetAll208 /// 209 /// 210 ///211 public override IQueryable 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 /// 226 /// Insert227 /// 228 ///229 /// 230 public override TEntity Insert(TEntity model)231 {232 return DbSet.Add(model).Entity;233 }234 235 /// 236 /// InsertGetId237 /// 238 /// 239 ///240 public override TPrimaryKey InsertGetId(TEntity model)241 {242 model = Insert(model);243 244 Context.SaveChanges();245 246 return model.Id;247 }248 249 /// 250 /// Insert251 /// 252 /// 253 public override void Insert(IEnumerableentities)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 /// 268 /// Delete269 /// 270 /// 271 public override void Delete(TEntity entity)272 {273 DbSet.Remove(entity);274 Context.SaveChanges();275 }276 277 ///278 /// Delete279 /// 280 /// 281 public override void Delete(IEnumerableentities)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 /// 296 /// Update297 /// 298 /// 299 public override void Update(TEntity entity)300 {301 DbSet.Update(entity);302 Context.SaveChanges();303 }304 305 ///306 /// Update307 /// 308 /// 309 public override void Update(IEnumerableentities)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 ///2 /// Index 3 /// 4 ///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



