Как настроить таргетинг на другую базу данных с помощью Audit.Net - Audit.EntityFramework.Core

Я пытаюсь реализовать пакет Audit.EntityFramework.Core из Audit.Net репозиторий, но я столкнулся с некоторыми трудностями. Я не могу сохранить изменения или настроить таргетинг на другую базу данных. Я изменил свои функции SaveChanges и SaveChangesAsync для вызова функций сохранения класса Audit.Net DbContextHelper, но мне кое-что не хватает.

Есть ли способ сделать следующее?

  1. Настройте таргетинг на другую базу данных для хранения данных аудита, используя DbContext аудит, наследуемый от DbContext, который я пытаюсь проверить?
    public class MyDbContext : DbContext {} //Types defined here
    public class AuditDbContext : MyDbContext {} //This context stores audit data into a different DB
    
  2. Не требуется сопоставление между типом и его проверяемым типом при настройке глобального соединения? (Я стараюсь избегать явного вызова AuditTypeMapper для каждого типа с моделью, которая в настоящее время претерпевает много изменений).
    //MyDbContext has different connection string than AuditDbContext
    Audit.Core.Configuration.Setup()
    .UseEntityFramework(x => x
        .UseDbContext<AuditDbContext>());
    

Я пробовал код, похожий на следующий, но получаю ошибки выполнения на SaveChanges, которые указывают на то, что модель не настроена. Добавление миграции для AuditDbContext не помогло.


person Josh    schedule 04.09.2019    source источник
comment
Вы делаете это правильно, но вам необходимо определить соответствие между вашими типами сущностей и типами аудиторских сущностей. Как это будет? Как выглядят ваши таблицы аудита?   -  person thepirat000    schedule 04.09.2019
comment
Спасибо за ваш ответ. Я пытался создать таблицу аудита для каждого типа, надеюсь, используя наследование для создания набора таблиц аудита на основе исходных объектов. Но я открыт для использования единой таблицы аудита, если другой способ потребует слишком большого обслуживания.   -  person Josh    schedule 04.09.2019
comment
Пожалуйста, проверьте примеры конфигурации EF Provider.. У вас есть много вариантов. Определены ли ваши объекты и объекты аудита в одной сборке и пространстве имен?   -  person thepirat000    schedule 04.09.2019
comment
Я определил объекты аудита в том же пространстве имен, что и базовые объекты. Я снова просмотрю те документы, на которые вы ссылались, и опубликую, когда получу удовлетворительное решение. Спасибо.   -  person Josh    schedule 05.09.2019


Ответы (1)


Я понял, что пытался сделать.

Моими дизайнерскими целями были:

  1. Хранить записи аудита в другой базе данных
  2. Имейте таблицу аудита для каждого типа, которая соответствует проверяемому типу (с дополнительными полями аудита)
  3. Не требуют содержания отдельных аудиторских организаций. Изменения между операционной БД и аудиторской БД должны быть плавными.

Я обнаружил, что не работают:

  1. Создание контрольного DbContext, унаследованного от моего рабочего DbContext, не работает, потому что отношения, DBSets и идентификаторы нельзя обрабатывать таким же образом в контрольной БД.
  2. Динамическое создание типов с использованием отражения над операционными типами с помощью TypeBuilder не работает, потому что Audit.Net преобразует объекты между их операционными типами и типами аудита, а преобразование из типа CLR в динамически создаваемый тип не выполняется.
  3. Смешивание типов бетона и теневых типов EF Core не работает.

Предпринятые шаги

  1. Настроить глобальный аудит (в основном коде настройки)

    //Global setup of Auditing
    var auditDbCtxOptions = new DbContextOptionsBuilder<MyAuditDbContext>()
        .UseSqlServer(options.AuditDbConnectionString)
        .Options;
    
    Audit.Core.Configuration.Setup()
        .UseEntityFramework(x => x
            .UseDbContext<MyAuditDbContext>(auditDbCtxOptions)
            .AuditTypeNameMapper(typeName => 
            {
                return typeName;
            })
            .AuditEntityAction<AuditInfo>((ev, ent, auditEntity) =>
            {
                auditEntity.DatabaseAction = ent.Action;
            }));
    
  2. Если бы мои проверенные модели унаследовали от базового класса AuditInfo

    public abstract class AuditInfo
    {
        public DateTime Created { get; set; }
        public DateTime? Updated { get; set; }
        public string CreatedBy { get; set; }
        public string UpdatedBy { get; set; }
    
        [NotMapped] //This is not mapped on the operational DB
        public string DatabaseAction { get; set; }
    }
    
  3. Создал схему аудита на основе отражения с использованием новых DbContext и OnModelCreating

    public class MyAuditContext : DbContext
    {
        public MyAuditContext(DbContextOptions<MyAuditContext> options) : base(options)
        {
    
        }
    
        private readonly Type[] AllowedTypes = new Type[] 
        { 
            typeof(bool),
            typeof(int),
            typeof(decimal),
            typeof(string),
            typeof(DateTime),
        };
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            Console.WriteLine($"Generating dynamic audit model");
    
            //Go through each of the types in Hsa.Engine.Data.Models
            var asm = Assembly.GetExecutingAssembly();
            var modelTypes = asm.GetTypes()
                .Where(type => type.Namespace == "My.Data.Models.Namespace");
    
            //Create an entity For each type get all the properties on the model
            foreach(var model in modelTypes.Where(t => t.IsClass && !t.IsAbstract && t.BaseType == typeof(AuditInfo)))
            {
                Console.WriteLine($"Creating entity for {model.Name}");
    
                var table = modelBuilder.Entity(model, entity => 
                {
                    //Remove all types from base model, otherwise we get a bunch of noise about foreign keys, etc.
                    foreach(var prop in model.GetProperties())
                    {
                        entity.Ignore(prop.Name);
                    }
    
                    foreach(var prop in model.GetProperties().Where(p => AllowedTypes.Any(t => p.PropertyType.IsAssignableFrom(t))))
                    {
                        Console.WriteLine($"   Adding field: {prop.Name} - Type: {prop.PropertyType.Name}");
                        //Create a typed field for each property, not including ID or foreign key annotations (do include field lengths)
    
                        var dbField = entity.Property(prop.PropertyType, prop.Name);
    
                        if(prop.PropertyType.IsEnum)
                        {
                            dbField.HasConversion<string>();
                        }
    
                        if(dbField.Metadata.IsPrimaryKey())
                        {
                            dbField.ValueGeneratedNever(); //Removes existing model primary keys for the audit DB
                        }
                    }
    
                    //Add audit properties
                    entity.Property<int>("AuditId").IsRequired().UseSqlServerIdentityColumn();
                    entity.Property<DateTime>("AuditDate").HasDefaultValueSql("getdate()");
                    entity.Property<string>("DatabaseAction"); //included on AuditInfo but NotMapped to avoid putting it on the main DB. Added here to ensure it makes it into the audit DB
    
                    entity.HasKey("AuditId");
                    entity.HasIndex("Id");
                    entity.ToTable("Audit_" + model.Name);
                });
            }
    
            base.OnModelCreating(modelBuilder);
        }
    }
    
    
  4. Создал миграцию как для основной БД, так и для БД аудита.

Некоторым людям может не понадобиться переходить на эти уровни, но я хотел бы поделиться на случай, если кому-то понадобится что-то подобное при использовании Audit.Net.

person Josh    schedule 06.09.2019
comment
Привет, спасибо за интересные предложения. Я хотел бы попробовать этот подход с приложением .NET Core, но кое-что я не совсем понимаю. Если я не ошибаюсь, ваши модели наследуются от базового класса AuditInfo, верно? Но вы явно не кодируете соответствующие модели аудита, потому что вы используете отражение на проверенных моделях в MyAuditContext.OnModelCreating. Это верно? Что непонятно: а как насчет миграций? Как вам удается распространить изменения, происходящие в проверенных моделях, на AuditDbContext? Спасибо, Маттео - person m.phobos; 22.04.2020