Testing EF Core with relational data, schema and transactions - entity-framework

I have a problem creating a unit test with EF Core (2.0.1).
I create my db with the following options:
var options = new DbContextOptionsBuilder<MyContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.ConfigureWarnings((b) =>
{
b.Ignore(InMemoryEventId.TransactionIgnoredWarning);
})
.Options;
The code I want to test looks something like this:
using (IDbContextTransaction transaction = await context.Database.BeginTransactionAsync())
{
await context.Database.ExecuteSqlCommandAsync("DELETE FROM fooSchema.Customers WHERE ID = {0}", id);
await context.SaveChangesAsync();
// Other stuff...
context.Customers.Add(fooCustomer);
await context.SaveChangesAsync();
}
First I had the issue with InMemory not supporting transactions. I solved it using ConfigureWarnings as shown in the code. But then it turns out InMemory doesn't handle ExecuteSqlCommandAsync. So then I tried SQLLite, but it doesn't handle custom schemas instead.
How do I create a DbContext, without any "real" DB, that handles transactions, schema and ExecuteSqlCommandAsync?
It is OK to suppress the error from ExecuteSqlCommandAsync. But I cannot find the EventId for it. In reality it works great, this is just for the unit test.

Related

PostgreSql - EFCore - Transaction not working as expected

The project is an ASP.Net core project using EF core en PostgreSql as database.
Basically the flow boils down to :
var transaction = Database.BeginTransaction();
var someEntityA = new EntityA()
{
Id = Guid.NewGuid().ToString();
};
await DataBase.DbSet<EntityA>.AddAsync(someEntityA);
await DataBase.SaveChangesAsync();
var someEntityB = new EntityB()
{
Id = Guid.NewGuid().ToString();
EntityAId = someEntityA.Id;
};
await DataBase.DbSet<EntityB>.AddAsync(someEntityB);
await DataBase.SaveChangesAsync();
transaction.Commit();
When the second SaveChangesAsync is hit, a FK violation is thrown.
Do we execute the same flow without
var transaction = Database.BeginTransaction();
transaction.Commit();
Then it is all fine.
This flow is spread over multiple classes (service and repository classes) so just removing the first SaveChangesAsync is not an option. Hence that is the reason why using the manually transaction handling.
This flow works fine in MS Sql but in PostgreSql apparantly not.
Any suggestions how to make it work in PostgreSql ?

Flutter / SQFLite adding rows rawInsert

I am working with an existant SQLite database that is copied from assets. Further now everything is alright but when trying to insert some data in the table an error appears:
DatabaseException(no such table: Parametre (code 1 SQLITE_ERROR[1])
and this is my :
Future<bool> insertUser (String user, String depot, String pwd) async {
int result = 0;
Database database = await openDatabase(dbPath);
final db = await database;
try {
result = await db.rawInsert(
'INSERT INTO Parametre (USER, DEPOT) VALUES ("$user", "$depot")');
}catch(e){
print(e);
}
if(result>0){
return true;
}else{
return false;
}
}
what have i missed?
Alright, the problem appeared to be in the dbPath. The dbPath was set to 'assets/myDatabase.db'. First step was to copy the database from the assets to the app.
Second after copying the database i don't have to use the full path to openDatabase. The right way was to use only the database name, means openDatabase('myDatabase.db').
From the looks of it, either you've mispelled the table name Parametre or you don't have a table with that name in your database. You should run a CREATE TABLE statement to create the table somewhere before (or, if you have it in your code, it has not been run or contains some other error).
It may help you knowing that sqflite provides two great arguments to its openDatabase method: onCreate and onUpdate. They allow you to call a custom function when the database is created (a simple way to create all the tables your app needs at the first run) and another different function to update your database by adding tables or altering columns if you just provide a coherent version argument to the same openDatabase function.

Can I configure Audit.NET to create audit records in two tables on for one audit event?

I am currently implementing Audit.NET into an ASP.NET Core Web API project that is using EF Core. I am using the Entity Framework Data Provider and currently have it configured to map all entities to a single audit log (AuditLog) table with the code below.
Audit.Core.Configuration.Setup()
.UseEntityFramework(_ => _
.AuditTypeMapper(t => typeof(AuditLog))
.AuditEntityAction<AuditLog>((ev, entry, audit) =>
{
audit.Date = DateTime.UtcNow;
audit.AuditData = JsonConvert.SerializeObject(entry);
audit.UserIdentifier = userId;
})
.IgnoreMatchedProperties(true));
This is working great, however, I would like to write audit entries to the BlogApprovals table if the entity type is Blog - in addition to the entry getting added to AuditLog. So for a Blog entity I would like an audit record in both BlogApprovals and AuditLog. Is this possible?
Not really, since the EntityFrameworkDataProvider is designed to map each entity to only one audit entity.
But you could trigger the extra insert, after the operation is complete, by using an OnSaving Custom Action, like this:
Audit.Core.Configuration.AddOnSavingAction(scope =>
{
// OnSaving event fires after context SaveChanges
var efEvent = (scope.Event as AuditEventEntityFramework)?.EntityFrameworkEvent;
if (efEvent != null && efEvent.Success)
{
foreach (var e in efEvent.Entries)
{
if (e.Table == "Blogs" && e.Action == "Insert")
{
// there was an insert on blogs table, insert the blogapproval
var ctx = efEvent.GetDbContext() as MyContext;
ctx.BlogApproval.Add(new BlogApproval() { Note = "note..." });
(ctx as IAuditBypass).SaveChangesBypassAudit();
}
}
}
});

Update Statement Deleting Navigation Property Records

I'm using EF Core to add new games to a Sudoku API I'm building. Each game has one user who can have many roles. Whenever I add a new game I have to manually pull the users roles and add them back to the context otherwise they are lost. Likewise, as I'm adding new games there are situations where all other prior games for the user are lost. What am I doing wrong?
I've tried
_context.Games.Add(game);
which caused it's own set of issues, I then tried
_context.Games.Update(game);
but I'm getting the current issue:
public async Task<Game> CreateGame(CreateGameRO createGameRO) {
var userActionResult =
await _userService.GetUser(createGameRO.UserId);
var difficultyActionResult =
await _difficultiesService.GetDifficulty(createGameRO.DifficultyId);
SudokuMatrix matrix = new SudokuMatrix();
matrix.GenerateSolution();
Game game = new Game(
userActionResult.Value,
matrix,
difficultyActionResult.Value);
// EF Core loses reference to the users roles when creating new games.
// Before we save the new game we pull a reference to the users roles...
var user = game.User;
var userRoles = _context.UsersRoles.Where(u => u.UserId == user.Id).ToList();
//var userGames = _context.Games.Where(g => g.UserId == user.Id).ToList();
_context.Games.Update(game);
await _context.SaveChangesAsync();
// ...then we reattach the users roles to the data context.
_context.UsersRoles.AddRange(userRoles);
//_context.Games.AddRange(userGames);
await _context.SaveChangesAsync();
return game;
}
I added logging to my app and noted the following generated SQL commands:
DELETE FROM "Games"
WHERE "Id" = #p0;
DELETE FROM "Games"
WHERE "Id" = #p1;
DELETE FROM "SudokuCells"
WHERE "Id" = #p2;
DELETE FROM "SudokuCells"
WHERE "Id" = #p3;
DELETE FROM "SudokuCells"
WHERE "Id" = #p4;
The situations where this happens is as follows... suppose I make a user Dean Winchester and then add a second user Sam Winchester. I then create two new games for Dean and everything is fine, I create two new games for Sam and everything is still fine... I then create another game for Dean and the above SQL is created and the first two games are deleted.
Since no one else can answer this I'm going to go ahead and do so.
Supposedly I'm working in a disconnected environment with a WebAPI where the context doesn't know about the other games and roles my users have unless I specifically track them. However, I have documented evidence in my log where EF core is going out of it's way to track down the users games and roles... and deletes them!
This behavior makes no sense to me and I'd appreciate it if someone could explain the underlying logic for handling it this way. To me it is counter intuitive that an update statement is encapsulating deletes.
My solution now looks like this:
Game game = new Game(
userActionResult.Value,
matrix,
difficultyActionResult.Value);
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == userActionResult.Value.Id);
var userRoles = await _context.UsersRoles
.Where(ur => ur.UserId == user.Id)
.ToListAsync();
var userGames = await _context.Games
.Where(g => g.UserId == user.Id)
.ToListAsync();
_context.Games.Update(game);
await _context.SaveChangesAsync();
_context.UsersRoles.AddRange(userRoles);
await _context.SaveChangesAsync();
_context.Games.UpdateRange(userGames);
await _context.SaveChangesAsync();
I have to make three trips to the database to protect the data I didn't want touched in the first place... this is stupid but it works.
I'm not happy with this solution. Suppose a user had 1000 games? I have to pull all 1000 games into memory... add my new game... EF core then deletes all prior 1000 games... then I have to re add the 1000 games?

