Задать вопрос
EpeTuK
@EpeTuK
Full-Stack developer

Как реализовать версионность данных на EntityFramework CodeFirst (метод таблиц-версий, подход TPC)?

Появилась задача реализовать в базе данных версионность данных. Была выбрана парадигма создания таблиц версий, то есть на каждую таблицу Entity создается таблица EntityHistory, в которой есть все поля из Entity + DateModification (дата изменения записи), EntityId (ссылка на запись сущности в таблице Entity), UserId (ссылка на пользователя, внесшего изменение), Operation (тип изменения записи - create, update, delete, etc.). Из такой схемы напрашивается применение TPC-подхода: создаю модель Entity, наследую от нее EntityHistory, в DataContext.OnModelCreating() указываю соответствующий маппинг. Но при этом в генерируемой миграции создаются команды на удаление ForeignKey-ев на таблицу Entity из других таблиц, также не создается ForeignKey в таблице EntityHistory на Entity. Что я делаю не так и как это все победить?

Примеры:

EntityHistory.cs:

public class EntityHistory : Entity, IHistory
    {
        public DateTimeOffset DateModification { get; set; } 

        [Required]
        public int IdEntity { get; set; }

        [ForeignKey("IdEntity")]
        public virtual Entity Entity { get; set; }

        public int? IdOperation { get; set; }

        [Required]
        public string IdUser { get; set; }

        [ForeignKey("IdUser")]
        public AspNetUser User { get; set; }

        public EntityHistory(string idUser, int idEntity, int idOperation)
        {
            this.IdUser = idUser;
            this.IdEntity = idEntity;
            this.IdOperation = idOperation;
            this.DateModification = DateTimeOffset.Now;
        }

        private EntityHistory()
        {

        }

    }

Entity.cs:

public class Entity : AttributeAnnotatedValidator, Identified<int>
    {
        [Key]
        public int Id { get; set; }

        [Required(ErrorMessage = "Необходимо указать номер договора!")]
        public string Number { get; set; }

        ...

        [ForeignKey("Contractor")]
        [Required]
        public int ContractorId { get; set; }

        public Organization Contractor { get; set; }

        [ForeignKey("Customer")]
        public int? CustomerId { get; set; }

        public Organization Customer { get; set; }

        public List<EntityHistory> EntityHistory;
    }

DefaultConnection.cs :

public class DefaultConnection : DefaultStorageContext
    {
        public DbSet<AspNetUser> AspNetUsers { get; set; }
        public DbSet<Entity> Entity{ get; set; }
        public DbSet<Contractor> Contractors { get; set; }
        public DbSet<AgreementReason> AgreementReasons { get; set; }
        public DbSet<StateOfAgreement> StateOfAgreements { get; set; }
        public DbSet<AgreementStateFlow> AgreementStateFlow { get; set; }
        public DbSet<EntityHistory> EntityHistory { get; set; }
        public DbSet<Organization> Organizations { get; set; }
        ...

        public DefaultConnection()
        {
            Database.SetInitializer<DefaultConnection>(new DropCreateDatabaseIfModelChanges<DefaultConnection>());
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
            modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
         modelBuilder.Conventions.Remove<OneToOneConstraintIntroductionConvention>();

            modelBuilder.Entity<EntityHistory>().Map(
                m => {
                    m.MapInheritedProperties();
                    m.ToTable("EntityHistory");
                });

            base.OnModelCreating(modelBuilder);
        }
}

неправильно сгенеренная миграция:

public partial class Create_EntityHistory : DbMigration
    {
        public override void Up()
        {
            DropForeignKey("dbo.AgreementManagedObjects", "AgreementId", "dbo.Entity");
            DropForeignKey("dbo.AgreementStateFlows", "AgreementId", "dbo.Entity");
            DropForeignKey("dbo.ExportAgreements", "Agreement_Id", "dbo.Entity");
            DropIndex("dbo.AgreementManagedObjects", new[] { "EntityId" });
            DropIndex("dbo.AgreementStateFlows", new[] { "EntityId" });
            DropIndex("dbo.ExportAgreements", new[] { "Entity_Id" });
            DropPrimaryKey("dbo.Entity");
            CreateTable(
                "dbo.EntityHistory",
                c => new
                    {
                        Id = c.Int(nullable: false),
                        Number = c.String(nullable: false),
                        ...
                        DateModification = c.DateTimeOffset(nullable: false, precision: 7),
                        IdEntity = c.Int(nullable: false),
                        IdOperation = c.Int(),
                        IdUser = c.String(nullable: false, maxLength: 128),
                    })
                .PrimaryKey(t => t.Id)
                .ForeignKey("dbo.Organizations", t => t.ContractorId)
                .ForeignKey("dbo.Organizations", t => t.CustomerId)
                .ForeignKey("dbo.AspNetUsers", t => t.IdUser)
                .Index(t => t.ContractorId)
                .Index(t => t.CustomerId)
                .Index(t => t.IdUser);

            AlterColumn("dbo.Entity", "Id", c => c.Int(nullable: false));
            AddPrimaryKey("dbo.Entity", "Id");            
        }
    }
}
  • Вопрос задан
  • 928 просмотров
Подписаться 3 Оценить Комментировать
Пригласить эксперта
Ответы на вопрос 2
Nipheris
@Nipheris Куратор тега C#
Вы уверены, что надо этим мучать EF? Я думаю, версионирование данных это один из кейсов, когда стоит применить триггеры, а не поднимать версионность на уровень объектной БД.
Ответ написан
yarosroman
@yarosroman Куратор тега C#
C# the best
Косяк EF видимо, у меня в ASP.Net Core (SQLite), EF в миграции сначала удаляет все индексы, потом создает вновь, причем для таблицы которая в модели не изменилась. Правьте руками миграции.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы