Update Statement Deleting Navigation Property Records - postgresql

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?

Related

Unable to get data using BulkRead method of EFCore.BulkExtensions

Hope you are doing well.
I'm using EFCore.BulkExtensions[3.1.6] in .Net Core 3.1 Web API to perform bulk operations. Bulk insert and update are working fine but I'm not able to use BulkRead method to get the bulk data.
I refer this link https://github.com/borisdj/EFCore.BulkExtensions#read-example and tried it but I'm not getting data. Maybe I didn't understand the example.
Here is the code which I've tried:
IList<VehicleSubModel> submodels = new List<VehicleSubModel>(); // VehicleSubModel is the domain Entity
var result = submodels.Select(s => new VehicleSubModel() { Id = s.Id, Name = s.Name }).ToList();
var bulkConfig = new BulkConfig { UpdateByProperties = new List<string> { nameof(VehicleSubModel.Id), nameof(VehicleSubModel.Name) } };
await Task.Run(() => Context.BulkRead(result, bulkConfig));
I want to get Id and Name of all VehicleSubModel but it's not returning any record. Could anyone please explain how we can use the BulkRead method of EFCore.BulkExtensions. I spend several hours to get it done, search many links but not getting its solution.
Can anyone please help?

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.

Testing EF Core with relational data, schema and transactions

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.

Azure Mobile App Offline Sync - URL length limitations, batched pulls, queryId length

Schematically, I spend my time doing things looking like the following.
public async Task<ItemA> GetItemsA(object someParams)
{
var res = new List<ItemA>();
var listOfItemAIds = await GetIdsFromServerAsync(someParams);
var tableAQuery = _tableA.Where(x => listOfItemAIds.Contains(x.Id));
await _tableA.PullAsync(null, tableAQuery);
var itemsA= await tableAQuery.ToListAsync();
var listOfItemBIds = itemsA.Select(x => x.bId).ToList();
await _tableB.PullAsync(null, _tableB.Where(x => listOfItemBIds .Contains(x.Id));
foreach(var itemA in itemsA)
{
itemA.ItemB = await _tableB.LookupAsync(itemA.itemBId);
}
return res;
}
There are several problems with that:
listOfTableAIds.Contains(x.Id) leads to errors due to URL length limitations
As a cannot represent the content of listOfItemAIds or listOfItemBIds with a queryId of less than 50 chars, I end up pulling data that I might already have
It's a shame that all my pulls are not batched into a single server call
I could directly get all I need from a single server query but then I wouldn't benefit from the Azure Mobile Sync framework
Any suggestions on how to improve that sequence?
I would suggest that you refine things so that you query is simpler. You may overall pull more data, but then that data will be available within tableB. For instance, you may want to say "where itemBId is not null".

Logic for tracking entity framework property value changes in MVC

I think I am missing something in my understanding of tracking property value changes in entity framework.
I have an application where i store service requests. Whenever a team value in changed in the service request record, I want to create a team history record in a related teamhistory entity.
I have created the app in MVC using the standard scaffolding for controllers and views.
In the (post)edit task in the controller, the standard logic generated has the following code
if (ModelState.IsValid)
{
db.Entry(serviceRequest).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(serviceRequest);
I have modified that to include the creating of the teamhistory record and an individualhistory record for individual assigned within team. The code for creating these related records work, BUT i want these records only created when the values on team or member(individual) change from what they were previously.
So far the conditions i have specified due not trigger this correctly because I havent gotten the condition right. Below is the current code:
//string teamorig = db.Entry(serviceRequest).Property(u => u.Team).OriginalValue.ToString();
//string teamcurr = db.Entry(serviceRequest).Property(u => u.Team).CurrentValue.ToString();
//if (teamorig != teamcurr)
var TeamIsModified = db.Entry(serviceRequest).Property(u => u.Team).IsModified;
if (TeamIsModified)
{
serviceRequest.TeamAssignmentHistories.Add(new TeamAssignmentHistory()
{
DateAssigned = DateTime.Now,
AssignedBy = User.Identity.Name,
ServiceRequest = serviceRequest.Id,
Team = serviceRequest.Team
});
}
//=========================================================================================
// if individual assigned has changed add individual history record========================
var IndividualIsModified = db.Entry(serviceRequest).Property(u => u.Member).IsModified;
if (IndividualIsModified)
{
serviceRequest.IndividualAssignmentHistories.Add(new IndividualAssignmentHistory()
{
DateAssigned = DateTime.Now,
AssignedBy = User.Identity.Name,
ServiceRequest = serviceRequest.Id,
AssignedTo = serviceRequest.Member.Value,
});
}
//===========================================================================================
The var teamismodified logic doesnt work. When I save the page without making any changes on it- the logic kicks off because in debugging it thinks the field has been modified.
When I comment out that code and uncomment the code above it for original and currentvalues- ie the teamorig and teamcurr logic, teamcurr and teamorig have the same values in debug, even when they have been forced into a change on the save in the MVC view page. Because they have the same values, the if condition is false so the team history record is not created.
The above code has been sandwiched in between
db.Entry(serviceRequest).State = EntityState.Modified;
and
await db.SaveChangesAsync();
statements.
What am I not understanding about entity framework tracking changes in mvc? Why does think its modified when i make not changes to team, and why are teamorig and teamcurr the same when I do make the changes?
Any advice would be welcome. Thanks