前言
在项目开发中,我们很多时候都会设计 软删除、所属用户 等等一系列字段 来方便我们在业务查询的时候进行各种过滤
然后引申的问题就是:
在业务查询的时候,我们要如何加上这些条件?或者动态禁用某些查询条件呢?
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 IDynamicFilter 2 { 3 BaseQueryFilter InitFilter(DbContext dbContext); 4 }
这样我们这边就得到了一个标准
例如 我们我们需要一个 所属用户和 软删除 的数据筛选器 我们只需要继承他即可
我们如何区分他们呢?
我们在之前使用 Z.EntityFramework.Plus 是看到了 可以设置筛选器的Key
所以 我们也同样扩展个属性 DynamicFilterAttribute 来作为他们的名字
1 public class DynamicFilterAttribute: Attribute 2 { 3 /// <summary> 4 /// Name 5 /// </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 dbContext 17 .Filter<ICreate>("CreateUserId", x => x.Where(w => w.CreateUserId == string.Empty )); 18 19 return dbContext 20 .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 dbContext 10 .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 DynamicFilterConfig 5 { 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 /// CacheGenericDynamicFilter 10 /// </summary> 11 public static Dictionary<string, IDynamicFilter> CacheGenericDynamicFilter { get; set; } 12 13 /// <summary> 14 /// AddDynamicFilter 15 /// </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 /// AsWorkDataNoFilter 26 /// </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 : class 34 { 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 /// SetCacheGenericDynamicFilter 47 /// </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 context 15 /// </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 : DbContext 21 { 22 return GetCurrentDbContext<TDbContext>(dic, tranDic, string.Empty); 23 } 24 25 /// <summary> 26 ///GetCurrentDbContext 27 /// </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 : DbContext 35 { 36 conString = typeof(TDbContext).ToString(); 37 var dbContext = dic.ContainsKey(conString + "DbContext") ? dic[conString + "DbContext"] : null; 38 try 39 { 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 : class 12 { 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 : class 11 { 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 /// FindBy 114 /// </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 /// FindBy 127 /// </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 /// GetAll 147 /// </summary> 148 /// <returns></returns> 149 public override IQueryable<TEntity> GetAll() 150 { 151 return DbSet; 152 } 153 154 155 /// <summary> 156 /// GetAll 157 /// </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 /// GetAll 172 /// </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 /// GetAll 182 /// </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 /// AsNoFilterGetAll 199 /// </summary> 200 /// <returns></returns> 201 public override IQueryable<TEntity> AsNoFilterGetAll() 202 { 203 return DbSet.AsNoFilter(); 204 } 205 206 /// <summary> 207 /// AsNoFilterGetAll 208 /// </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 #endregion 222 223 #region Insert 224 225 /// <summary> 226 /// Insert 227 /// </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 /// InsertGetId 237 /// </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 /// Insert 251 /// </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 #endregion 264 265 #region Delete 266 267 /// <summary> 268 /// Delete 269 /// </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 /// Delete 279 /// </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 #endregion 292 293 #region Update 294 295 /// <summary> 296 /// Update 297 /// </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 /// Update 307 /// </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 #endregion 320 }
说明:仓储的设计理念是从 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