Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AuditDbContext saves audit and entity in one transaction. #316

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions src/Audit.EntityFramework/AuditDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ public void AddAuditCustomField(string fieldName, object value)
}

#if EF_FULL
// TODO: I didn't change anything, because there is an issue and the tests should be fixed before.
public override int SaveChanges()
{
return _helper.SaveChanges(this, () => base.SaveChanges());
Expand All @@ -192,21 +193,27 @@ Task<int> IAuditBypass.SaveChangesBypassAuditAsync()
#else
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
return _helper.SaveChanges(this, () => base.SaveChanges(acceptAllChangesOnSuccess));
var saveChanges = _helper.SaveChanges(
this,
() => -1);
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
return _helper.SaveChangesAsync(this, () => base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken));
var saveChanges = await _helper.SaveChangesAsync(
this,
() => Task.FromResult(-1));
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
int IAuditBypass.SaveChangesBypassAudit()
{
return base.SaveChanges(true);
return -1;
}
Task<int> IAuditBypass.SaveChangesBypassAuditAsync()
{
return base.SaveChangesAsync(true, default);
return Task.FromResult(-1);
}
#endif
#endregion
}
}
}
141 changes: 141 additions & 0 deletions test/Audit.EntityFramework.Core.UnitTest/EfTransactionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Audit.Core;
using Microsoft.EntityFrameworkCore;
using NUnit.Framework;

