EF 5を使用してMVC 4アプリケーションを構築しています。エンドユーザーが行った変更を記録する、つまり監査証跡を行う必要があります。
私はこの質問を数回しましたが、以前は本当に満足のいく答えを得ていませんでした。だから私はどこかに行くことを望んでより多くの詳細を追加しています。
現在、私は複数のリポジトリを持っています
すなわち
public class AuditZoneRepository : IAuditZoneRepository
{
private AISDbContext context = new AISDbContext();
public int Save(AuditZone model, ModelStateDictionary modelState)
{
if (model.Id == 0)
{
context.AuditZones.Add(model);
}
else
{
var recordToUpdate = context.AuditZones.FirstOrDefault(x => x.Id == model.Id);
if (recordToUpdate != null)
{
recordToUpdate.Description = model.Description;
recordToUpdate.Valid = model.Valid;
recordToUpdate.ModifiedDate = DateTime.Now;
}
}
try
{
context.SaveChanges();
return 1;
}
catch (Exception ex)
{
modelState.AddModelError("", "Database error has occured. Please try again later");
return -1;
}
}
}
public class PostcodesRepository : IPostcodesRepository
{
private AISDbContext context = new AISDbContext();
public int Save(Postcodes model, ModelStateDictionary modelState)
{
if (model.Id == 0)
{
context.Postcodes.Add(model);
}
else
{
var recordToUpdate = context.Postcodes.FirstOrDefault(x => x.Id == model.Id);
if (recordToUpdate != null)
{
recordToUpdate.Suburb = model.Suburb;
recordToUpdate.State = model.State;
recordToUpdate.Postcode = model.Postcode;
recordToUpdate.AuditZoneId = model.AuditZoneId;
recordToUpdate.ModifiedDate = DateTime.Now;
}
}
try
{
context.SaveChanges();
return 1;
}
catch (Exception ex)
{
modelState.AddModelError("", "Database error has occured. Please try again later");
return -1;
}
}
}
今、私はコードを追加して、保存の試行で追加する必要がある変更があるかどうかを確認することを知っています。 context.SaveChanges()の前。
しかし、現在、10のリポジトリがあります。本当に10か所にコードを追加したくありません。このコードはまったく同じことをするので。リポジトリが継承するベースクラスをどうにかして欲しい。
何か助け?サンプルコードはありますか?ポインタはありますか?
いただければ幸いです。他の人がこれをやったことは確かだ
私は自分のキー、関係、テーブルをマッピングしています
public class AuditZoneMap : EntityTypeConfiguration<AuditZone>
{
public AuditZoneMap()
{
// Primary Key
HasKey(t => t.Id);
// Properties
Property(t => t.Description)
.HasMaxLength(100);
// Table & Column Mappings
ToTable("AuditZone");
Property(t => t.Id).HasColumnName("Id");
Property(t => t.Description).HasColumnName("Description");
Property(t => t.Valid).HasColumnName("Valid");
Property(t => t.CreatedDate).HasColumnName("CreatedDate");
Property(t => t.CreatedBy).HasColumnName("CreatedBy");
Property(t => t.ModifiedDate).HasColumnName("ModifiedDate");
Property(t => t.ModifiedBy).HasColumnName("ModifiedBy");
// Relationships
HasOptional(t => t.CreatedByUser)
.WithMany(t => t.CreatedByAuditZone)
.HasForeignKey(d => d.CreatedBy);
HasOptional(t => t.ModifiedByUser)
.WithMany(t => t.ModifiedByAuditZone)
.HasForeignKey(d => d.ModifiedBy);
}
}
EFでChangeTrackerプロパティを使用することをお勧めします。
DBContext.cs内には次のものがあります。
public class DBContext : DbContext
{
public DBContext () : base("DatabaseName")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
public DbSet<YourPocoModelNameHere > YourPocoModelNameHere { get; set; }
// This is overridden to prevent someone from calling SaveChanges without specifying the user making the change
public override int SaveChanges()
{
throw new InvalidOperationException("User ID must be provided");
}
public int SaveChanges(int userId)
{
// Get all Added/Deleted/Modified entities (not Unmodified or Detached)
foreach (var ent in this.ChangeTracker.Entries().Where(p => p.State == System.Data.EntityState.Added || p.State == System.Data.EntityState.Deleted || p.State == System.Data.EntityState.Modified))
{
// For each changed record, get the audit record entries and add them
foreach (AuditLog x in GetAuditRecordsForChange(ent, userId))
{
this.AuditLogs.Add(x);
}
}
// Call the original SaveChanges(), which will save both the changes made and the audit records
return base.SaveChanges();
}
private List<AuditLog> GetAuditRecordsForChange(DbEntityEntry dbEntry, int userId)
{
List<AuditLog> result = new List<AuditLog>();
DateTime changeTime = DateTime.UtcNow;
// Get the Table() attribute, if one exists
//TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), false).SingleOrDefault() as TableAttribute;
TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), true).SingleOrDefault() as TableAttribute;
// Get table name (if it has a Table attribute, use that, otherwise get the pluralized name)
string tableName = tableAttr != null ? tableAttr.Name : dbEntry.Entity.GetType().Name;
// Get primary key value (If you have more than one key column, this will need to be adjusted)
var keyNames = dbEntry.Entity.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Count() > 0).ToList();
string keyName = keyNames[0].Name; //dbEntry.Entity.GetType().GetProperties().Single(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Count() > 0).Name;
if (dbEntry.State == System.Data.EntityState.Added)
{
// For Inserts, just add the whole record
// If the entity implements IDescribableEntity, use the description from Describe(), otherwise use ToString()
foreach (string propertyName in dbEntry.CurrentValues.PropertyNames)
{
result.Add(new AuditLog()
{
AuditLogId = Guid.NewGuid(),
UserId = userId,
EventDateUTC = changeTime,
EventType = "A", // Added
TableName = tableName,
RecordId = dbEntry.CurrentValues.GetValue<object>(keyName).ToString(),
ColumnName = propertyName,
NewValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null ? null : dbEntry.CurrentValues.GetValue<object>(propertyName).ToString()
}
);
}
}
else if (dbEntry.State == System.Data.EntityState.Deleted)
{
// Same with deletes, do the whole record, and use either the description from Describe() or ToString()
result.Add(new AuditLog()
{
AuditLogId = Guid.NewGuid(),
UserId = userId,
EventDateUTC = changeTime,
EventType = "D", // Deleted
TableName = tableName,
RecordId = dbEntry.OriginalValues.GetValue<object>(keyName).ToString(),
ColumnName = "*ALL",
NewValue = (dbEntry.OriginalValues.ToObject() is IDescribableEntity) ? (dbEntry.OriginalValues.ToObject() as IDescribableEntity).Describe() : dbEntry.OriginalValues.ToObject().ToString()
}
);
}
else if (dbEntry.State == System.Data.EntityState.Modified)
{
foreach (string propertyName in dbEntry.OriginalValues.PropertyNames)
{
// For updates, we only want to capture the columns that actually changed
if (!object.Equals(dbEntry.OriginalValues.GetValue<object>(propertyName), dbEntry.CurrentValues.GetValue<object>(propertyName)))
{
result.Add(new AuditLog()
{
AuditLogId = Guid.NewGuid(),
UserId = userId,
EventDateUTC = changeTime,
EventType = "M", // Modified
TableName = tableName,
RecordId = dbEntry.OriginalValues.GetValue<object>(keyName).ToString(),
ColumnName = propertyName,
OriginalValue = dbEntry.OriginalValues.GetValue<object>(propertyName) == null ? null : dbEntry.OriginalValues.GetValue<object>(propertyName).ToString(),
NewValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null ? null : dbEntry.CurrentValues.GetValue<object>(propertyName).ToString()
}
);
}
}
}
// Otherwise, don't do anything, we don't care about Unchanged or Detached entities
return result;
}
}
これは、DBで次のテーブルを使用します。
USE [databasename]
GO
/****** Object: Table [dbo].[auditlog] Script Date: 06/01/2014 05:56:49 p. m. ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[auditlog](
[auditlogid] [uniqueidentifier] NOT NULL,
[userid] [int] NOT NULL,
[eventdateutc] [datetime] NOT NULL,
[eventtype] [char](1) NOT NULL,
[tablename] [nvarchar](100) NOT NULL,
[recordid] [nvarchar](100) NOT NULL,
[columnname] [nvarchar](100) NOT NULL,
[originalvalue] [nvarchar](max) NULL,
[newvalue] [nvarchar](max) NULL,
CONSTRAINT [PK_AuditLog] PRIMARY KEY NONCLUSTERED
(
[auditlogid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[auditlog] WITH CHECK ADD CONSTRAINT [FK_auditlog_users] FOREIGN KEY([userid])
REFERENCES [dbo].[users] ([userid])
GO
ALTER TABLE [dbo].[auditlog] CHECK CONSTRAINT [FK_auditlog_users]
GO
これをすべて設定したら、dbContext.SaveChanges(ここではuserId)を呼び出すだけです。
これがあなたのために働くことを願っています...私は私のすべてのアプリケーションでそれを使用し、うまくいきます!
楽しめ。
完全なコードはここにあります: https://jmdority.wordpress.com/2011/07/20/using-entity-framework-4-1-dbcontext-change-tracking-for-audit-logging/
このNuGetパッケージを見つけて( TrackerEnabledDbContext )、次の4つの手順を実行しました。
インストールパッケージTrackerEnabledDbContext
TrackerEnabledDbContext名前空間のTrackerContextからDbContextを継承する
_public class ApplicationDbContext : TrackerContext
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
_
移行を追加し、データベースを更新します。変更を記録するために2つの新しいテーブルが作成されました(AuditLogおよびAuditLogDetails)。
追跡するテーブルを決定し、_[TrackChanges]
_属性をクラスに適用します。特定の列の追跡をスキップする場合は、それらの列(プロパティ)に_[SkipTracking]
_属性を適用できます。
データベースを変更するたびに、DbContext.SaveChanges()
を呼び出します。これで整数を取るオーバーロードが利用可能になりました。これは、ログインした人のユーザーIDである必要があります。ユーザーIDを渡さない場合、この変更は追跡テーブルに記録されません。
_databaseContext.SaveChanges(userId);
_
そしてそれだけです。後で、これでログを取得できます。
_var AuditLogs = db.GetLogs<Proyecto>(id).ToList();
_
免責事項:私はプロジェクトの所有者です Entity Framework Plus
EF +には、EF5、EF6、およびEF Coreをサポートする監査機能があります。
// using Z.EntityFramework.Plus; // Don't forget to include this.
var ctx = new EntityContext();
// ... ctx changes ...
var audit = new Audit();
audit.CreatedBy = "ZZZ Projects"; // Optional
ctx.SaveChanges(audit);
// Access to all auditing information
var entries = audit.Entries;
foreach(var entry in entries)
{
foreach(var property in entry.Properties)
{
}
}
データベースの自動保存のような多くのオプションが利用可能です。
ドキュメント: EF + Audit
汎用リポジトリパターンでは、db context savechangesイベントの汎用イベントハンドラを記述できます。
私はそれをグーグルで検索し、多くの多くの情報を収集しました。
だから私は一般的な単一のメソッドを書くことを計画しています
私が使用しているDb構造
監査表
CREATE TABLE [dbo].[Audit](
[Id] [BIGINT] IDENTITY(1,1) NOT NULL,
[TableName] [nvarchar](250) NULL,
[Updated By] [nvarchar](100) NULL,
[Actions] [nvarchar](25) NULL,
[OldData] [text] NULL,
[NewData] [text] NULL,
[Created For] varchar(200) NULL,
[Updated Date] [datetime] NULL,
CONSTRAINT [PK_DBAudit] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
2. Auditテーブルエンティティを使用してdbcontextを更新します。
3. Dbcontext savechangesの汎用イベントハンドラーをフックする
c#コード
namespace ARMS.Domain
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.Objects;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
public partial class ARMSContext
{
Collection<Audit> auditTrailList = new Collection<Audit>();
partial void OnContextCreated()
{
this.SavingChanges += new EventHandler(ArmsEntities_SavingChanges);
}
public enum AuditActions
{
Added,
Modified,
Deleted
}
void ArmsEntities_SavingChanges(object sender, EventArgs e)
{
auditTrailList.Clear();
IEnumerable<ObjectStateEntry> changes =
this.ObjectStateManager.GetObjectStateEntries(
EntityState.Added | EntityState.Deleted | EntityState.Modified);
foreach (ObjectStateEntry stateEntryEntity in changes)
{
if (!stateEntryEntity.IsRelationship && stateEntryEntity.Entity != null && !(stateEntryEntity.Entity is Audit))
{
Audit audit = this.GetAudit(stateEntryEntity);
auditTrailList.Add(audit);
}
}
if (auditTrailList.Count > 0)
{
foreach (var audit in auditTrailList)
{
this.Audits.AddObject(audit);
}
}
}
public Audit GetAudit(ObjectStateEntry entry)
{
Audit audit = new Audit();
audit.Updated_By ="Test";
audit.TableName = entry.EntitySet.ToString();
audit.Updated_Date = DateTime.Now;
audit.Created_For = Convert.ToString(entry.Entity);
audit.Actions = Enum.Parse(typeof(AuditActions),entry.State.ToString(), true).ToString();
StringBuilder newValues = new StringBuilder();
StringBuilder oldValues = new StringBuilder();
if (entry.State == EntityState.Added)
{
SetAddedProperties(entry, newValues);
audit.NewData = newValues.ToString();
}
else if (entry.State == EntityState.Deleted)
{ SetDeletedProperties(entry, oldValues);
audit.OldData = oldValues.ToString();
}
else if (entry.State == EntityState.Modified)
{
SetModifiedProperties(entry, oldValues, newValues);
audit.OldData = oldValues.ToString();
audit.NewData = newValues.ToString();
}
return audit;
}
private void SetAddedProperties(ObjectStateEntry entry, StringBuilder newData)
{
CurrentValueRecord currentValues = entry.CurrentValues;
for (int i = 0; i < currentValues.FieldCount; i++)
{
newData.AppendFormat("{0}={1} || ", currentValues.GetName(i), currentValues.GetValue(i));
}
}
private void SetDeletedProperties(ObjectStateEntry entry, StringBuilder oldData)
{
foreach (var propertyName in entry.GetModifiedProperties())
{
var oldVal = entry.OriginalValues[propertyName];
if (oldVal != null)
{
oldData.AppendFormat("{0}={1} || ", propertyName, oldVal);
}
}
}
private void SetModifiedProperties(ObjectStateEntry entry, StringBuilder oldData, StringBuilder newData)
{
foreach (var propertyName in entry.GetModifiedProperties())
{
var oldVal = entry.OriginalValues[propertyName];
var newVal = entry.CurrentValues[propertyName];
if (oldVal != null && newVal != null && !Equals(oldVal, newVal))
{
newData.AppendFormat("{0}={1} || ", propertyName, newVal);
oldData.AppendFormat("{0}={1} || ", propertyName, oldVal);
}
}
}
}
}
変更をキャプチャするためのクラスを作成するか、エンティティが追加、変更、または削除されたときに変更を追跡します。
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Web;
namespace MVC_AuditTrail.Models
{
public class AuditTrailFactory
{
private DbContext context;
public AuditTrailFactory(DbContext context)
{
this.context = context;
}
public Audit GetAudit(DbEntityEntry entry)
{
Audit audit = new Audit();
// var user = (User)HttpContext.Current.Session[":user"];
audit.UserId = "swapnil";// user.UserName;
audit.TableName = GetTableName(entry);
audit.UpdateDate = DateTime.Now;
audit.TableIdValue = GetKeyValue(entry);
//entry is Added
if (entry.State == EntityState.Added)
{
var newValues = new StringBuilder();
SetAddedProperties(entry, newValues);
audit.NewData = newValues.ToString();
audit.Actions = AuditActions.I.ToString();
}
//entry in deleted
else if (entry.State == EntityState.Deleted)
{
var oldValues = new StringBuilder();
SetDeletedProperties(entry, oldValues);
audit.OldData = oldValues.ToString();
audit.Actions = AuditActions.D.ToString();
}
//entry is modified
else if (entry.State == EntityState.Modified)
{
var oldValues = new StringBuilder();
var newValues = new StringBuilder();
SetModifiedProperties(entry, oldValues, newValues);
audit.OldData = oldValues.ToString();
audit.NewData = newValues.ToString();
audit.Actions = AuditActions.U.ToString();
}
return audit;
}
private void SetAddedProperties(DbEntityEntry entry, StringBuilder newData)
{
foreach (var propertyName in entry.CurrentValues.PropertyNames)
{
var newVal = entry.CurrentValues[propertyName];
if (newVal != null)
{
newData.AppendFormat("{0}={1} || ", propertyName, newVal);
}
}
if (newData.Length > 0)
newData = newData.Remove(newData.Length - 3, 3);
}
private void SetDeletedProperties(DbEntityEntry entry, StringBuilder oldData)
{
DbPropertyValues dbValues = entry.GetDatabaseValues();
foreach (var propertyName in dbValues.PropertyNames)
{
var oldVal = dbValues[propertyName];
if (oldVal != null)
{
oldData.AppendFormat("{0}={1} || ", propertyName, oldVal);
}
}
if (oldData.Length > 0)
oldData = oldData.Remove(oldData.Length - 3, 3);
}
private void SetModifiedProperties(DbEntityEntry entry, StringBuilder oldData, StringBuilder newData)
{
DbPropertyValues dbValues = entry.GetDatabaseValues();
foreach (var propertyName in entry.OriginalValues.PropertyNames)
{
var oldVal = dbValues[propertyName];
var newVal = entry.CurrentValues[propertyName];
if (oldVal != null && newVal != null && !Equals(oldVal, newVal))
{
newData.AppendFormat("{0}={1} || ", propertyName, newVal);
oldData.AppendFormat("{0}={1} || ", propertyName, oldVal);
}
}
if (oldData.Length > 0)
oldData = oldData.Remove(oldData.Length - 3, 3);
if (newData.Length > 0)
newData = newData.Remove(newData.Length - 3, 3);
}
public long? GetKeyValue(DbEntityEntry entry)
{
var objectStateEntry = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
long id = 0;
if (objectStateEntry.EntityKey.EntityKeyValues != null)
id = Convert.ToInt64(objectStateEntry.EntityKey.EntityKeyValues[0].Value);
return id;
}
private string GetTableName(DbEntityEntry dbEntry)
{
TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), false).SingleOrDefault() as TableAttribute;
string tableName = tableAttr != null ? tableAttr.Name : dbEntry.Entity.GetType().Name;
return tableName;
}
}
public enum AuditActions
{
I,
U,
D
}
}
次に、監査テーブルエンティティとコンテキストクラスを作成します。
このメソッドのsavechangesメソッドをオーバーライドすると、監査変更が取得され、ベースエンティティが保存される前に保存されます。
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Web;
namespace MVC_AuditTrail.Models
{
public class Student
{
public int StudentID { get; set; }
public string Name { get; set; }
public string mobile { get; set; }
}
public class Audit
{
public long Id { get; set; }
public string TableName { get; set; }
public string UserId { get; set; }
public string Actions { get; set; }
public string OldData { get; set; }
public string NewData { get; set; }
public Nullable<long> TableIdValue { get; set; }
public Nullable<System.DateTime> UpdateDate { get; set; }
}
public class StdContext : DbContext
{
private AuditTrailFactory auditFactory;
private List<Audit> auditList = new List<Audit>();
private List<DbEntityEntry> objectList = new List<DbEntityEntry>();
public StdContext() : base("stdConnection")
{
Database.SetInitializer<StdContext>(new CreateDatabaseIfNotExists<StdContext>());
}
public DbSet<Student> Student { get; set; }
public DbSet<Audit> Audit { get; set; }
public override int SaveChanges()
{
auditList.Clear();
objectList.Clear();
auditFactory = new AuditTrailFactory(this);
var entityList = ChangeTracker.Entries().Where(p => p.State == EntityState.Added || p.State == EntityState.Deleted || p.State == EntityState.Modified);
foreach (var entity in entityList)
{
Audit audit = auditFactory.GetAudit(entity);
bool isValid = true;
if (entity.State == EntityState.Modified && string.IsNullOrWhiteSpace(audit.NewData) && string.IsNullOrWhiteSpace(audit.OldData))
{
isValid = false;
}
if (isValid)
{
auditList.Add(audit);
objectList.Add(entity);
}
}
var retVal = base.SaveChanges();
if (auditList.Count > 0)
{
int i = 0;
foreach (var audit in auditList)
{
if (audit.Actions == AuditActions.I.ToString())
audit.TableIdValue = auditFactory.GetKeyValue(objectList[i]);
this.Audit.Add(audit);
i++;
}
base.SaveChanges();
}
return retVal;
}
}
}