EntityFramework Plus의 Audit Auto-Save 기능 을 설정하려고하는데 매우 멍청한 것처럼 보입니다. "SaveChanges & SaveChangesAsync를 재정 의하여 자동으로 저장"경로를 따르고 있지만 현재 사용할 프로젝트가 한동안 실행 중이므로 코드 우선을 사용하려고합니다. 그렇게 말하면 내 DbContext는 다음과 같습니다.
public class CadastralDbContext : DbContext
{
public CadastralDbContext(DbContextOptions<CadastralDbContext> options) : base(options) { }
static CadastralDbContext()
{
AuditManager.DefaultConfiguration.AutoSavePreAction = (context, audit) =>
(context as CadastralDbContext).AuditEntries.AddRange(audit.Entries);
}
public DbSet<AuditEntry> AuditEntries { get; set; }
public DbSet<AuditEntryProperty> AuditEntryProperties { get; set; }
//Ommited my DbSets
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(CadastralDbContext).Assembly);
/*** Ignore these for now ***/
//modelBuilder.Entity<AuditEntry>().Ignore(x => x.Properties);
//modelBuilder.Entity<AuditEntryProperty>().Ignore(x => x.Parent);
}
public override int SaveChanges()
{
var audit = new Audit();
audit.PreSaveChanges(this);
var rowAffecteds = base.SaveChanges();
audit.PostSaveChanges();
if (audit.Configuration.AutoSavePreAction != null)
{
audit.Configuration.AutoSavePreAction(this, audit);
base.SaveChanges();
}
return rowAffecteds;
}
public async Task<int> SaveChangesAsync()
{
return await SaveChangesAsync(CancellationToken.None);
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
var audit = new Audit();
audit.PreSaveChanges(this);
var rowAffecteds = await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
audit.PostSaveChanges();
if (audit.Configuration.AutoSavePreAction != null)
{
audit.Configuration.AutoSavePreAction(this, audit);
await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
return rowAffecteds;
}
}
}
기본적으로 튜토리얼 자체는 프레임 워크 자체의 클래스 인 DbSet<AuditEntry>
및 DbSet<AuditEntryProperty>
를 추가하여 말합니다. 메타 데이터를 검사하면 다음과 같은 이점이 있습니다.
//
// Summary:
// An audit entry.
public class AuditEntry
{
//
// Summary:
// Gets or sets the object state entry.
[NotMapped]
public object Entity;
//
// Summary:
// Gets or sets the object state entry.
[NotMapped]
public EntityEntry Entry;
//
// Summary:
// Gets or sets the parent.
public Audit Parent;
public AuditEntry();
//
// Summary:
// Gets or sets the identifier of the audit entry.
[Column(Order = 0)]
public int AuditEntryID { get; set; }
//
// Summary:
// Gets or sets who created this object.
[Column(Order = 5)]
[MaxLength(255)]
public string CreatedBy { get; set; }
//
// Summary:
// Gets or sets the the date of the changes.
[Column(Order = 6)]
public DateTime CreatedDate { get; set; }
//
// Summary:
// Gets or sets the name of the entity set.
[Column(Order = 1)]
[MaxLength(255)]
public string EntitySetName { get; set; }
//
// Summary:
// Gets or sets the name of the entity type.
[Column(Order = 2)]
[MaxLength(255)]
public string EntityTypeName { get; set; }
//
// Summary:
// Gets or sets the properties.
public List<AuditEntryProperty> Properties { get; set; }
//
// Summary:
// Gets or sets the entry state.
[Column(Order = 3)]
public AuditEntryState State { get; set; }
//
// Summary:
// Gets or sets the name of the entry state.
[Column(Order = 4)]
[MaxLength(255)]
public string StateName { get; set; }
}
과
//
// Summary:
// An audit entry property.
public class AuditEntryProperty
{
//
// Summary:
// Gets or sets the new value audited.
[NotMapped]
public PropertyEntry PropertyEntry;
public object NewValue;
public object OldValue;
public AuditEntryProperty();
//
// Summary:
// Gets or sets the name of the property internally.
[NotMapped]
public string InternalPropertyName { get; set; }
//
// Summary:
// Gets or sets a value indicating whether OldValue and NewValue is set.
[NotMapped]
public bool IsValueSet { get; set; }
//
// Summary:
// Gets or sets the name of the relation audited.
[Column(Order = 2)]
[MaxLength(255)]
public string RelationName { get; set; }
//
// Summary:
// Gets or sets the name of the property audited.
[Column(Order = 3)]
[MaxLength(255)]
public string PropertyName { get; set; }
//
// Summary:
// Gets or sets the parent.
public AuditEntry Parent { get; set; }
//
// Summary:
// Gets or sets the identifier of the audit entry property.
[Column(Order = 0)]
public int AuditEntryPropertyID { get; set; }
//
// Summary:
// Gets or sets the new value audited formatted.
[Column("NewValue", Order = 5)]
public string NewValueFormatted { get; set; }
//
// Summary:
// Gets or sets the identifier of the audit entry.
[Column(Order = 1)]
public int AuditEntryID { get; set; }
//
// Summary:
// Gets or sets the old value audited formatted.
[Column("OldValue", Order = 4)]
public string OldValueFormatted { get; set; }
}
다음 두 속성에 대해 충분히 저장하는 것이 좋습니다. public List<AuditEntryProperty> Properties { get; set; }
및 public AuditEntry Parent { get; set; }
. virtual
표시되어 있지 않으므로 마이그레이션 추가가 실패합니다. 나는 테이블을 생성 할 수 있는지 확인하기 위해 해결 방법을 시도했지만 실제로 성공했습니다 (이 라인은 이전에 주석 처리되었습니다).
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//...
modelBuilder.Entity<AuditEntry>().Ignore(x => x.Properties);
modelBuilder.Entity<AuditEntryProperty>().Ignore(x => x.Parent);
}
그것은 수동으로해야한다는 표시가 없기 때문에 두 테이블이 가지고있는 PrimaryKey-ForeignKey 관계를 비활성화하는 것처럼 보입니다. 나는 스크립트를 실행하려고 시도했지만 결과는 치명적이었습니다.
CREATE INDEX [IX_AuditEntryID] ON [dbo].[AuditEntryProperties]([AuditEntryID])
GO
ALTER TABLE [dbo].[AuditEntryProperties]
ADD CONSTRAINT [FK_dbo.AuditEntryProperties_dbo.AuditEntries_AuditEntryID]
FOREIGN KEY ([AuditEntryID])
REFERENCES [dbo].[AuditEntries] ([AuditEntryID])
ON DELETE CASCADE
GO
삽입시 다음과 같은 SQL 오류가 발생했습니다. String or binary data would be truncated
. 따라서 사용자가 삽입, 업데이트 또는 삭제 작업을 요청할 때마다 AuditEntry 테이블 (테이블과 같은 데이터를 보유)에 레코드를 저장하므로 프레임 워크에 "50 % 출력"이있는 이전 상태로 롤백했습니다. AuditEntryProperties (새로운 값, 이전 값, 열)에는 아무것도 유지되지 않으며 이러한 속성 이외의 다른 속성은이 모든 원인의 원인이라고 생각할 수 없습니다.
AuditEntry와 AuditEntryProperties를 모두 무시할 수 있다고 생각했지만, 그것은 큰 어리석은 해결 방법처럼 들립니다. 저는 DB 전문가가 아닙니다. 여기서 무엇을 놓치고 있습니까?
편집 : 마이그레이션 코드 추가를 잊었습니다.
migrationBuilder.CreateTable(
name: "AuditEntries",
columns: table => new
{
AuditEntryID = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
CreatedBy = table.Column<string>(maxLength: 255, nullable: true),
CreatedDate = table.Column<DateTime>(nullable: false),
EntitySetName = table.Column<string>(maxLength: 255, nullable: true),
EntityTypeName = table.Column<string>(maxLength: 255, nullable: true),
State = table.Column<int>(nullable: false),
StateName = table.Column<string>(maxLength: 255, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AuditEntries", x => x.AuditEntryID);
});
migrationBuilder.CreateTable(
name: "AuditEntryProperties",
columns: table => new
{
AuditEntryPropertyID = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
AuditEntryID = table.Column<int>(nullable: false),
PropertyName = table.Column<string>(maxLength: 255, nullable: true),
RelationName = table.Column<string>(maxLength: 255, nullable: true),
NewValue = table.Column<string>(nullable: true),
OldValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AuditEntryProperties", x => x.AuditEntryPropertyID);
});
Fluent API를 사용하여 FK를 추가하려면 2를 편집하십시오 .
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(CadastralDbContext).Assembly);
modelBuilder.Entity<AuditEntryProperty>().HasOne<AuditEntry>(prop => prop.Parent).WithMany(a => a.Properties).HasForeignKey(prop => prop.AuditEntryID);
}
해당 속성이 가상이 아니므로 마이그레이션을 계속 수행 할 수 없습니다.
EF Plus 이슈 트래커 에 이슈를 만들었습니다
여기서 시도해 볼 수있는 프로젝트를 찾을 수 있습니다. 스택 오버플로는 이러한 종류의 문제를위한 플랫폼이 아니기 때문에 이슈 트래커에 대한 토론을 계속하는 것이 좋습니다.