question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Multiple DbContext and multiple AuditTypeExplicitMapper doesn't work

See original GitHub issue

Hi, I have an application where I have many DB contexts but one database on the server. Generally, all works fine until I tried adding a new audit for the second context. It looks like mapping working for context which was created last. I tried the inheritance approach and without inheritance, with audit logic inside a base context (all contexts inherit from it) or specific context and any approach doesn’t work.

Sorry for the code/text style. I don’t know why but I cant c&p the code correctly to the code boxes.

Code: Firs context:

internal class SubmissionsContext : BaseDbContext{
...
public SubmissionsContext(DbContextOptions<SubmissionsContext> options) : base(options)
        {
            Audit.EntityFramework.Configuration.Setup().ForContext<SubmissionsContext>(config => config
                    .IncludeEntityObjects()
                    .AuditEventType("SubmissionsContext"))
                .UseOptIn()
                .Include<Submission>()
                .Include<Milestone>()
                .Include<UserAssignment>()
                .Include<Timeline>();
            
            AuditConfiguration.Configure(options);
        }
    ...
}

And AuditConfiguration

internal static class AuditConfiguration
    {
        public static void Configure(DbContextOptions<SubmissionsContext> dbContextOptions, SubmissionsContext context)
        {
            Configuration.Setup()
                .UseEntityFramework(ef => ef.UseDbContext<SubmissionsContext>(dbContextOptions)
                    .AuditTypeExplicitMapper(MapAuditModels));
        }

        private static void MapAuditModels(IAuditEntityMapping m)
        {
            m
                .Map<Submission, SubmissionAudit>((submission, submissionAudit) =>
                {
                    submissionAudit.AssemblyQualifiedName =
                        typeof(Submission).AssemblyQualifiedName.ToShortAssemblyQualifiedName();
                    submissionAudit.Title = "Submission";
                })
               (...)
                .AuditEntityAction((evt, entry, auditEntity) =>
                {
                    var a = (dynamic) auditEntity;
                    a.TablePrimaryKey = (Guid)entry.PrimaryKey["Id"];
                    a.Changes = JsonConvert.SerializeObject(entry.Changes);
                    a.ColumnValues = JsonConvert.SerializeObject(entry.ColumnValues);
                    a.AuditDate = DateTimeOffset.Now;
                    a.AuditAction = entry.Action;
                    a.AuditUserId =
                        Guid.TryParse(evt.CustomFields.SingleOrDefault(x => x.Key == "UserId").Value?.ToString(),
                            out var userId)
                            ? userId
                            : Guid.Empty;
                });
        }

Second context:

    internal class DeliverablesContext : BaseDbContext
    {
        ....
        public DeliverablesContext(DbContextOptions<DeliverablesContext> options) : base(options)
        {
            Audit.EntityFramework.Configuration.Setup().ForContext<DeliverablesContext>(config => config
                    .IncludeEntityObjects()
                    .AuditEventType("DeliverablesContext"))
                .UseOptIn()
                .Include<Deliverable>()
                .Include<Repository>()
                .Include<RepositoryFile>();

            AuditConfiguration.Configure(options);
        }
    The audit config for this is similar to the first but includes other entities. 

Could you tell me if it’s some bug or maybe I am doing something wrong? I don’t have idea what can be wrong and it looks like some bug for me.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
Cachorro93commented, Aug 13, 2021

It works how I expected 😉 A lot of thanks to you! You’re master 😉 Maybe it will be a good idea to add some API for this to the future version?

1reaction
thepirat000commented, Aug 12, 2021

In that case, maybe you can implement a custom multiple dataprovider that maintain a list of different EntityFrameworkDataProvider instances that will be called depending on some condition. You should only override two methods: ReplaceEvent and ReplaceEventAsync.

For example:

public class MultiEntityFrameworkDataProvider : AuditDataProvider
{
    // Store a list of Condition->DataProvider
    private List<ValueTuple<Func<AuditEvent, bool>, EntityFrameworkDataProvider>> _providers = new();
    // Adds a new data provider to the list, depending on the DbContext type T
    public void AttachDataProvider<T>(EntityFrameworkDataProvider dataProvider)
        where T : DbContext
    {
        Func<AuditEvent, bool> condition = ev => ev.GetEntityFrameworkEvent()?.GetDbContext() is T;
        AttachDataProvider(condition, dataProvider);
    }

    public void AttachDataProvider(Func<AuditEvent, bool> condition, EntityFrameworkDataProvider dataProvider)
    {
        _providers.Add((condition, dataProvider));
    }

    public override object InsertEvent(AuditEvent auditEvent)
    {
        foreach ((Func<AuditEvent, bool> condition, EntityFrameworkDataProvider dataProvider) in _providers)
        {
            if (condition.Invoke(auditEvent))
            {
                dataProvider.InsertEvent(auditEvent);
                break;
            }
        }
        return null;
    }

    public async override Task<object> InsertEventAsync(AuditEvent auditEvent)
    {
        foreach ((Func<AuditEvent, bool> condition, EntityFrameworkDataProvider dataProvider) in _providers)
        {
            if (condition.Invoke(auditEvent))
            {
                await dataProvider.InsertEventAsync(auditEvent);
                break;
            }
        }
        return null;
    }
}

Then you can setup on your startup, just a new instance of the MultiEntityFrameworkDataProvider:

Audit.Core.Configuration.Setup()
    .UseCustomProvider(new MultiEntityFrameworkDataProvider());
// Or just:
// Audit.Core.Configuration.DataProvider = new MultiEntityFrameworkDataProvider();

And then you could have the configuration separated on each DbContext, similar to your current approach, i.e.:

// Namespace: Deliverables
public static class AuditConfiguration
{
    public static void Configure(DbContextOptions<DeliverablesContext> dbContextOptions)
    {
        var dp = Audit.Core.Configuration.DataProvider as MultiEntityFrameworkDataProvider;
        dp.AttachDataProvider<DeliverablesContext>(new EntityFrameworkDataProvider(ef => ef
            .UseDbContext<DeliverablesContext>(dbContextOptions)
            .AuditTypeExplicitMapper(MapAuditModels)));
    }

    private static void MapAuditModels(IAuditEntityMapping m)
    {
        m
            .Map<Deliverable, DeliverableAudit>()
            ...;
    }
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

How do I target another database with Audit.Net
I figured out what I was trying to do. My design goals were: Store audit records in a different database; Have an audit...
Read more >
Managing DbContext the right way with Entity Framework 6
Your DbContext management strategy should support multiple DbContext-derived types. If your application needs to connect to multiple databases ( ...
Read more >
Entity Types - EF Core
How to configure and map entity types using Entity Framework Core. ... to have the same entity type mapped in multiple DbContext types....
Read more >
How do i handle multiple DbContexts with relations to each ...
I'll go with having both of them in the same context (different schemas) to solve the navigation/foreign key problems. If the current design ......
Read more >
Should I separate my DbContext into different classes?
I am using Entity Framework to interact with the database file. ... Should I be separating the data into many different DbContext classes ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found