打造更好用的 ef 自动审计

intro

上次基于 ef core 实现了一个自动审计的功能,详细可以参考 ,虽然说多数情况下可以适用,但是因为要显式继承于一个 auditdbcontextbaseauditdbcontext,所以对代码的侵入性比较强,对于已经无法修改的代码或者已经继承于某一个类了,就无法再继承 auditdbcontext 了,就没有办法实现自动审计了,在 weihanli.entityframework 1.7.0 新版本里引入了 aop 的设计,结合 aop 来实现就简单很多了,不再需要对原有的 dbcontext 有任何修改就可以轻松实现自动审计了,下面来看如何做

实例演示

服务注册

使用 addproxydbcontext 代替 adddbcontextaddproxydbcontextpool 代替 adddbcontextpool,会自动注册代理服务,以实现 aop 拦截

var services = new servicecollection();
// 使用内置的扩展注册 dbcontext 代理服务
//services.addproxydbcontext<testdbcontext>(options =>
//{
//    options
//        .useloggerfactory(loggerfactory)
//        //.enabledetailederrors()
//        //.enablesensitivedatalogging()
//        // .useinmemorydatabase("tests")
//        .usesqlserver(dbconnectionstring)
//        //.addinterceptors(new querywithnolockdbcommandinterceptor())
//        ;
//});

// 使用内置的扩展注册 dbcontextpool 代理服务,只是为了方便使用,只会代理 dbcontext
services.addproxydbcontextpool<testdbcontext>(options =>
{
    options
        .useloggerfactory(loggerfactory)
        //.enabledetailederrors()
        //.enablesensitivedatalogging()
        // .useinmemorydatabase("tests")
        .usesqlserver(dbconnectionstring)
        //.addinterceptors(new querywithnolockdbcommandinterceptor())
        ;
});
// 注册 aop 服务
services.addfluentaspects(options =>
{
    // 配置使用 auditdbcontextinterceptor 拦截 dbcontext 的 savechanges 和 savechangesasync 方法
    options.interceptmethod<dbcontext>(m =>
            m.name == nameof(dbcontext.savechanges)
            || m.name == nameof(dbcontext.savechangesasync))
        .with<auditdbcontextinterceptor>()
        ;
});
// 注册 servicelocator(可选,根据自己需要
dependencyresolver.setdependencyresolver(services);

审计配置

auditconfig.configure(builder =>
{
    builder
        // 配置操作用户获取方式
        .withuseridprovider(environmentaudituseridprovider.instance.value)
        //.withunmodifiedproperty() // 保存未修改的属性,默认只保存发生修改的属性
        // 保存更多属性
        .enrichwithproperty("machinename", environment.machinename)
        .enrichwithproperty(nameof(applicationhelper.applicationname), applicationhelper.applicationname)
        // 保存到自定义的存储
        .withstore<auditfilestore>()
        .withstore<auditfilestore>("logs0.log")
        // 忽略指定实体
        .ignoreentity<auditrecord>()
        // 忽略指定实体的某个属性
        .ignoreproperty<testentity>(t => t.createdat)
        // 忽略所有属性名称为 createdat 的属性
        .ignoreproperty("createdat")
        ;
});

使用示例

dependencyresolver.tryinvokeservice<testdbcontext>(dbcontext =>
{
    dbcontext.database.ensuredeleted();
    dbcontext.database.ensurecreated();
    var testentity = new testentity()
    {
        extra = new { name = "tom" }.tojson(),
        createdat = datetimeoffset.utcnow,
    };
    dbcontext.testentities.add(testentity);
    dbcontext.savechanges();

    testentity.createdat = datetimeoffset.now;
    testentity.extra = new { name = "jerry" }.tojson();
    dbcontext.savechanges();

    dbcontext.remove(testentity);
    dbcontext.savechanges();

    var testentity1 = new testentity()
    {
        extra = new { name = "tom1" }.tojson(),
        createdat = datetimeoffset.utcnow,
    };
    dbcontext.testentities.add(testentity1);
    var testentity2 = new testentity()
    {
        extra = new { name = "tom2" }.tojson(),
        createdat = datetimeoffset.utcnow,
    };
    dbcontext.testentities.add(testentity2);
    dbcontext.savechanges();
});
dependencyresolver.tryinvokeservice<testdbcontext>(dbcontext =>
{
    dbcontext.remove(new testentity()
    {
        id = 2
    });
    dbcontext.savechanges();
});
// disable audit
auditconfig.disableaudit();
// enable audit
// auditconfig.enableaudit();

审计日志输出结果

more

这样以来就不需要修改原有代码了~~,心情大好,哈哈~

如果应用多有多个 dbcontext 有的需要审计,有的不需要审计,则可以在配置的时候指定具体的 dbcontext类型如 testdbcontext,这样就只会启用 testdbcontext 的自动审计,别的 dbcontext 比如 test2dbcontext 就不会自动审计了

reference

  • https://github.com/weihanli/weihanli.entityframework/blob/dev/samples/weihanli.entityframework.core3_0sample/program.cs
  • https://www.nuget.org/packages/weihanli.entityframework/