Skip to content

Commit f91296e

Browse files
Merge pull request #519 from waqasm78/master
Added Async example
2 parents 6c05fe9 + c8508d2 commit f91296e

27 files changed

+1472
-734
lines changed

docs2/pages/documentations/audit.md

Lines changed: 0 additions & 733 deletions
This file was deleted.

docs2/pages/documentations/query-cache.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ var task = ctx.Countries.Where(x => x.IsActive).FromCacheAsync();
6565
var task = ctx.States.Where(x => x.IsActive).FromCacheAsync(DateTime.Now.AddHours(2));
6666

6767
```
68+
[Try it in EF6](https://dotnetfiddle.net/Uh9miZ) | [Try it in EF Core](https://dotnetfiddle.net/U9pU1a)
6869

6970
## EF+ Query Cache Query Deferred
7071

docs2/pages/documentations/query-deferred.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ var countDeferred = ctx.Customers.DeferredCount();
111111
var taskCount = countDeferred.ExecuteAsync();
112112

113113
```
114+
[Try it in EF6](https://dotnetfiddle.net/0BpVn1) | [Try it in EF Core](https://dotnetfiddle.net/1pttmj)
114115

115116
## Real Life Scenarios
116117

@@ -121,7 +122,6 @@ EF Query Deferred brings advantages to other third party features:
121122
- Allows to use Immediate Method with YOUR own features.
122123

123124
## Behind the code
124-
125125
When a deferred method is used, the query expression is created exactly like a non-deferred method but instead of invoking the execute method from the query provider, a new instance of a class QueryDeferred<TResult> is created using the query and the expression.
126126

127127
The QueryDeferred instance has methods to either execute the expression from the query provider or let a third party library use the object query.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Audit
2+
3+
## Introduction
4+
5+
Entity Framework Core saves entities in a database but doesn't let you easily track changes, for example, a history of all modifications and their author in an audit table.
6+
7+
**EF+ Audit** easily tracks changes, exclude/include entity or property and auto save audit entries in the database.
8+
9+
10+
{% include template-example.html %}
11+
```csharp
12+
13+
// using Z.EntityFramework.Plus; // Don't forget to include this.
14+
15+
var ctx = new EntityContext();
16+
// ... ctx changes ...
17+
18+
var audit = new Audit();
19+
audit.CreatedBy = "ZZZ Projects"; // Optional
20+
ctx.SaveChanges(audit);
21+
22+
// Access to all auditing information
23+
var entries = audit.Entries;
24+
foreach(var entry in entries)
25+
{
26+
foreach(var property in entry.Properties)
27+
{
28+
}
29+
}
30+
31+
```
32+
[Try it](https://dotnetfiddle.net/dc7v3W)
33+
34+
## Scenarios
35+
36+
- [AutoSave](scenarios/ef-core-audit-autosave.md)
37+
- [Data Annotations](scenarios/ef-core-audit-data-annotations.md)
38+
- [Exclude & Include Entity](scenarios/ef-core-audit-exclude-include-entity.md)
39+
- [Exclude & Include Property](scenarios/ef-core-audit-exclude-include-property.md)
40+
- [Format Value](scenarios/ef-core-audit-format-value.md)
41+
- [Ignore Events](scenarios/ef-core-audit-ignore-events.md)
42+
- [Property Unchanged](scenarios/ef-core-audit-property-unchanged.md)
43+
- [Soft Add & Soft Delete](scenarios/ef-core-audit-soft-add-soft-delete.md)
44+
- [Retrieve AuditEntries for specific item](scenarios/ef-core-audit-retrieve-audit-entries-for-specific-item.md)
45+
- [Audit Customization](scenarios/ef-core-audit-customization.md)
46+
- [Audit + Entity Framework Extensions](scenarios/ef-core-audit-ef-extensions.md)
47+
48+
## Limitations
49+
50+
- **DO NOT** support relationship
51+
- **DO NOT** populate EntitySetName value
52+
53+
## Requirements
54+
55+
- **EF+ Audit:** Full version or Standalone version
56+
- **Database Provider:** All supported
57+
- **Entity Framework Core Version:** EFCore 1.0+
58+
- **Minimum Framework Version:** .NET Core 1.0
59+
60+
## Troubleshooting
61+
62+
Why only my key is added when updating my entity?
63+
64+
This issue often happens for MVC user. They create a new entity through HttpPost values and force the state to "Modified", the context is not aware of the original value and use the current value instead. So, every property has the original value == current value and our auditing only log the key since all other values are equals.
65+
66+
We recommend setting the **IgnorePropertyUnchanged **to false to log every property.
67+
68+
Here is an example of this issue: [Issues #8](https://github.com/zzzprojects/EntityFramework-Plus/issues/8)
69+
70+
## Conclusion
71+
72+
**Auditing** in Entity Framework Core could not be simpler, there are always some entities in an application where an audit table can be crucial to keep track of what's happening, and you now have access to an easy to use library for these situations.
73+
74+
Need help getting started? [[email protected]](mailto:[email protected])
75+
76+
We welcome all comments, ideas and suggestions to improve our library.
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# AutoSave Audit
2+
3+
## Problem
4+
5+
You need to automatically save audit entries in the database to keep a history in an audit table.
6+
7+
## Solution
8+
9+
If an action for the property **AutoSavePreAction** is set, audit entries will automatically be saved in the database when **SaveChanges** or **SaveChangesAsync** methods are called.
10+
11+
***By using EF+ Audit entity***
12+
13+
{% include template-example.html %}
14+
```csharp
15+
16+
public class EntityContext : DbContext
17+
{
18+
// ... context code ...
19+
public DbSet<AuditEntry> AuditEntries { get; set; }
20+
public DbSet<AuditEntryProperty> AuditEntryProperties { get; set; }
21+
22+
static EntityContext()
23+
{
24+
AuditManager.DefaultConfiguration.AutoSavePreAction = (context, audit) =>
25+
// ADD "Where(x => x.AuditEntryID == 0)" to allow multiple SaveChanges with same Audit
26+
(context as EntityContext).AuditEntries.AddRange(audit.Entries);
27+
}
28+
}
29+
30+
var audit = new Audit();
31+
audit.CreatedBy = "ZZZ Projects"; // Optional
32+
ctx.SaveChanges(audit);
33+
34+
```
35+
[Try it](https://dotnetfiddle.net/wi8men)
36+
37+
***By using a different context***
38+
39+
{% include template-example.html %}
40+
```csharp
41+
42+
public class OracleContext : DbContext
43+
{
44+
// ... context code ...
45+
}
46+
47+
public class SqlServerContext : DbContext
48+
{
49+
// ... context code ...
50+
public DbSet<AuditEntry> AuditEntries { get; set; }
51+
public DbSet<AuditEntryProperty> AuditEntryProperties { get; set; }
52+
}
53+
54+
AuditManager.DefaultConfiguration.AutoSavePreAction = (context, audit) =>
55+
{
56+
var sqlServerContext = new SqlServerContext();
57+
sqlServerContext.AuditEntries.AddRange(audit.Entries);
58+
sqlServerContext.SaveChanges();
59+
};
60+
61+
var audit = new Audit();
62+
audit.CreatedBy = "ZZZ Projects"; // Optional
63+
oracleContext.SaveChanges(audit);
64+
65+
```
66+
[Try it](https://dotnetfiddle.net/RZeTZq)
67+
68+
***Custom AuditEntry & Database First Approach***
69+
70+
{% include template-example.html %}
71+
```csharp
72+
73+
AuditManager.DefaultConfiguration.AutoSavePreAction = (context, audit) =>
74+
{
75+
// ADD "Where(x => x.AuditEntryID == 0)" to allow multiple SaveChanges with same Audit
76+
var customAuditEntries = audit.Entries.Select(x => Import(x));
77+
(context as Entities).AuditEntries.AddRange(customAuditEntries);
78+
};
79+
80+
using (var ctx = new Entities())
81+
{
82+
Audit audit = new Audit();
83+
audit.CreatedBy = "ZZZ Projects"; // Optional
84+
85+
ctx.Entity_Basic.Add(new Entity_Basic() {ColumnInt = 2});
86+
ctx.SaveChanges(audit);
87+
}
88+
89+
public Static AuditEntry Import(Z.EntityFramework.Plus.AuditEntry entry)
90+
{
91+
var customAuditEntry = new AuditEntry
92+
{
93+
EntitySetName = entry.EntitySetName,
94+
EntityTypeName = entry.EntityTypeName,
95+
State = (int)entry.State,
96+
StateName = entry.StateName,
97+
CreatedBy = entry.CreatedBy,
98+
CreatedDate = entry.CreatedDate
99+
};
100+
101+
customAuditEntry.AuditEntryProperties = entry.Properties.Select(x => Import(x)).ToList();
102+
103+
return customAuditEntry;
104+
}
105+
106+
public Static AuditEntryProperty Import(Z.EntityFramework.Plus.AuditEntryProperty property)
107+
{
108+
var customAuditEntry = new AuditEntryProperty
109+
{
110+
RelationName = property.RelationName,
111+
PropertyName = property.PropertyName,
112+
OldValue = property.OldValueFormatted,
113+
NewValue = property.NewValueFormatted
114+
};
115+
116+
return customAuditEntry;
117+
}
118+
119+
```
120+
[Try it](https://dotnetfiddle.net/JRiebw)
121+
122+
***Saving automatically by overriding SaveChanges & SaveChangesAsync***
123+
124+
{% include template-example.html %}
125+
```csharp
126+
127+
AuditManager.DefaultConfiguration.AutoSavePreAction = (context, audit) =>
128+
// ADD "Where(x => x.AuditEntryID == 0)" to allow multiple SaveChanges with same Audit
129+
(context as TestContext).AuditEntries.AddRange(audit.Entries);
130+
131+
public class EntityContext : DbContext
132+
{
133+
// ... context code ...
134+
135+
public override int SaveChanges()
136+
{
137+
var audit = new Audit();
138+
audit.PreSaveChanges(this);
139+
var rowAffecteds = base.SaveChanges();
140+
audit.PostSaveChanges();
141+
142+
if (audit.Configuration.AutoSavePreAction != null)
143+
{
144+
audit.Configuration.AutoSavePreAction(this, audit);
145+
base.SaveChanges();
146+
}
147+
148+
return rowAffecteds;
149+
}
150+
151+
public override Task<int> SaveChangesAsync()
152+
{
153+
return SaveChangesAsync(CancellationToken.None);
154+
}
155+
156+
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken)
157+
{
158+
var audit = new Audit();
159+
audit.PreSaveChanges(this);
160+
var rowAffecteds = await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
161+
audit.PostSaveChanges();
162+
163+
if (audit.Configuration.AutoSavePreAction != null)
164+
{
165+
audit.Configuration.AutoSavePreAction(this, audit);
166+
await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
167+
}
168+
169+
return rowAffecteds;
170+
}
171+
}
172+
173+
using(var ctx = new EntityContext())
174+
{
175+
// ... code ...
176+
ctx.SaveChanges();
177+
}
178+
179+
```
180+
[Try it](https://dotnetfiddle.net/FtG10X)
181+
182+
***SQL Script (for Database First)***
183+
184+
{% include template-example.html %}
185+
```csharp
186+
187+
CREATE TABLE [dbo].[AuditEntries] (
188+
[AuditEntryID] [int] NOT NULL IDENTITY,
189+
[EntitySetName] [nvarchar](255),
190+
[EntityTypeName] [nvarchar](255),
191+
[State] [int] NOT NULL,
192+
[StateName] [nvarchar](255),
193+
[CreatedBy] [nvarchar](255),
194+
[CreatedDate] [datetime] NOT NULL,
195+
CONSTRAINT [PK_dbo.AuditEntries] PRIMARY KEY ([AuditEntryID])
196+
)
197+
198+
GO
199+
200+
CREATE TABLE [dbo].[AuditEntryProperties] (
201+
[AuditEntryPropertyID] [int] NOT NULL IDENTITY,
202+
[AuditEntryID] [int] NOT NULL,
203+
[RelationName] [nvarchar](255),
204+
[PropertyName] [nvarchar](255),
205+
[OldValue] [nvarchar](max),
206+
[NewValue] [nvarchar](max),
207+
CONSTRAINT [PK_dbo.AuditEntryProperties] PRIMARY KEY ([AuditEntryPropertyID])
208+
)
209+
210+
GO
211+
212+
CREATE INDEX [IX_AuditEntryID] ON [dbo].[AuditEntryProperties]([AuditEntryID])
213+
214+
GO
215+
216+
ALTER TABLE [dbo].[AuditEntryProperties]
217+
ADD CONSTRAINT [FK_dbo.AuditEntryProperties_dbo.AuditEntries_AuditEntryID]
218+
FOREIGN KEY ([AuditEntryID])
219+
REFERENCES [dbo].[AuditEntries] ([AuditEntryID])
220+
ON DELETE CASCADE
221+
222+
GO
223+
224+
```

0 commit comments

Comments
 (0)