栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

EF Core 中实现 动态数据过滤器

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

EF Core 中实现 动态数据过滤器

前言

  在项目开发中,我们很多时候都会设计  软删除、所属用户 等等一系列字段 来方便我们在业务查询的时候进行各种过滤

  然后引申的问题就是:

    在业务查询的时候,我们要如何加上这些条件?或者动态禁用某些查询条件呢?

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(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(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(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         /// 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 List 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(); 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 IQueryable Filter(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 DbSet DbSet => 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(IEnumerable 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         /// 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(IEnumerable 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         /// 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(IEnumerable 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  ///  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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/230964.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号