thepirat000 / Audit.NET

An extensible framework to audit executing operations in .NET and .NET Core.
MIT License
2.3k stars 324 forks source link

Where to put Audit.Core.Configuration.Setup() #378

Closed mrlife closed 3 years ago

mrlife commented 3 years ago

Where can/should this get called? I could not find in the readme the location to call Audit.Core.Configuration.Setup().

I first tried calling Audit.Core.Configuration.Setup() in the database context OnModelCreating(), and the audit table was created. However, there were some null errors when trying to save to the audit table.

Second, I tried tried calling Audit.Core.Configuration.Setup() in the database context OnConfiguring(), since I saw it in another issue. That resulted in the audit table being deleted, so that doesn't seem like the right place.

Your configuration call to Audit.Core.Configuration.Setup() should be done only once, at the startup of your application.

Originally posted by @thepirat000 in https://github.com/thepirat000/Audit.NET/issues/247#issuecomment-539686730

thepirat000 commented 3 years ago

What kind of application? If it's asp.net you could do it on the strartup. Otherwise in any place that is run before any audited action takes place, and that runs only once of course...

On the static constructor of your dbcontext is another choice, that will run the first time a dbcontext is created and only once.

mrlife commented 3 years ago

@thepirat000 I'm on ASP.NET Core 5.0.4. I have an EF query that needs to run to look up the user information, so the static constructor or startup.cs aren't great options in this case. However, as a test, I removed the EF query and placed the Setup() call in the static constructor, and the audit table e.g. Audit_Order is not created in the database.

I have Audit.EntityFramework.Core v16.5.4 installed on a .NET/EF Core 5.0.4 project. I am inheriting Audit.EntityFramework.AuditDbContext from the database context. And I have the below Setup() call. Everything compiles fine, but the Audit_MyModel table isn't created when I run a migration. Any thoughts on what I'm doing wrong?

static MyContext()
{
    Audit.Core.Configuration.Setup()
        .UseEntityFramework(ef => ef
            .AuditTypeExplicitMapper(m => m
                .Map<MyModel, Audit_MyModel>()
                .AuditEntityAction<AuditBase>((evt, entry, auditEntity) =>
                {
                    auditEntity.AuditDate = DateTime.Now;
                    auditEntity.AuditAction = entry.Action; // Insert, Update, Delete
                })
            )
        );
}
thepirat000 commented 3 years ago

Audit table creation is up to you, the library will not create the tables.

Why do you say the startup is not a great option to put the configuration?

mrlife commented 3 years ago

Audit table creation is up to you, the library will not create the tables.

Do users of the library typically do this with DbSets?

Why do you say the startup is not a great option to put the configuration?

I would need to access user claims through DI and to look up information about the user, i.e.

Audit.Core.Configuration.Setup()
    .UseEntityFramework(ef => ef
        .AuditTypeExplicitMapper(m => m
            .Map<Order, Audit_Order>()
            .Map<OrderItem, Audit_OrderItem>()
            .AuditEntityAction<IAudit>((evt, entry, auditEntity) =>
            {
                // access to claims through DI and to query a DbSet here
            })
        )
    );
thepirat000 commented 3 years ago

I don't know what the users of the library usually do. There are many data providers for the audit output saving, and there are many use cases for the EntityFramework data provider.

For the DI part, you should be able to access any of your registered services, or the IServiceProvider as a parameter on your Startup.Configure() method and then use it on the setup delegate, for example:

public void Configure(IApplicationBuilder app, IServiceProvider svcProvider)
{
    Audit.Core.Configuration.Setup()
        .UseEntityFramework(ef => ef
            .AuditTypeExplicitMapper(m => m
                .Map<Order, Audit_Order>()
                .Map<OrderItem, Audit_OrderItem>()
                .AuditEntityAction<IAudit>((evt, entry, auditEntity) =>
                {
                    var svc = svcProvider.GetRequiredService<IYourService>();
                    // ...
                })
            )
        );
    // ...
}

or if it's a singleton, you could just:

public void Configure(IApplicationBuilder app, IYourService svc)
{
    Audit.Core.Configuration.Setup()
        .UseEntityFramework(ef => ef
            .AuditTypeExplicitMapper(m => m
                .Map<Order, Audit_Order>()
                .Map<OrderItem, Audit_OrderItem>()
                .AuditEntityAction<IAudit>((evt, entry, auditEntity) =>
                {
                    // use svc here
                })
            )
        );
    // ...
}

Is the DbSet you need to access on the same DbContext that is being audited? If so, you can access it with GetDbContext() method on the EF event:

using Audit.EntityFramework;
//...
Audit.Core.Configuration.Setup()
      .UseEntityFramework(ef => ef
          .AuditTypeExplicitMapper(m => m
              .Map<Order, Audit_Order>()
              .Map<OrderItem, Audit_OrderItem>()
              .AuditEntityAction<IAudit>((evt, entry, auditEntity) =>
              {
                  var svc = svcProvider.GetRequiredService<IYourService>();
                  var dbContext = evt.GetEntityFrameworkEvent().GetDbContext();
                  // ...
              })
          )
      );
mrlife commented 3 years ago

To help illustrate, here is a sample app. It's the official Blazor with EF Core sample with the following modifications to add in this library.

I added the package to the .csproj, added AuditDbContext for inheritance, created Audit_Contact, and added the Setup() call to the context's static constructor.

After running the app and deleting one of the records, this is the error:

UNIQUE constraint failed: Audit_Contacts.Id

thepirat000 commented 3 years ago

You should remove any unique index based on the audited table ID on the audit table, since the audit table will potentially contain multiple rows for the same ID. I guess that's the problem

Or, if the Audit_Contact.Id is not mean to be mapped to the Contact.Id, then you should set the IgnoreMatchedProperties config to true

https://github.com/thepirat000/Audit.NET/blob/master/src/Audit.EntityFramework/README.md#ef-provider-options

mrlife commented 3 years ago

Thank you, I fixed by adding an explicit key property to the Audit_* model classes so that the Id matched up with the audited model and EF Core uses the explicit key property for its tracking purposes.

e.g.

public class MyModel
{
    public int Id { get; set; }
    public string Property { get; set; }
}

public class Audit_MyModel : IAudit
{
    [Key]
    public int AuditId { get; set; }
    public int Id { get; set; }
    public string Property { get; set; }

    public string AuditAction { get; set; }
    public DateTime AuditDate { get; set; }
}

I think your library is wonderful and appreciate your work to create and maintain it.