Azure Mobile Offline Sync: Cannot delete an operation from __operations

I'm having a huge issue that I've been trying for days to get through. I have a scenario in which I'm trying to handle an Insert Conflict in my Xamarin project. The issue is that the record in the Cloud DB doesn't exist because there was an issue with a foreign key constraint so I'm in a scenario in which the sync conflict handler needs to delete the local record along with the record in the __operations table in SQLite. I've tried everything. Purge with the override set to 'true' so that it should delete the local record and all operations associated. Doesn't work. I've been just trying to force delete it by accessing the SQL store manually:
var id = localItem[MobileServiceSystemColumns.Id];
var operationQuery = await store.ExecuteQueryAsync("__operations", $"SELECT * FROM __operations WHERE itemId = '{id}'", null).ConfigureAwait(false);
var syncOperation = operationQuery.FirstOrDefault();
var tableName = operation.Table.TableName;
await store.DeleteAsync(tableName, new List<string>(){ id.ToString() });
if (syncOperation != null)
{
await store.DeleteAsync("__operations", new List<string>() { syncOperation["id"].ToString() }).ConfigureAwait(false);
}
I am able to query the __operations table and I can see the ID of the item I want to delete. The DeleteAsync method runs without exception but no status is returned so I have no idea if this worked or not. When I try to sync again the operation stubbornly exists. This seems ridiculous. How do I just delete an operation without having to sync with the web service? I'm about to dig down further and try to force it even harder by using the SQLiteRaw library but I'm really really hoping I'm missing something obvious? Can anyone help? THANKS!
You need to have a subclass of the Microsoft.WindowsAzure.MobileServices.Sync.MobileServiceSyncHandler class, which overrides OnPushCompleteAsync() in order to handle conflicts and other errors. Let's call the class SyncHandler:
public class SyncHandler : MobileServiceSyncHandler
{
public override async Task OnPushCompleteAsync(MobileServicePushCompletionResult result)
{
foreach (var error in result.Errors)
{
await ResolveConflictAsync(error);
}
await base.OnPushCompleteAsync(result);
}
private static async Task ResolveConflictAsync(MobileServiceTableOperationError error)
{
Debug.WriteLine($"Resolve Conflict for Item: {error.Item} vs serverItem: {error.Result}");
var serverItem = error.Result;
var localItem = error.Item;
if (Equals(serverItem, localItem))
{
// Items are the same, so ignore the conflict
await error.CancelAndUpdateItemAsync(serverItem);
}
else // check server item and local item or the error for criteria you care about
{
// Cancels the table operation and discards the local instance of the item.
await error.CancelAndDiscardItemAsync();
}
}
}
Include an instance of this SyncHandler() when you initialize your MobileServiceClient:
await MobileServiceClient.SyncContext.InitializeAsync(store, new SyncHandler()).ConfigureAwait(false);
Read up on the MobileServiceTableOperationError to see other conflicts you can handle as well as its methods to allow resolving them.