namespace Audit.EntityFramework.Core.UnitTest
{
[TestFixture]
public class EfTransactionTests
{
class Message
{
public Guid MessageId { get; set; }

public string Sender { get; set; }

public string Receiver { get; set; }

public string Content { get; set; }
}

class MessageAudit
{
[Key] public Guid AuditLogId { get; set; }

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

public DateTimeOffset AuditTimestamp { get; set; }

public string AuditAction { get; set; }

public Guid MessageId { get; set; }
}

class TestContext : AuditDbContext
{
public DbSet<Message> Messages { get; set; }
public DbSet<MessageAudit> MessageAudits { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlServer("data source=localhost;initial catalog=TransactionTestEfCore;integrated security=true;");
}

[OneTimeSetUp]
public async Task SetUp()
{
using (var context = new TestContext())
{
await context.Database.EnsureCreatedAsync();
}
}

[Test]
public async Task ExceptionInAuditEntity()
{
var messageId = Guid.NewGuid();

Audit.EntityFramework.Configuration.Setup()
.ForContext<TestContext>();

Audit.Core.Configuration.Setup()
.UseEntityFramework(ef => ef
.AuditTypeExplicitMapper(m => m
.Map<Message, MessageAudit>()
.AuditEntityAction<MessageAudit>((auditEvent, eventEntry, entity) =>
{
entity.AuditData = eventEntry.ToJson();
entity.AuditTimestamp = DateTimeOffset.UtcNow;
entity.AuditAction = eventEntry.Action;
})
)
);

using (var context = new TestContext())
{
Message message = new Message
{
MessageId = messageId
};
await context.AddAsync(message);
await context.SaveChangesAsync();
}

using (var context = new TestContext())
{
Message message = new Message
{
MessageId = messageId
};
await context.AddAsync(message);
Assert.CatchAsync<Exception>(async () => await context.SaveChangesAsync());
}

using (var context = new TestContext())
{
Assert.AreEqual(1, await context.Messages.CountAsync(e => e.MessageId == messageId));
Assert.AreEqual(1, await context.MessageAudits.CountAsync(e => e.MessageId == messageId));
}
}

[Test]
public async Task ExceptionInAudit()
{
const string exceptionMessage = "test";
var messageId = Guid.NewGuid();

Audit.EntityFramework.Configuration.Setup()
.ForContext<TestContext>();

Audit.Core.Configuration.Setup()
.UseEntityFramework(ef => ef
.AuditTypeExplicitMapper(m => m
.Map<Message, MessageAudit>()
.AuditEntityAction<MessageAudit>((auditEvent, eventEntry, entity) =>
{
throw new Exception(exceptionMessage);
})
)
);

using (var context = new TestContext())
{
Message message = new Message
{
MessageId = messageId
};
await context.AddAsync(message);
Exception exception = Assert.CatchAsync<Exception>(async () => await context.SaveChangesAsync());
Assert.AreEqual(exceptionMessage, exception.Message);
}

using (var context = new TestContext())
{
Assert.AreEqual(0, await context.Messages.CountAsync(e => e.MessageId == messageId));
Assert.AreEqual(0, await context.MessageAudits.CountAsync(e => e.MessageId == messageId));
}
}
}
}
146 changes: 146 additions & 0 deletions test/Audit.EntityFramework.Full.UnitTest/EfTransactionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Threading.Tasks;
using Audit.Core;
using NUnit.Framework;

namespace Audit.EntityFramework.Full.UnitTest
{
// TODO: Test doesn't work before my change. There is an issue before.

[TestFixture]
public class EfTransactionTests
{
class Message
{
public Guid MessageId { get; set; }

public string Sender { get; set; }

public string Receiver { get; set; }

public string Content { get; set; }
}

class MessageAudit
{
[Key] public Guid AuditLogId { get; set; }

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

public DateTimeOffset AuditTimestamp { get; set; }

public string AuditAction { get; set; }

public Guid MessageId { get; set; }
}

class TestContext : AuditDbContext
{
public TestContext()
: base("data source=localhost;initial catalog=TransactionTestEfFull;integrated security=true;")
{

}

public DbSet<Message> Messages { get; set; }
public DbSet<MessageAudit> MessageAudits { get; set; }
}

[OneTimeSetUp]
public void SetUp()
{
using (var context = new TestContext())
{
context.Database.CreateIfNotExists();
}
}

[Test]
public async Task ExceptionInAuditEntity()
{
var messageId = Guid.NewGuid();

Audit.EntityFramework.Configuration.Setup()
.ForContext<TestContext>();

Audit.Core.Configuration.Setup()
.UseEntityFramework(ef => ef
.AuditTypeExplicitMapper(m => m
.Map<Message, MessageAudit>()
.AuditEntityAction<MessageAudit>((auditEvent, eventEntry, entity) =>
{
entity.AuditData = eventEntry.ToJson();
entity.AuditTimestamp = DateTimeOffset.UtcNow;
entity.AuditAction = eventEntry.Action;
})
)
);

using (var context = new TestContext())
{
Message message = new Message
{
MessageId = messageId
};
context.Messages.Add(message);
await context.SaveChangesAsync();
}

using (var context = new TestContext())
{
Message message = new Message
{
MessageId = messageId
};
context.Messages.Add(message);
Assert.CatchAsync<Exception>(async () => await context.SaveChangesAsync());
}

using (var context = new TestContext())
{
Assert.AreEqual(1, await context.Messages.CountAsync(e => e.MessageId == messageId));
Assert.AreEqual(1, await context.MessageAudits.CountAsync(e => e.MessageId == messageId));
}
}

[Test]
public async Task ExceptionInAudit()
{
const string exceptionMessage = "test";
var messageId = Guid.NewGuid();

Audit.EntityFramework.Configuration.Setup()
.ForContext<TestContext>();

Audit.Core.Configuration.Setup()
.UseEntityFramework(ef => ef
.AuditTypeExplicitMapper(m => m
.Map<Message, MessageAudit>()
.AuditEntityAction<MessageAudit>((auditEvent, eventEntry, entity) =>
{
throw new Exception(exceptionMessage);
})
)
);

using (var context = new TestContext())
{
Message message = new Message
{
MessageId = messageId
};
context.Messages.Add(message);
Exception exception = Assert.CatchAsync<Exception>(async () => await context.SaveChangesAsync());
Assert.AreEqual(exceptionMessage, exception.Message);
}

using (var context = new TestContext())
{
Assert.AreEqual(0, await context.Messages.CountAsync(e => e.MessageId == messageId));
Assert.AreEqual(0, await context.MessageAudits.CountAsync(e => e.MessageId == messageId));
}
}
}
}