具有审计表的实体框架
目录
介绍
背景
使用代码
Main方法
运行程序
IAudited
Model1.edmx
总结
- 下载TestEntityFramework.zip - 20 KB
介绍
本文提供了将Entity Framework 6与现有SQL Server表一起使用的示例。这些表具有更新触发器,可将当前版本的记录复制到相应的审计表,并使用新的TraceVersion和UTimeStamp更新记录。
背景
数据库中的所有表都有4个额外的列用于审计目的:
- UserId(int):Id修改记录的用户
- Deleted(bit):表示是否删除记录
- TraceVersion(int):记录的版本号
- UTimeStamp(datetime):上次修改的日期和时间
SQL操作执行以下操作:
- INSERT:在Insert上没有触发器,记录按原样插入表中。数据访问层可确保Deleted=0,TraceVersion=1,UTimeStamp=当前日期和时间。
- UPDATE:有一个AFTER UPDATE触发器。如果
- Deleted=0:将表中的当前记录插入审计表,然后更新当前记录,TraceVersion增加1,并将其UTimeStamp设置为当前日期和时间。
- Deleted=1:与Deleted=0相同,但是额外的,更新的记录(Deleted=1)也会插入到审计表中,并从主表中删除。
- DELETE:AFTER DELETE触发器禁止DELETE声明。删除记录必须通过将Deleted列更新为1。
例如,以下语句将在数据库中生成以下记录:
- INSERT CUsers(UserId,Deleted,TraceVersion,UTimeStamp,NTUser,FName,LName,Active) VALUES(1,0,1,GETDATE(),'gmeyer','George','Meyer',1)
一条记录插入主表:
表 |
ID |
用户身份 |
删除 |
TraceVersion |
UTimeStamp |
NTUSER |
FName参数 |
LName的 |
活性 |
CUsers |
2 |
1 |
0 |
1 |
2019-09-10 11:08:23.340 |
gmeyer |
George |
Meyer |
1 |
- UPDATE CUsers SET LName='Meyers' WHERE Id=2
当前记录(TraceVersion=1)将插入到Audit表中。更新的记录得到TraceVersion=2:
表 |
ID |
用户身份 |
删除 |
TraceVersion |
UTimeStamp |
NTUSER |
FName参数 |
LName的 |
活性 |
CUsers_Audit |
2 |
1 |
0 |
1 |
2019-09-10 11:08:23.340 |
gmeyer |
George |
Meyer |
1 |
CUsers |
2 |
1 |
0 |
2 |
2019-09-10 11:17:03.640 |
gmeyer |
George |
Meyers |
1 |
- UPDATE CUsers SET Deleted=1
当前记录(TraceVersion=2)将插入到Audit表中。更新的记录(Deleted=1)获取TraceVersion=3并也添加到Audit表中。记录将从主表中删除:
表 |
ID |
用户身份 |
删除 |
TraceVersion |
UTimeStamp |
NTUSER |
FName参数 |
LName的 |
活性 |
CUsers_Audit |
2 |
1 |
0 |
1 |
2019-09-10 11:08:23.340 |
gmeyer |
George |
Meyer |
1 |
CUsers_Audit |
2 |
1 |
0 |
2 |
2019-09-10 11:17:03.640 |
gmeyer |
George |
Meyers |
1 |
CUsers_Audit |
2 |
1 |
0 |
3 |
2019-09-10 11:17:44.020 |
gmeyer |
George |
Meyers |
1 |
没有记录CUsers。
用于创建表,触发器和插入管理用户的SQL语句如下:
CREATE TABLE [dbo].[CUsers]([Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,[UserId] [int] NOT NULL,[Deleted] [bit] NOT NULL,[TraceVersion] [int] NOT NULL,[UTimeStamp] [datetime] NOT NULL,[NTUser] [varchar](50) NOT NULL,[FName] [varchar](20) NOT NULL,[LName] [varchar](50) NOT NULL,[Active] [bit] NOT NULL,CONSTRAINT [IX_CUsers] UNIQUE NONCLUSTERED ([NTUser] ASC) ON [PRIMARY]
) ON [PRIMARY]
GOALTER TABLE [dbo].[CUsers] WITH CHECK ADD CONSTRAINT [FK_CUsers_CUsers] FOREIGN KEY([UserId])
REFERENCES [dbo].[CUsers] ([Id])
GOCREATE TABLE [dbo].[CUsers_Audit]([Id] [int] NOT NULL,[UserId] [int] NOT NULL,[Deleted] [bit] NOT NULL,[TraceVersion] [int] NOT NULL,[UTimeStamp] [datetime] NOT NULL,[NTUser] [varchar](50) NOT NULL,[FName] [varchar](20) NOT NULL,[LName] [varchar](50) NOT NULL,[Active] [bit] NOT NULL,CONSTRAINT [PK_CUsers_Audit] PRIMARY KEY CLUSTERED ([Id] ASC, _[TraceVersion] ASC) ON [PRIMARY]) ON [PRIMARY]
GO---------- AUDIT TRIGGER SCRIPT FOR TABLE CUsers---------------
CREATE TRIGGER [dbo].[trCUsers_AUDIT_UD] ON [dbo].[CUsers]
AFTER UPDATE, DELETE
AS
/* If no rows were affected, do nothing */
IF @@ROWCOUNT=0RETURNSET NOCOUNT ON
BEGIN TRYDECLARE @Counter INT, @Now DATETIMESET @Now = GETDATE()/* Check the action (UPDATE or DELETE) */SELECT @Counter = COUNT(*)FROM INSERTEDIF @Counter = 0 --> DELETETHROW 50000, 'DELETE action is prohibited for CUsers', 1/* Insert previous record to Audit */INSERT INTO CUsers_Audit([Id],[UserId],[Deleted],_[TraceVersion],[UTimeStamp],[NTUser],[FName],[LName],[Active]) SELECT d.[Id],d.[UserId],d.[Deleted],d.[TraceVersion],_d.[UTimeStamp],d.[NTUser],d.[FName],d.[LName],d.[Active]FROM DELETED d/* Update master record TraceVersion, UTimeStamp */UPDATE mainSET main.TraceVersion = d.TraceVersion + 1, main.UTimeStamp = @NowFROM CUsers mainINNER JOIN DELETED d ON d.Id = main.IdINNER JOIN INSERTED i ON i.Id = main.Id/* Process deleted rows */IF NOT EXISTS(SELECT 1 FROM INSERTED WHERE Deleted = 1)RETURN/* Re-insert last updated master record into Audit table where Deleted = 1 */INSERT INTO CUsers_Audit([Id],[UserId],[Deleted],[TraceVersion],_[UTimeStamp],[NTUser],[FName],[LName],[Active]) SELECT d.[Id],d.[UserId],d.[Deleted],d.[TraceVersion],d.[UTimeStamp],_d.[NTUser],d.[FName],d.[LName],d.[Active]FROM CUsers dINNER JOIN INSERTED i ON d.Id = i.IdWHERE i.Deleted = 1/* Delete master record */DELETE cFROM CUsers cINNER JOIN INSERTED i ON c.Id = i.IdWHERE i.Deleted = 1
END TRY
BEGIN CATCHTHROW
END CATCH
GOALTER TABLE [dbo].[CUsers] ENABLE TRIGGER [trCUsers_AUDIT_UD]
GOINSERT CUsers(UserId,Deleted,TraceVersion,UTimeStamp,NTUser,FName,LName,Active)
VALUES(1,0,1,GETDATE(),'admin','Admin','Admin',1)
该Entity Framework会为每一个更新Entity创建一个SQL UPDATE语句,但不创建一个SELECT语句来检索由触发更新的TraceVersion和UTimeStamp列。该Entity Framework会为每一个被删除Entity创建一个SQL DELETE语句,但在这种情况下,UPDATE语句被要求设置列Deleted为1。
使用代码
该项目是一个控制台应用程序。
Main方法
Program.cs中的Main方法完全按照上述语句插入,更新和删除记录,但使用SQLEntity Framework:
static void Main(string[] args){try{int id;CUser user;var connString =ConfigurationManager.ConnectionStrings["DB1Entities"].ConnectionString;Console.WriteLine("Connection string={0}", connString);Console.WriteLine("Adding user");using (var context = new DB1Entities()){context.Database.Log = Console.WriteLine;var dateNow = DateTime.Now;user = new CUser(){UserId = 1,NTUser = "gmeyer",FName = "George",LName = "Meyer",Active = true};context.CUsers.Add(user);context.SaveChanges();id = user.Id;Console.WriteLine("user.Id={0}", user.Id);WriteChangeTrackerCount(context);}using (var context = new DB1Entities()){context.Database.Log = Console.WriteLine;context.CUsers.Attach(user);user.LName = "Meyers";context.SaveChanges();Console.WriteLine("user.TraceVersion={0}", user.TraceVersion);WriteChangeTrackerCount(context);}using (var context = new DB1Entities()){context.Database.Log = Console.WriteLine;context.CUsers.Attach(user);context.CUsers.Remove(user);context.SaveChanges();Console.WriteLine("context.Entry(user).State={0}", context.Entry(user).State);WriteChangeTrackerCount(context);}}catch (Exception ex){Console.WriteLine(ex.ToString());}Console.ReadKey();}
运行程序
要运行该程序,应创建SQL Server上的数据库,并且在该数据库中,应使用先前给出的SQL脚本创建这两个表。应在app.config中相应地修改连接字符串。在提供的连接字符串中,将调用数据库DB1。运行项目应该创建以下输出:
Connection string=metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string="data source=localhost;initial catalog=DB1;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
Adding user
Opened connection at 9/11/2019 9:44:08 AM +03:00
Started transaction at 9/11/2019 9:44:08 AM +03:00
INSERT [dbo].[CUsers]([UserId], [Deleted], [TraceVersion], [UTimeStamp], [NTUser], [FName], [LName], [Active])
VALUES (@0, @1, @2, @3, @4, @5, @6, @7)
SELECT [Id]
FROM [dbo].[CUsers]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
-- @0: '1' (Type = Int32)
-- @1: 'False' (Type = Boolean)
-- @2: '1' (Type = Int32)
-- @3: '9/11/2019 9:44:08 AM' (Type = DateTime2)
-- @4: 'gmeyer' (Type = AnsiString, Size = 50)
-- @5: 'George' (Type = AnsiString, Size = 20)
-- @6: 'Meyer' (Type = AnsiString, Size = 50)
-- @7: 'True' (Type = Boolean)
-- Executing at 9/11/2019 9:44:09 AM +03:00
-- Completed in 45 ms with result: SqlDataReaderCommitted transaction at 9/11/2019 9:44:09 AM +03:00
Closed connection at 9/11/2019 9:44:09 AM +03:00
user.Id=3
ChangeTracker.Entries().ToList().Count=1
Opened connection at 9/11/2019 9:44:09 AM +03:00
Started transaction at 9/11/2019 9:44:09 AM +03:00
UPDATE [dbo].[CUsers]
SET [LName] = @0
WHERE (([Id] = @1) AND ([TraceVersion] = @2))
-- @0: 'Meyers' (Type = AnsiString, Size = 50)
-- @1: '3' (Type = Int32)
-- @2: '1' (Type = Int32)
-- Executing at 9/11/2019 9:44:09 AM +03:00
-- Completed in 138 ms with result: 1Committed transaction at 9/11/2019 9:44:09 AM +03:00
Closed connection at 9/11/2019 9:44:09 AM +03:00
Opened connection at 9/11/2019 9:44:09 AM +03:00
SELECT[Extent1].[Id] AS [Id],[Extent1].[UserId] AS [UserId],[Extent1].[Deleted] AS [Deleted],[Extent1].[TraceVersion] AS [TraceVersion],[Extent1].[UTimeStamp] AS [UTimeStamp],[Extent1].[NTUser] AS [NTUser],[Extent1].[FName] AS [FName],[Extent1].[LName] AS [LName],[Extent1].[Active] AS [Active]FROM [dbo].[CUsers] AS [Extent1]WHERE [Extent1].[Id] = 3
-- Executing at 9/11/2019 9:44:10 AM +03:00
-- Completed in 14 ms with result: SqlDataReaderClosed connection at 9/11/2019 9:44:10 AM +03:00
user.TraceVersion=2
ChangeTracker.Entries().ToList().Count=1
Opened connection at 9/11/2019 9:44:10 AM +03:00
Started transaction at 9/11/2019 9:44:10 AM +03:00
UPDATE [dbo].[CUsers]
SET [Deleted] = @0
WHERE (([Id] = @1) AND ([TraceVersion] = @2))
-- @0: 'True' (Type = Boolean)
-- @1: '3' (Type = Int32)
-- @2: '2' (Type = Int32)
-- Executing at 9/11/2019 9:44:10 AM +03:00
-- Completed in 15 ms with result: 1Committed transaction at 9/11/2019 9:44:10 AM +03:00
Closed connection at 9/11/2019 9:44:10 AM +03:00
context.Entry(user).State=Detached
ChangeTracker.Entries().ToList().Count=0
IAudited
接口IAudited由所有实体实现。它定义所有实体都有列Deleted,TraceVersion和UTimeStamp。
interface IAudited
{bool Deleted { get; set; }int TraceVersion { get; set; }DateTime UTimeStamp { get; set; }
}
Model1.edmx
实体框架模型Model1.edmx是使用Add new item/ADO.NET Entity Data Model/EF Designer from database并选择数据库DB1和表CUsers创建的。在TraceVersion列的属性中将ConcurrencyMode设置为Fixed。
当有许多表时,使用XML (Text) Editor打开Model1.edmx并进行搜索更容易
<Property Name="TraceVersion" Type="Int32" Nullable="false" />
并替换为
<Property Name="TraceVersion" Type="Int32" Nullable="false" ConcurrencyMode="Fixed" />
设置ConcurrencyMode为 Fixed是有效果的,即所有更新语句都将TraceVersion添加到WHERE子句中,例如:
UPDATE [dbo].[CUsers]
SET [LName] = @0
WHERE (([Id] = @1) AND ([TraceVersion] = @2))
乐观并发是这样实现的。
从Model1.edmx生成模板文件Model1.Context.tt,然后从中生成C#代码文件Model1.Context.cs。同样从Model1.edmx生成模板文件Model1.tt,用于为每个实体生成C#代码文件,在本例中为CUser.cs。要使此模型与给定数据库一起使用,必须进行以下修改。
Model1.Context.tt
此模板用于创建Context类。
1、添加了以下using声明:
using System.Configuration;
using System.Linq;
2、实体框架从app.config获取连接字符串。原始构造函数如下所示:
public DB1Entities(): base("name=<# =container.Name#>") {}
但是,连接字符串通常是从其他模块中检索的,它可能是编码的并且必须进行解码,因此创建了GetConnectionString函数。此函数可用于检索连接字符串。在本例中,此函数还会从app.config中读取连接字符串。构造函数调用GetConnectionString函数。
private static string GetConnectionString() {return ConfigurationManager.ConnectionStrings["DB1Entities"].ConnectionString;
}public DB1Entities(): base(GetConnectionString())
{
}
3、重写方法SaveChanges。
public override int SaveChanges(){...int rowCount = base.SaveChanges();...return rowCount;}
4、该模型应该确保所有新的实体有Deleted=false,TraceVersion=1和UTimestamp=current date and time。以下代码执行此操作:
var entriesAdded =ChangeTracker.Entries().Where(e => e.State == EntityState.Added).ToList();
foreach (var item in entriesAdded)
{if (item.Entity is IAudited entity){entity.Deleted = false;entity.TraceVersion = 1;entity.UTimeStamp = DateTime.Now;}
}
5、该模型应重新加载更新的实体的字段TraceVersion和UTimeStamp 。不幸的是,它只能重新加载整个实体。未来的改进应该是只重新加载字段TraceVersion和UTimeStamp。
var entriesModified =ChangeTracker.Entries().Where(e => e.State == EntityState.Modified).ToList();
int rowCount = base.SaveChanges();
if (rowCount > 0)
{entriesModified.ForEach(e=>e.Reload());
}
6、模型应将已删除的实体更改为列Deleted设置为1的更新实体。保存后,应分离这些实体。这就像软删除,但记录从主表移动到审计表。
var entriesDeleted = ChangeTracker.Entries().Where(e => e.State == EntityState.Deleted).ToList();foreach (var item in entriesDeleted){if (item.Entity is IAudited entity){// Set the entity to unchanged // (if we mark the whole entity as Modified, // every field gets sent to Db as an update)item.State = EntityState.Unchanged;// Only update the Deleted flag - only this will get sent to the Dbentity.Deleted = true;}}int rowCount = base.SaveChanges();if (rowCount > 0){foreach (var item in entriesDeleted){if (item.Entity is IAudited entity){item.State = EntityState.Detached;}}}
Model1.tt
此模板用于创建实体类,在本例中为CUser.cs。唯一的修改是让每个Entity类实现接口IAudited。类定义的原始模板是:
public string EntityClassOpening(EntityType entity)
{return string.Format(CultureInfo.InvariantCulture,"{0} {1}partial class {2}{3}",Accessibility.ForType(entity),_code.SpaceAfter(_code.AbstractOption(entity)),_code.Escape(entity),_code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)));
}
生成以下Entity类:
public partial class CUser
entity.BaseType似乎永远是null的,因此生成的类不从一些基本的类型继承。模板已更改为:
public string EntityClassOpening(EntityType entity)
{return string.Format(CultureInfo.InvariantCulture,"{0} {1}partial class {2} : IAudited",Accessibility.ForType(entity),_code.SpaceAfter(_code.AbstractOption(entity)),_code.Escape(entity));
}
生成以下Entity类:public partial class CUser : IAudited
public partial class CUser : IAudited
总结
该项目证明可以创建一个Entity Framework Model:
- 确保所有新的(添加的)实体获得Deleted=false,TraceVersion=1并且UTimeStamp=当前日期和时间。
- 重新加载所有更新的实体,使实体获得新的TraceVersion和UTimeStamp被触发给出。
- 将所有删除更改为更新列Deleted=1,保存后分离这些实体。
具有审计表的实体框架相关推荐
- 带有审计表的实体框架核心(EF Core)
目录 介绍 背景 使用代码 主(Main)方法 运行程序 IAudited Model1.cs Model1.Partial.cs 部分类CUsers TVUT类 局部类Model1 OnModelC ...
- 实体框架 5.0:空间数据类型、性能增强、数据库提升
实体框架 5.0提供了对空间数据类型的支持,其利用DbGeography和DbGeometry类来实现.同时,通过在缓存模式中对内联LINQ查询进行转换,引入了LINQ to Entities的自动化 ...
- ADO.NET Entity Framework如何:通过每种类型一个表继承以定义模型(实体框架)
本主题介绍如何手动创建具有每种类型一个表继承层次结构的概念模型.每种类型一个表继承使用数据库中单独的表为继承层次结构中的每种类型维护非继承属性和键属性的数据. 说明: 建议使用 ADO.NET 实体数 ...
- 使用实体框架返回数据表
目录 背景 扩展方式 实体框架(Entity Framework) 实体框架核心(Entity Framework Core) 使用扩展方法 使用常规查询 使用参数化查询 不同数据库的DbParame ...
- SQL Server上的审计表和数据版本控制
目录 介绍 背景--审计表 背景--数据版本控制 让我们一石二鸟,并使用审计表也用于版本化数据 使事情变得清晰的视图示例 审计表 审计触发器 上下文信息 从C# EF6传递上下文 最终评论和一些高级技 ...
- 使用实体框架核心创建简单的审计跟踪
目录 介绍 背景 审核日志数据 审核表实体 审核类型 审计Db上下文 审核表配置 数据更改到审核表 实体更改为审核表实体 所有实体更改为审计表 在现有的DbContext中使用审核跟踪 创建DbCon ...
- C#代码生成工具:文本模板初体验 使用T4批量修改实体框架(Entity Framework)的类名...
转自:http://www.cnblogs.com/huangcong/archive/2011/07/20/1931107.html 在之前的文本模板(T4)初体验中我们已经知道了T4的用处,下面就 ...
- Rafy 领域实体框架设计 - 重构 ORM 中的 Sql 生成
前言 Rafy 领域实体框架作为一个使用领域驱动设计作为指导思想的开发框架,必然要处理领域实体到数据库表之间的映射,即包含了 ORM 的功能.由于在 09 年最初设计时,ORM 部分的设计并不是最重要 ...
- Entity Framework 实体框架的形成之旅--实体数据模型 (EDM)的处理(4)
在前面几篇关于Entity Framework 实体框架的介绍里面,已经逐步对整个框架进行了一步步的演化,以期达到统一.高效.可重用性等目的,本文继续探讨基于泛型的仓储模式实体框架方面的改进优化,使我 ...
最新文章
- android获取图片缩略图,Android系获取图片和视频的缩略图
- 真香!20张图揭开「队列」的迷雾,一目了然
- SSL方式获取邮箱收件箱
- 「镁客早报」阿里巴巴与Office Depot合作,服务美国小企业;HTC与印厂商谈品牌许可协议,或退出手机市场...
- 极大似然估计的渐进正态性
- python time,datetime与highchart中的time
- 鸿蒙系统和汽车,华为鸿蒙系统和新日电动车,到底是什么关系?
- onepill Android端
- Kubernetes 云原生 容器类型有哪些
- 软考论文写作方法及规范
- 电脑控制所有手机的两种方式
- 前后端分离和不分离图解
- 17美亚团队赛电子取证
- 重装电脑系统完整教程
- BeautyGAN论文翻译
- c语言定时器定时1ms程序,STM32 Cubemx 配置定时器定时1mS
- java 判断字符 不等于 或者_java中字符串不等于怎么判断
- 币圈炒币如何避免被额韭菜?
- 字符画——ASCII art
- python爬淘宝商品销量信息_python爬取淘宝商品销量信息
热门文章
- mysql 联合索引的命中规则_可能是全网最好的MySQL重要知识点/面试题总结
- 无向图中两点之间的距离_九上数学:二次函数图像,一动点到两定点距离和最小...
- object picker 微信小程序_第三天学习微信小程序开发总结
- oracle修改用户密码命令_oracle 11g dba用户秘密修改其他用户密码
- php mysql 连接类_深入理解php的MySQL连接类
- 2021牛气新年素材模板,你真的不来看一看吗?
- oracle字体加粗函数_Oracle日期操作函数
- java web试题_Java web开发经典面试题汇总(内附答案详解)
- Windows下判断文件是否存在
- Linux虚拟化KVM-Qemu分析(一)