Marten Not Storing Entity - c#

I'm working with Marten as my data layer and it's been great so far, but I've run into an issue that just doesn't make sense. I have a simple method that saves a transaction (a purchase) then updates a listing, adding the ID of the transaction to a collection. My problem is, it appears that Marten is not storing my updated listing, although it is storing the transaction.
When I look in the database, the TransactionIds property is null, but if I step through the code, everything seems to execute correctly. Am I doing something wrong here?
public async Task CreateListingTransactionAsync(ListingTransaction transaction)
{
if (transaction == null)
throw new ValidationException("Transaction is required to create a transaction");
bool isNew = transaction.Id == Guid.Empty;
await _listingTransactionValidator.ValidateAndThrowAsync(transaction);
using (var session = _store.LightweightSession())
{
session.Store(transaction);
if (isNew)
{
var listing = await session.LoadAsync<Listing>(transaction.ListingId);
if (listing == null)
throw new EntityNotFoundException($"Listing with Id: {transaction.ListingId} not found");
if (listing.TransactionIds == null)
listing.TransactionIds = new List<Guid>();
listing.TransactionIds.Add(transaction.Id);
session.Store(listing);
}
await session.SaveChangesAsync();
}
}

There could be a problem with the serialization of TransactionIds collection.
If that's not the case then here are some random things to try (and try to understand why it worked later):
Try session.Update(listing); instead of session.Store(listing);.
Try different type of document session. http://jasperfx.github.io/marten/documentation/troubleshoot/

Related

Entity Framework returns Points = 0 although in the db it is not 0

I'm working on a game, where players can get points, and players can earn points that are saved in my database. I have one method that looks like this:
private async Task SendAnswer(Answer answer)
{
clicked = true;
answerText = answer.AnswerText;
team = await gameRepo.GetTeamByNameAndGameSession(Teamname, GameSessionId);
if (answer.isCorrect)
{
team.TeamPoints = team.TeamPoints + answer.Points;
}
team.Answer = answer;
await gameRepo.UpdateTeam(team);
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendTeamAnswer", team, GameSessionId);
}
}
That one works just fine, but then I also have this one in another view:
private async Task ChooseBestAnswer(Team team)
{
var answer = currentQuestion.Answers.FirstOrDefault();
team.TeamPoints = team.TeamPoints + answer.Points;
await gameRepo.UpdateTeam(team);
}
Both of them uses this method
public async Task UpdateTeam(Team teamToUpdate)
{
var oldTeam = await _context.Teams.FirstOrDefaultAsync(t => t.TeamName == teamToUpdate.TeamName && t.GameSessionGuid == teamToUpdate.GameSessionGuid);
if (teamToUpdate is not null)
{
oldTeam = teamToUpdate;
}
_context.SaveChangesAsync();
}
In the first method everything works as it should but at the second "oldteam" suddenly returns that points = 0 although I can see in the database that it is not 0, how is this possible, I use the same method put it fetches a 0 where there isn't any. All the other variables that are returned from the db to "oldteam" are correct it is just the points that suddenly are zero.
Does anyone know what is going on?
A couple of problems with this code:
public async Task UpdateTeam(Team teamToUpdate)
{
var oldTeam = await _context.Teams.FirstOrDefaultAsync(t => t.TeamName == teamToUpdate.TeamName && t.GameSessionGuid == teamToUpdate.GameSessionGuid);
if (teamToUpdate is not null)
{
oldTeam = teamToUpdate;
}
_context.SaveChangesAsync();
}
As mentioned in the comments, the SaveChangesAsync isn't awaited, but more importantly, this code doesn't update the team in the database. You are loading the existing team, but then simply overwriting the in-memory reference. That doesn't copy values across. Instead:
public async Task UpdateTeam(Team teamToUpdate)
{
if (teamToUpdate == null) throw new NullReferenceException(nameof(teamToUpdate));
var existingTeam = await _context.Teams.SingleAsync(t => t.TeamName == teamToUpdate.TeamName && t.GameSessionGuid == teamToUpdate.GameSessionGuid);
existingTeam.TeamPoints = teamToUpdate.TeamPoints;
// copy any additional fields that are allowed to be updated.
await _context.SaveChangesAsync();
}
Key changes to consider here. Assert the passed in state early and handle if it's invalid. If you expect 1 team to be found, use Single rather than First, and if an entry is expected, don't use the OrDefault variations. Those should be used only if you expect that an item might not be found. Once we have the existing data record, copy the values that can change across and call SaveChanges, awaiting the async operation.
This code will throw exceptions if expected state isn't valid, but it will throw meaningful exceptions to be handled at an appropriate level. (Rather than less descriptive exceptions when assumptions aren't met, or failing silently.)

Resolve concurrency conflicts on conflicting entities by calling instance methods

This documentation explains how you would resolve concurrency conflict when updating the database through the entity framework.
The solution revolves around reading values of the overlapping instances in a prop-value dictionary, deciding how to resolve the overlap, updating the concurrency token, and then saving the results back to the database; repeat until successful. Copy-pasted the code from docs here.
var saved = false;
while (!saved)
{
try
{
// Attempt to save changes to the database
context.SaveChanges();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is Person)
{
var proposedValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
foreach (var property in proposedValues.Properties)
{
var proposedValue = proposedValues[property];
var databaseValue = databaseValues[property];
// TODO: decide which value should be written to database
// proposedValues[property] = <value to be saved>;
}
// Refresh original values to bypass next concurrency check
entry.OriginalValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ entry.Metadata.Name);
}
}
}
}
In this example, databaseValues is a prop-value dictionary, not an instance of the object.
In my application, conflicts can be resolved by calling a method on the instance with the original values and proposing an update. I wonder if you can get an instance of the object in the DB (which is expected from an ORM anyway) instead of the prop-value dictionary from ex.Entries (i.e., entry.GetDatabaseValues() and entry.CurrentValues;).
I could think of a method that iterates through the prop-value dictionary and create an instance; though I am wondering if there is any built-in/better solution.
(If relevant, the database is Postgres.)

Can't update with .Attach()

I'm trying to update my UserRoles table but it won't update. I'm trying to update two things : 1. the email 2. the user role. Because the update needs to happen in 2 tables I'm using two separate commands. When I run the update on the Email alone (Users) it works but if I update the role (AspUserRoles) it does nothing. When I run it both it doesn't work either because UserRoles.Attach(userRole) is preventing it from updating. I also get no errors.
I checked if ApplicationRole.Id and ApplicationUser.Id has a value and it does return the value I want.
Here's my UserController.cs :
public async Task<IActionResult> Edit(UserViewModel model, Guid id)
{
var alert = new Alert();
try
{
if(!ModelState.IsValid)
{
alert.Message = alert.ExceptionMessage = ApplicationDbContextMessage.INVALID;
throw new Exception();
}
var originalModel = ApplicationDbContext.Users.FirstOrDefault(u => u.Id == id);
var userRole = ApplicationDbContext.UserRoles.FirstOrDefault(i => i.UserId == id);
if(originalModel == null)
{
alert.Message = alert.ExceptionMessage = ApplicationDbContextMessage.NOTEXISTS;
throw new Exception();
}
originalModel.Email = model.ApplicationUser.Email;
userRole.RoleId = model.ApplicationRole.Id;
ApplicationDbContext.Users.Attach(originalModel);
ApplicationDbContext.UserRoles.Attach(userRole);
ApplicationDbContext.Entry(originalModel).State = EntityState.Modified;
if (await ApplicationDbContext.SaveChangesAsync() == 0)
{
alert.Message = alert.ExceptionMessage = ApplicationDbContextMessage.EDITNOK;
throw new Exception();
}
alert.Message = ApplicationDbContextMessage.EDITOK;
return RedirectToAction("Index");
}
catch(Exception ex)
{
alert.Type = AlertType.Error;
alert.ExceptionMessage = ex.Message;
model = await ViewModel(model.ApplicationUser);
ModelState.AddModelError(string.Empty, alert.ExceptionMessage);
}
return View(model);
}
The way you are modifying data in this code, you don't need to call Attach or Add on the Context to let it know about changes to entities, that will happen automatically.
From the moment you pull an entity out of a DbSet of the DbContext it is being tracked (attached) by that DbContext. When you call SaveChanges on the DbContext it will scan any entities that it is tracking, comparing current values to old values, to find changes. Those changes then get sent to the data base.
You should literally be able to remove 3 lines of code from what you originally posted and have it work.
...
originalModel.Email = model.ApplicationUser.Email;
userRole.RoleId = model.ApplicationRole.Id;
ApplicationDbContext.Users.Attach(originalModel); // <--- Delete this line
ApplicationDbContext.UserRoles.Attach(userRole); // <--- Delete this line
ApplicationDbContext.Entry(originalModel).State = EntityState.Modified; // <--- Delete this line
if (await ApplicationDbContext.SaveChangesAsync() == 0)
...
A little something else I noticed. It looks like you might be using one single DbContext instance for the entire application. That is usually considered an "Anti-Patern" in Entity Framework. You should create a new DbContext instance (with using) for every "logical" operation you perform. That instance should only be alive for the life of that operation.
In MVC, this is usually one DbContext instance per ActionMethod.

Copy of entity (rollback purpose) before save?

As a newbie in my work I got in charge of the project for a colleague who left the company. It was his project so this complicate all work because I am quite alone for everything and also I learn EF as you go along. The project is a client-server app uses EF6, UoW and repository approach.
Let me outline the problem. There is a method on server side in repository
MyEntity SaveMyEntity(MyEntity myEntity)
{
//Do some validation before save - call stored procedure
//If fails throw exception
using(var context = new SomeContext())
{
//little bit of code for imagination how implementation looks like
context.my_entity_context_base.Attach(myEntity);
context.SyncObjectState(myEntity, myEntity.ObjectState);
context.SaveChanges();
//...
if (myEntity.EntityB != null && myEntity.EntityB.Count > 0)
{
foreach (var u in myEntity.EntityB.ToList())
{
context.entity_b_context_base.Attach(u);
context.SyncObjectState(u, u.ObjectState);
}
context.SaveChanges();
}
if (myEntity.EntityC != null && myEntity.EntityC.Count > 0)
{
foreach (var t in myEntity.EntityC.ToList())
{
context.entity_c_context_base.Attach(t);
context.SyncObjectState(t, t.ObjectState);
}
context.SaveChanges();
}
// and so on ..
}
//Validation after save - stored procedur is called
//If fails throws exception
//If validate_after_save fails there is rollback on DB side.
base.ExecuteStoredProcedure("validate_after_save", ref parameters, ref errorText);
if (!string.IsNullOrEmpty(errorText))
{
throw new OwnException("Save failed: " + errorText);
}
return this.GetMyEtity(myEntity.id);
}
When no exception is threw everything is fine but when validation after save throws ex the client side doesn't get actual entity. This is really problem in some scenarios as I found out previously. For exmple I want save entity. Validation after save throws ex (so client doesn't receive the entity). So I'll change something and save it again. Because previous validation after save throws an exception the client has still the state "Added" so it throws primary key violation ex.
So I got the task to create same mechanism wich makes rollback in EF. When the exception is threw they want original data. As you can see in the code above there is nothing like that now. Of course, I have transaction on my mind but how is it possible to implement it to current implementation you can see above?
My idea is get the current entity from DB. In the case of exception I call SaveMyEntity with data I got before save. But few problems:
I am not sure where to store the reference of original data (client
or server)?
I'll have to change the state of original entity.
This smells of corruption of relations
Can I have some advice from you?
Thanks a lot!
EDIT:
I just found out existence of TransactionScope. So I tried do something like this
void SaveMyEntity(MyEntity myEntity)
{
//...
using (var scope = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted }))
{
using(var context = new SomeContext())
{
//...
if (myEntity.EntityB != null && myEntity.EntityB.Count > 0)
{
foreach (var u in myEntity.EntityB.ToList())
{
context.entity_b_context_base.Attach(u);
context.SyncObjectState(u, u.ObjectState);
}
context.SaveChanges();
}
if (myEntity.EntityC != null && myEntity.EntityC.Count > 0)
{
foreach (var t in myEntity.EntityC.ToList())
{
context.entity_c_context_base.Attach(t);
context.SyncObjectState(t, t.ObjectState);
}
context.SaveChanges();
}
// and so on ..
}
base.ExecuteStoredProcedure("validate_after_save", ref parameters, ref errorText);
if (!string.IsNullOrEmpty(errorText))
{
//Rollback
scope.Dispose();
throw new OwnException("Save failed: " + errorText);
}
else
{
//Commit
scope.Complete();
}
}
}
And it actually works nice. But I am not sure if it is right solution.

