Really simple CoreData relationship but returns nil and null? - iphone

This bug has been busting me for the past 4 hours.
Also when I swap it round and get the User data first then Message data... the User.name will show, but the Message.message will not. So the data is definitely going in but the relationship between them seems to be broken.

Firstly, +1 for the effort with the image you created to illustrate your problem.
The cause of your issue is that you never assigned the user to the message (or vice-versa).
Try
message.fetchUser = user;
or
user.fetchMessage = message;
Then save your context and perform the fetch request.

As Rog said, I needed to assign the user to the message (or vice vera). Basically I placed this bit of code after where its entering the data.
messageDetails.fetchUser = userDetails

Related

EF Core and Blazor Server - entity cannot be tracked

Been using EF Core with Razor pages for a few years now, but Blazor with EF Core has me questioning myself on tasks that used to be simple. I'm creating a golf app, and I'm attempting to update a particular person's round of golf.
Having stumbled in the beginning, I have learned that using dependency injection for the dbContext in Blazor causes several errors including the one in my subject line. Instead, I'm using DI to inject an IDbContextFactory and creating a new context in each method of my services.
The following code updates a golfer's round. When editing, the user may change the course, teebox, or any of the 18 scores. I'm able to update the round once, but if I go back into the same round to edit it a second time I get the "cannot be tracked" "already tracking" error.
I've scoured the internet for possible reasons, I've tried .AsNoTracking() on my initial GetRound(), I've tried detaching the entry after SaveChangesAsync(), I've tried using the ChangeTracker to check whether I need to attach to the Round object being updated. Nothing I've done allows me to update the same round twice without doing a reload in between the first and second update.
I'll provide whatever code necessary, but I'll start with the offending code:
public async Task<bool> UpdateRoundAsync(RoundModel Round)
{
var rtnVal = false;
try
{
using (var _context = _dbFactory.CreateDbContext())
{
_context.Rounds.Attach(Round).State = EntityState.Modified;
await _context.SaveChangesAsync();
_context.Entry(Round).State = EntityState.Detached;
}
rtnVal = true;
}
catch (Exception ex)
{
Console.Write(ex.Message);
throw;
}
return rtnVal;
}
When I run the above code, I see NOTHING in the change tracker as modified until I attach to the Round. Despite nothing being tracked, despite the dbContext being created new, then disposed, I still get an error that I'm already tracking the entity.
Help? What am I doing wrong?
Danny
UPDATE:
Edited the repro as requested, but it did not change the issue - still unable to update the Round twice without a reload in between.
Caveat: I'm not happy posting this as an answer, but it does solve the problem for now. I won't mark it as THE answer until I understand more about EFCore and Blazor together.
I did find that I was making a call to get course details without telling EF that I didn't want it to track the entity, however, that still didn't fix the problem.
In the end, I simply forced the page to reload programmatically: NavMgr.NavigateTo("[same page]", true) after my update call. It feels very un-Blazor-like to do it this way, but ultimately I'm still learning Blazor and not getting much feedback on this post. I'm going to forage ahead, and hope that clarity comes down the road.
For anyone that may run across this post, I ran into the same issue in a completely different project, and finally found something that made sense (here on S/O).
In this line of code:
_context.Rounds.Attach(Round).State = EntityState.Modified;
It should be:
_context.Entry(Round).State = EntityState.Modified;
I never knew that these two were different, and I never had an issue using the first example's syntax before starting to code with Blazor.
If you are unaware, like me, the first way of setting the state to modified updates the entity and all related entities - which is why I was getting the error when I tried to make additional changes to the round-related objects.
The second way of setting the state ONLY updates the entity itself and leaves the related entities in a State of Unchanged.
Thank you to #TwoFingerRightClick for his comment on the accepted answer on this post: Related post

Saving a score to firebase with attached values

