FreeSql.Repository 實作了過濾器,它不僅是查詢時過濾,連删除/修改/插入時都會進行驗證,避免資料安全問題。
過濾器
目前過濾器依附在倉儲層實作,每個倉儲執行個體都有 IDataFilter 屬性,可利用其完成過濾器管理,它是獨立的修改後不影響全局。
public interface IDataFilter<TEntity> where TEntity : class {
IDataFilter<TEntity> Apply(string filterName, Expression<Func<TEntity, bool>> filterAndValidateExp);
IDisposable Enable(params string[] filterName);
IDisposable EnableAll();
IDisposable Disable(params string[] filterName);
IDisposable DisableAll();
bool IsEnabled(string filterName);
}
臨時禁用
using (repos1.DataFilter.Disable("test")) {
//在這段中,repos1 之 test 過濾器失效
}
//repos1 之 test 過濾器重新生效
如何使用倉儲
dotnet add package FreeSql.Repository
var fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\document.db;Pooling=true;Max Pool Size=10")
.UseLogger(loggerFactory.CreateLogger<IFreeSql>())
.UseAutoSyncStructure(true) //自動遷移實體的結構到資料庫
.Build();
public class Song {
[Column(IsIdentity = true)]
public int Id { get; set; }
public string Title { get; set; }
}
1、IFreeSql 的擴充方法;
var curd1 = fsql.GetRepository<Song, int>();
var curd2 = fsql.GetGuidRepository<Song>();
2、繼承現實;
public class SongRepository : BaseRepository<Song, int> {
public SongRepository(IFreeSql fsql) : base(fsql) {}
//在這裡增加 CURD 以外的方法
}
3、Autofac 注入,使用方法繼續往下看【全局過濾器】;
過濾與驗證
假設我們有User(使用者)、Topic(主題)兩個實體,在領域類中定義了兩個倉儲:
var userRepository = fsql.GetGuidRepository<User>();
var topicRepository = fsql.GetGuidRepository<Topic>();
在開發過程中,總是擔心 topicRepository 的資料安全問題,即有可能查詢或操作到其他使用者的主題。是以我們在v0.0.7版本進行了改進,增加了 filter lambad 表達式參數。
var userRepository = fsql.GetGuidRepository<User>(a => a.Id == 1);
var topicRepository = fsql.GetGuidRepository<Topic>(a => a.UserId == 1);
- 在查詢/修改/删除時附加此條件,進而達到不會修改其他使用者的資料;
- 在添加時,使用表達式驗證資料的合法性,若不合法則抛出異常;
全局過濾器
全局過濾器,可幫助實作“軟删除”、“租戶”等設計,目前使用 Autofac 注入的方式實作的全局過濾器。
public IServiceProvider ConfigureServices(IServiceCollection services) {
services.AddSingleton<IFreeSql>(fsql);
services.AddMvc();
var builder = new ContainerBuilder();
//示範全局過濾的倉儲類注入,如果實體中不存在 Title 屬性,則條件不生效
builder.RegisterFreeRepository(filter =>
filter.Apply<Song>("test", a => a.Title == DateTime.Now.ToString() + Thread.CurrentThread.ManagedThreadId)
);
builder.Populate(services);
var container = builder.Build();
return new AutofacServiceProvider(container);
}
public class xxxx {
public int Id { get; set; }
}
public class Song {
[Column(IsIdentity = true)]
public int Id { get; set; }
public string Title { get; set; }
}
//在控制器使用
public SongsController(GuidRepository<Song> repos1, GuidRepository<xxxx> repos2) {
//在此打斷點,調試
}
第一次請求:
repos1.Select.ToSql()
"SELECT a."Id", a."Title" \r\nFROM "Song" a \r\nWHERE (a."Title" = strftime('%Y-%m-%d %H:%M.%f',datetime(current_timestamp,'localtime')) || 21)"
repos2.Select.ToSql()
"SELECT a."Id" \r\nFROM "xxxx" a"
第二次請求:
"SELECT a."Id", a."Title" \r\nFROM "Song" a \r\nWHERE (a."Title" = strftime('%Y-%m-%d %H:%M.%f',datetime(current_timestamp,'localtime')) || 4)"
//禁用過濾器
repos1.DataFilter.Disable("test")
"SELECT a."Id", a."Title" \r\nFROM "Song" a"
測試總結:
1、注入的變量值在使用時有了動态變化,每次擷取時都是新的(Thread.CurrentThread.ManagedThreadId);
2、設定的全局過濾,若某實體不存在表達式函數中的字段時,不會生效(如上xxxx不存在Title);
3、使用 DataFilter.Disable("test") 可臨時關閉過濾器的效果,使用 DataFilter.Enable("test") 可重新開啟;
4、倉儲對象建立時,從全局過濾器copy進來,然後自己管理自己。修改後不影響其他或全局設定。
github 源碼: https://github.com/2881099/FreeSql