Update only works in debug mode

I'm new to using entity as a data layer between MVC and SQL Server, so I apologize up front if what I'm doing is bad practice.
Let me start by sharing the code that is handling the update.
Update Delivery:
public bool One(Delivery toUpdate)
{
using (var dbContext = new FDb())
{
try
{
var deliveryInDb = this.dbTable(dbContext).Single(x => x.DeliveryId == toUpdate.DeliveryId);
dbContext.Entry(deliveryInDb).CurrentValues.SetValues(toUpdate);
//removal first
List<DeliveryDay> currentDays = FEngineCore.DeliveryDay.Get.ForValue((x => x.DeliveryId), toUpdate.DeliveryId);
List<DeliveryTime> currentTimes = FEngineCore.DeliveryTime.Get.ForValue((x => x.DeliveryId), toUpdate.DeliveryId);
//remove delivery days that are not needed
foreach (var curDay in currentDays)
{
if (!toUpdate.DeliveryDays.Select(x => x.DeliveryDayId).Contains(curDay.DeliveryDayId))
{
FEngineCore.DeliveryDay.Delete.One((x => x.DeliveryDayId), curDay.DeliveryDayId);
deliveryInDb.DeliveryDays.Remove(curDay);
}
}
//remove delivery times that are not needed
foreach (var curTime in currentTimes)
{
if (!toUpdate.DeliveryTimes.Select(x => x.DeliveryTimeId).Contains(curTime.DeliveryTimeId))
{
FEngineCore.DeliveryTime.Delete.One((x => x.DeliveryTimeId), curTime.DeliveryTimeId);
deliveryInDb.DeliveryTimes.Remove(curTime);
}
}
foreach (var day in toUpdate.DeliveryDays)
{
if (day.DeliveryDayId == 0)
{
dbContext.DeliveryDays.Add(day);
}
else
{
if (dbContext.DeliveryDays.Local.Any(e => e.DeliveryDayId == day.DeliveryDayId))
{
dbContext.Entry(dbContext.DeliveryDays.Local.First(e => e.DeliveryDayId == day.DeliveryDayId)).CurrentValues.SetValues(day);
dbContext.Entry(dbContext.DeliveryDays.Local.First(e => e.DeliveryDayId == day.DeliveryDayId)).State = EntityState.Modified;
}
else
{
DeliveryDay modDay = new DeliveryDay
{
DayOfWeek = day.DayOfWeek,
DeliveryDayId = day.DeliveryDayId,
DeliveryId = day.DeliveryId,
Interval = day.Interval
};
dbContext.DeliveryDays.Attach(modDay);
dbContext.Entry(modDay).State = EntityState.Modified;
}
deliveryInDb.DeliveryDays.Add(day);
}
}
foreach (var time in toUpdate.DeliveryTimes)
{
if (time.DeliveryTimeId == 0)
{
dbContext.DeliveryTimes.Add(time);
}
else
{
if (dbContext.DeliveryTimes.Local.Any(e => e.DeliveryTimeId == time.DeliveryTimeId))
{
dbContext.Entry(dbContext.DeliveryTimes.Local.First(e => e.DeliveryTimeId == time.DeliveryTimeId)).CurrentValues.SetValues(time);
dbContext.Entry(dbContext.DeliveryTimes.Local.First(e => e.DeliveryTimeId == time.DeliveryTimeId)).State = EntityState.Modified;
}
else
{
DeliveryTime modTime = new DeliveryTime
{
DeliveryId = time.DeliveryId,
DeliveryLocationId = time.DeliveryLocationId,
DeliveryTimeId = time.DeliveryTimeId,
DropoffTime = time.DropoffTime
};
dbContext.DeliveryTimes.Attach(modTime);
dbContext.Entry(modTime).State = EntityState.Modified;
}
deliveryInDb.DeliveryTimes.Add(time);
}
}
dbContext.SaveChanges();
dbContext.Entry(deliveryInDb).State = EntityState.Detached;
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.InnerException);
return false;
}
}
}
Let me continue by explaining that the delivery object has 2 children; DeliveryTime and DeliveryDay. The issue that arises happens when I try to remove one deliveryTime and modify nothing else. The end result of running the code normally (not in debug) is that the deliveryTime is in fact not removed. Here's the interesting thing guys, when I debug it and go through the break points, everything works as expected!
Let me continue by posting the code that is running behind the removal method of the deliveryTime (actually all entity objects in my system).
public bool One<V>(Expression<Func<T, V>> property, V value) where V : IComparable
{
using (var dbContext = new FoodsbyDb())
{
try
{
T toDelete;
//get the body as a property that represents the property of the entity object
MemberExpression entityPropertyExpression = property.Body as MemberExpression;
//get the parameter that is representing the entity object
ParameterExpression entityObjectExpression = (ParameterExpression)entityPropertyExpression.Expression;
//represent the value being checked against as an expression constant
Expression valueAsExpression = Expression.Constant(value);
//check the equality of the property and the value
Expression equalsExpression = Expression.Equal(entityPropertyExpression, valueAsExpression);
//create an expression that takes the entity object as a parameter, and checks the equality using the equalsExpression variable
Expression<Func<T, bool>> filterLambda = Expression.Lambda<Func<T, bool>>(equalsExpression, entityObjectExpression);
toDelete = this.dbTable(dbContext)
.SingleOrDefault(filterLambda);
if (toDelete != null)
{
this.dbTable(dbContext)
.Remove(toDelete);
dbContext.SaveChanges();
return true;
}
return false;
}
catch (Exception ex)
{
Console.WriteLine(ex.InnerException);
return false;
}
}
}
The code above is obviously generic, and it handles all my entity objects. I have tested it in and out and know for sure the problem does not lie in there. I thought it would be helpful to post it so you all can have a full understanding of what's going on.
Here's my best guess as to what's going on:
The reference to the removed deliveryTime still exists when the database context is saved, but when I debug, the system has enough time to remove the context.
Here was one of my attempted solutions:
Remove all references to the children objects immediately after setting currentDays and currentTimes and then proceeding to add them back to deliveryInDb as you enumerate through them.
Because I am new to all of this, if you see some bad practice along with the solution, I wouldn't mind constructive criticism to improve my programming method.
I actually encountered this issue in a project at work. The project is an older MVC4 project using EF 6.1.
In our situation, a simple update attempting to set a related entity property to null was failing to actually set it to null while running the web app normally (in debug mode). When setting a break point on the line of code that sets the property to null the database would be updated as expected, though. So, the update was working when a break point was in place but not working when allowed to run normally.
Using an EF interceptor, we could see that, with the break point in place, the update query was going through as expected.
Now, in our situation the related entity was using the virtual keyword to allow for lazy loading. I think this is the root of the issue. When a break point is present, EF has enough time to both lazily load that related entity and evaluate whatever it needs to evaluate and finally set it to null. When running without a break point, I think EF gets caught up trying to lazily load that entity and therefore fails to think it needs to be updated. To be clear, I was both accessing the related entity property for the first time and setting it null using a one-liner of code.
foo.Bar = null;
I resolved this issue, in our scenario, by accessing that property at least once prior to setting it to null so that EF is forced to load it. With it loaded, setting it to null seems to work as intended now. So again, to be clear, I think the issue is a combo of lazy loading and the one-liner of code both accessing that property for the first time and assigning it to null.
It appears that you're using multiple instances of your DbContext, which are not synchronized.
The solution would be to use a single instance, and pass that instance between your methods.

Categories

Resources