What I'm wanting to accomplish is save a score to firebase that has two values attached to it. Here's the code that writes the score to firebase.
func writeToFirebase() {
DispatchQueue.global(qos: .userInteractive).async {
self.ref = Database.database().reference()
if GameManager.instance.getTopScores().contains(GameManager.instance.getGameScore()) {
self.ref?.child("user")
.child(GameManager.instance.getUsername())
.child(String(GameManager.instance.getGameScore()))
.updateChildValues( [
"badge":GameManager.instance.getBadgeLevel(),
"vehicle": GameManager.instance.getVehicleSelected()
]
)
}
}
}
The issue I'm having is when a new score is saved with its values it sometimes overwrites the other scores. This seems to be random and its not when they're the same score or anything like that. Sometimes it will only overwrite one score and sometimes multiple. I'm watching firebase and I can see it being overwritten, it turns red and then is deleted. Sometimes the new score being added will be red and get deleted. The score doesn't need to be a child, but I don't know how to attach values to it if it's not. Any help is appreciated
This issue seems to happen occasionally so I am going to post my comment as an answer.
There are situations where an observer may be added to a node and when data changes in that node, like a write or update, it will fire that observer which may then overwrite the existing data with nil.
You can see this visually in the console as when the write occurs, you can see the data change/update, then it turns red and then mysteriously vanishes.
As suggested in my comment, add a breakpoint to the function that performs the write and run the code. See if that function is called twice (or more). If that's the case, the first write is storing the data properly but upon calling it a second time, the values being written are probably nil, which then makes the node 'go away' as Firebase nodes cannot exist without a value.
Generally speaking if you see your data turn red and vanish, it's likely caused by nil values being written to the node.

Wait for backend service response before making changes to ag-grid table

I am using ag-grid/ag-grid-angular to provide an editable grid of data backed by a database. When a user edits a cell I want to be able to post the update to the backend service and if the request is successful update the grid and if not undo the user's changes and show an error.
I have approached this problem from a couple different angles but have yet to find the solution that meets all my requirements and am also curious about what the best practice would be to implement this kind of functionality.
My first thought was to leverage the cellValueChanged event. With this approach I can see the old and new values and then make a call to my service to update the database. If the request is successful then everything is great and works as expected. However, if the request fails for some reason then I need to be able to undo the user's changes. Since I have access to the old value I can easily do something like event.node.setDataValue(event.column, event.oldValue) to revert the user's changes. However, since I am updating the grid again this actually triggers the cellValueChanged event a second time. I have no way of knowing that this is the result of undoing the user's changes so I unnecessarily make a call to my service again to update the data even though the original request was never successful in updating the data.
I have also tried using a custom cell editor to get in between when the user is finished editing a cell and when the grid is actually updated. However, it appears that there is no way to integrate an async method in any of these classes to be able to wait for a response from the server to decide whether or not to actually apply the user's changes. E.g.
isCancelBeforeStart(): boolean {
this.service.updateData(event.data).subscribe(() => {
return false;
}, error => {
return true;
});
}
does not work because this method is synchronous and I need to be able to wait for a response from my service before deciding whether to cancel the edit or not.
Is there something I am missing or not taking in to account? Or another way to approach this problem to get my intended functionality? I realize this could be handled much easier with dedicated edit/save buttons but I am ideally looking for an interactive grid that is saving the changes to the backend as the user is making changes and providing feedback in cases where something went wrong.
Any help/feedback is greatly appreciated!
I understand what you are trying to do, and I think that the best approach is going to be to use a "valueSetter" function on each of your editable columns.
With a valueSetter, the grid's value will not be directly updated - you will have to update your bound data to have it reflected in the grid.
When the valueSetter is called by the grid at the end of the edit, you'll probably want to record the original value somehow, update your bound data (so that the grid will reflect the change), and then kick off the back-end save, and return immediately from the valueSetter function.
(It's important to return immediately from the valueSetter function to keep the grid responsive. Since the valueSetter call from the grid is synchronous, if you try to wait for the server response, you're going to lock up the grid while you're waiting.)
Then, if the back-end update succeeds, there's nothing to do, and if it fails, you can update your bound data to reflect the original value.
With this method, you won't have the problem of listening for the cellValueChanged event.
The one issue that you might have to deal with is what to do if the user changes the cell value, and then changes it again before the first back-end save returns.
onCellValueChanged: (event) => {
if (event.oldValue === event.newValue) {
return;
}
try {
// apiUpdate(event.data)
}
catch {
event.node.data[event.colDef.Field] = event.oldValue;
event.node.setDataValue(event.column, event.oldValue);
}
}
By changing the value back on node.data first, when setDataValue() triggers the change event again, oldValue and newValue are actually the same now and the function returns, avoiding the rather slow infinite loop.
I think it's because you change the data behind the scenes directly without agGrid noticing with node.data = , then make a change that agGrid recognises and rerenders the cell by calling setDataValue. Thereby tricking agGrid into behaving.
I would suggest a slightly better approach than StangerString, but to credit him the idea came from his approach. Rather than using a test of the oldValue/newValue and allowing the event to be called twice, you can go around the change detection by doing the following.
event.node.data[event.colDef.field] = event.oldValue;
event.api.refreshCells({ rowNodes: [event.node], columns: [event.column.colId] });
What that does is sets the data directly in the data store used by aggrid, then you tell it to refresh that grid. That will prevent the onCellValueChanged event from having to be called again.
(if you arent using colIds you can use the field or pass the whole column, I think any of them work)

Core Data relationship not saved to store

I've got this command line app that iterates through CSV files to create a Core Data SQLite store. At some point I'm building these SPStop objects, which has routes and schedules to-many relationships:
SPRoute *route = (SPRoute*)[self.idTransformer objectForOldID:routeID ofType:#"routes" onContext:self.coreDataController.managedObjectContext];
SPStop *stopObject = (SPStop*)[self.idTransformer objectForOldID:stopID ofType:#"stops" onContext:self.coreDataController.managedObjectContext];
[stopObject addSchedulesObject:scheduleObject];
[stopObject addRoutesObject:route];
[self.coreDataController saveMOC];
If I log my stopObject object (before or after saving; same result), I get the following:
latitude = "45.50909";
longitude = "-73.80914";
name = "Roxboro-Pierrefonds";
routes = (
"0x10480b1b0 <x-coredata://A7B68C47-3F73-4B7E-9971-2B2CC42DB56E/SPRoute/p2>"
);
schedules = (
"0x104833c60 <x-coredata:///SPSchedule/tB5BCE5DC-1B08-4D11-BCBB-82CD9AC42AFF131>"
);
Notice how the routes and schedules object URL formats differ? This must be for a reason, because further down the road when I use the sqlite store and print the same stopObject, my routes set is empty, but the schedules one isn't.
I realize this is very little debugging information but maybe the different URL formats rings a bell for someone? What could I be doing wrong that would cause this?
EDIT: it seems that one SPRoute object can only be assigned to one SPStop at once. I inserted breakpoints at the end of the iteration and had a look a the sqlite every time and I definitely see that as soon as an SPRoute object (that already had been assigned to a previous stop.routes) is assigned to a new SPStop, the previous stop.routes set gets emptied. How can this be?
Well, we had disabled Xcode's inverse relationship's warning which clearly states:
SPStop.routes does not have an inverse; this is an advanced
setting (no object can be in multiple destinations for a specific
relationship)
Which was precisely our issue. We had ditched inverse relationships because Apple states that they're only good for "data integrity". Our store is read-only so we figured we didn't really need them. We learn now that inverse relationships are a little more than just for "data integrity" :P

NSStream Event Timer - iPhone

All,
Is there a way to have a minimum time to keep a stream open before it closes? For some reason, my stream is closing prematurely which is causing errors elsewhere. I need to keep it open to make sure ALL of the data is gathered, and then it can run the other code.
Thanks,
James
In the case that someone falls upon this question later, I ended up creating nested if statements to pull it off.
Basically, there is one statement that checks if the end tag is not found (for my code, the END of the ENTIRE data that I should be receiving is </SessionData> - So, I did if([outputString rangeOfString:#"</SessionData>"].location == NSNotFound). I created a string called totalOutput that would have the outputString added onto the end of totalOutput until the </SessionData> is found.
If anyone ever needs help, just go ahead and comment on here and I can give more information.