Trouble with EF "Save" method, modified collection of child entities - c#

I have a parent entity (Treatment) with a collection of child entities (Segments). I have a save method that take a treatment, determines if it's new or existing, and then either adds it to the objectContext or attaches it to the object context based on whether it is new or existing.
It does the same thing with the children within the main entity. It iterates over the collection of child entities, and then adds or updates as appropriate.
What I'm trying to get it to do, is to delete any child objects that are missing. The problem is, when I'm updating the parent object and then I attach it to the object context, the parent object then has a collection of child objects from the DB. Not the collection I originally passed in. So if I had a Treatment with 3 segments, and I remove one segment from the collection, and then pass the Treatment into my save method, as soon as the Treatment object is attached to the objectcontext, the number of segments it has is changed from 2 to 3.
What am I doing wrong?
Here is the code of my save method:
public bool Save(Treatment myTreatment, modelEntities myObjectContext)
{
bool result = false;
if (myObjectContext != null)
{
if (myTreatment.Treatment_ID == 0)
{
myObjectContext.Treatments.AddObject(myTreatment);
}
else
{
if (myTreatment.EntityState == System.Data.EntityState.Detached)
{
myObjectContext.Treatments.Attach(myTreatment);
}
myObjectContext.ObjectStateManager.ChangeObjectState(myTreatment, System.Data.EntityState.Modified);
myObjectContext.Treatments.ApplyCurrentValues(myTreatment);
}
foreach (Segment mySegment in myTreatment.Segments)
{
if (mySegment.SegmentID == 0)
{
myObjectContext.ObjectStateManager.ChangeObjectState(mySegment, System.Data.EntityState.Added);
myObjectContext.Segments.AddObject(mySegment);
}
else
{
if (mySegment.EntityState == System.Data.EntityState.Detached)
{
myObjectContext.Segments.Attach(mySegment);
}
myObjectContext.ObjectStateManager.ChangeObjectState(mySegment, System.Data.EntityState.Modified);
myObjectContext.Segments.ApplyCurrentValues(mySegment);
}
}
}
result = (myObjectContext.SaveChanges(SaveOptions.None) != 0);
return result;
}
*EDIT****
Based on some of the feedback below, I have modified the "Save" method. The new method implementation is below. However, it still does not delete Segments that have been removed from the myTreatments.Segments collection.
public bool Save(Treatment myTreatment, tamcEntities myObjectContext)
{
bool result = false;
if (myObjectContext != null)
{
if (myTreatment.Treatment_ID == 0)
{
myObjectContext.Treatments.AddObject(myTreatment);
}
else
{
if (myTreatment.EntityState == System.Data.EntityState.Detached)
{
myObjectContext.Treatments.Attach(myTreatment);
}
myObjectContext.ObjectStateManager.ChangeObjectState(myTreatment, System.Data.EntityState.Modified);
}
foreach (Segment mySegment in myTreatment.Segments)
{
if (mySegment.SegmentID == 0)
{
myObjectContext.ObjectStateManager.ChangeObjectState(mySegment, System.Data.EntityState.Added);
}
else
{
myObjectContext.ObjectStateManager.ChangeObjectState(mySegment, System.Data.EntityState.Modified);
}
}
}
result = (myObjectContext.SaveChanges(SaveOptions.None) != 0);
return result;
}
FINAL EDIT
I have finally got it to work. Here is the updated Save method that is working properly. I had to save the initial list of Segments in a local variable and then compare it to the myTreatments.Segments list after it was attached to the DB, to determine a list of Segments to be deleted, and then iterate over that list and delete matching Segments from the newly attached myTreatment.Segments list. I also removed the passing in of the objectcontext per advice from several responders below.
public bool Save(Treatment myTreatment)
{
bool result = false;
List<Segment> myTreatmentSegments = myTreatment.Segments.ToList<Segment>();
using (tamcEntities myObjectContext = new tamcEntities())
{
if (myTreatment.Treatment_ID == 0)
{
myObjectContext.Treatments.AddObject(myTreatment);
}
else
{
if (myTreatment.EntityState == System.Data.EntityState.Detached)
{
myObjectContext.Treatments.Attach(myTreatment);
}
myObjectContext.ObjectStateManager.ChangeObjectState(myTreatment, System.Data.EntityState.Modified);
}
// Iterate over all the segments in myTreatment.Segments and update their EntityState to force
// them to update in the DB.
foreach (Segment mySegment in myTreatment.Segments)
{
if (mySegment.SegmentID == 0)
{
myObjectContext.ObjectStateManager.ChangeObjectState(mySegment, System.Data.EntityState.Added);
}
else
{
myObjectContext.ObjectStateManager.ChangeObjectState(mySegment, System.Data.EntityState.Modified);
}
}
// Create list of "Deleted" segments
List<Segment> myDeletedSegments = new List<Segment>();
foreach (Segment mySegment in myTreatment.Segments)
{
if (!myTreatmentSegments.Contains(mySegment))
{
myDeletedSegments.Add(mySegment);
}
}
// Iterate over list of "Deleted" segments and delete the matching segment from myTreatment.Segments
foreach (Segment mySegment in myDeletedSegments)
{
myObjectContext.ObjectStateManager.ChangeObjectState(mySegment, System.Data.EntityState.Deleted);
}
result = (myObjectContext.SaveChanges(SaveOptions.None) != 0);
}
return result;
}

Maybe I am missing something, but to me this code looks overly cumbersome.
Please bear with me, if I am on the wrong track here and misunderstood you.
Regarding the objects that should be deleted, I suggest that you store these in a separate collection that only holds the deleted items. You can the delete them from the ObjectContext.
Instead of calling ApplyCurrentValues I would, I would simply call myObjectContext.SaveChanges(). ApplyCurrentValues has, in this case, the downside that it does not take care of any other entity that has relations to the one you are saving.
MSDN documentation:
Copies the scalar values from the supplied object into the object in
the ObjectContext that has the same key.
As the other Segments are already attached to your Treatment, by using SaveChanges(), they will be added to the context automatically or updated if they were already added.
This should make all that manual handling of the EntityStates unnecessary.
EDIT:
Now I see where this is going...
Somewhere in you you code - outside of this Save() method - you are deleting the Segment instances. The trouble lies in the problem that your ObjectContext is totally unaware of this. And how should it be...?
You may have destroyed the instance of a certain Segment entity, but as the entities are detached, this means that they have no connection to the ObjectContext. Therefore the context has absolutely no idea of what you have done.
As a consequence, when you attach the treament to it, the context still believes that all Segments are alive because it does not know about the deletion and adds them again to the Treatment like nothing ever happened.
Solution:
Like I already said above, you need to keep track of your deleted entities.
In those spots, where you delete the Segments, do not actually delete them, but:
Remove() it from the Treatment instance.
Move the "deleted" Segment into a collection, e.g. List<Segment>. Let's call it deletedSegments.
Pass the deletedSegments collection into the Save() method
Loop through this collection and ObjectContect.Delete() them.
Do the rest of the remaining save logic as necessary.
Also, like Tomas Voracek mentioned, it is preferable to use contexts more locally. Create it only within the save method instead of passing it as an argument.

Ok try again.
When you say "remove" do you mean mark as deleted.
You are calling ChangeObjectState to change the state to modified.
So if you send 3 down, one deleted, one modified and one unchanged; then all will get marked as modified before save changes is called.

Related

PUN 2 Getting Custom Properties

I've recently taken on the task of custom properties in Photon. I have been able to figure out how to set the custom properties, but not get the custom properties. My hashtable is in my player controller script, while the place where I set (and where I want to get) properties is in a round loop script.
From RoundSystem:
private IEnumerator TeamBalance()
{
angelCount = Mathf.Floor(PhotonNetwork.PlayerList.Length * angelPercent);
currentAngels = angelCount;
currentPlayers = PhotonNetwork.PlayerList.Length;
foreach (var item in PhotonNetwork.PlayerList)
{
var itemPhotonView = (PhotonView)item.TagObject;
itemPhotonView.RPC("SetPlayerTeam", item, citiString);
}
for (int i = 0; i < angelCount;)
{
var item = PhotonNetwork.PlayerList[Random.Range(0, PhotonNetwork.PlayerList.Length)];
var itemPhotonView = (PhotonView)item.TagObject;
if (/* random player selected's, AKA, item's team == citiString */)
{
itemPhotonView.RPC("SetPlayerTeam", item, angelString);
i++;
}
}
yield return null;
//the reason this is in an IEnumerator with 'yield return null'
//is because I plan to add a waiting period once I figure this out
//it's for the game loop
}
From PlayerController:
[PunRPC]
public void SetPlayerTeam(string teamString)
{
//in the class: private ExitGames.Client.Photon.Hashtable playerProperties;
if (!playerProperties.ContainsKey("team"))
{
playerProperties.Add("team", teamString);
}
playerProperties["team"] = teamString;
PhotonNetwork.LocalPlayer.SetCustomProperties(playerProperties);
}
At the beginning of the round, a percentage (in this case 1/3) of players are chosen to be an "angel". The check here is needed because in cases of multiple angels, you don't want an already existing angel to count as a new change. (Also, it's probably important to known generally how to get custom properties if I'm going to be using them.) If I don't include the check in RoundSystem, the outcome is 2 citizens and 1 angel (in a test with 3 players). Also, if you see any spaghetti code that could be improved on, please don't hesitate to tell me. :)
Use Player.CustomProperties dictionary to access player's custom properties.
foreach (var item in PhotonNetwork.PlayerList)
{
if (item.CustomProperties.ContainsKey("team"))
{
Debug.Log(item.CustomProperties["team"]);
}
}
Also, the RoundSystem can implement IInRoomCallbacks interface and listen to OnPlayerPropertiesUpdate to catch the exact moment when the team gets updated. https://doc-api.photonengine.com/en/pun/v2/interface_photon_1_1_realtime_1_1_i_in_room_callbacks.html

How to reload collection in EF Core 2.x?

I know there is a Load method.
_dbContext.Entry(blog).Collection(b => b.Posts).Load()
But I'm try to handle concurrency conflicts, I've been add a post into blog.Posts. if call Load, it do not clear the blog.Posts, just append the existing Posts to it.
I had try:
blog.Posts = null;
_dbContext.Entry(blog).Collection(b => b.Posts).Load()
But blog.Posts become a empty collection (Zero Count).
So I want a Reload.
Unfortunately although EntityEntry has Reload method, there is no such method for ReferenceEntry and CollectionEntry (or in general, for NavigationEntry which the base of the previous two). And Reload methods refreshes just the primitive properties, so it can't be used to refresh the navigation properties.
Fortunately it's not that hard to create a custom one. It needs to detach (or reload?) all the current collection items, set IsLoaded to false and CurrentValue to null before calling Load.
Something like this (put it in a static class of your choice and add the necessary usings):
public static void Reload(this CollectionEntry source)
{
if (source.CurrentValue != null)
{
foreach (var item in source.CurrentValue)
source.EntityEntry.Context.Entry(item).State = EntityState.Detached;
source.CurrentValue = null;
}
source.IsLoaded = false;
source.Load();
}
so you can use the desired
_dbContext.Entry(blog).Collection(b => b.Posts).Reload();

CRCase_RowSelecting fires with next case on subsequent save

I created an extension for CRCaseMaint, and added the event CRCase_RowSelecting. Here is the code I am currently using:
protected virtual void CRCase_RowSelecting(PXCache sender, PXRowSelectingEventArgs e)
{
CRCase row = e.Row as CRCase;
if (row == null) return;
PXDatabase.ResetSlot<List<CRCase>>("OriginalCase");
List<CRCase> originalCaseSlot = PXDatabase.GetSlot<List<CRCase>>("OriginalCase");
if (originalCaseSlot.Count == 0)
{
originalCaseSlot.Add(sender.CreateCopy(row) as CRCase);
}
else
{
originalCaseSlot[0] = sender.CreateCopy(row) as CRCase;
}
}
When I first open a case, this event will fire a couple times, and the last time it fires, the current case is correctly stored in e.Row, so this code works great. When I click Save, I have a RowPersisting event that compares the case stored in the originalCaseSlot with the updated case. At the end, it sets the original case slot to the updated case. This also works well.
However, when I make another change without leaving the case, and click save, e.Row on the RowSelecting event now has the next case stored on it rather than the current case. Since I am not touching the next case in any way, I am surprised that this is happening.
My question is, should I be using a different event instead of RowSelecting, or is there something else I am missing?
Thank you all for your help.
Sometimes when the primary record gets updated or the user clicks on a form toolbar button, the framework selects 2 records from database: the current primary record and the next one. This is why RowSelecting is invoked 2nd time for the next CRCase record.
Honestly, using PXDatabase Slots to store user session-specific records is not a good idea. PXDatabase Slots are shared among all user sessions and should only be used to cache frequently used data from database, which is not prone to frequent updates. This makes the main purpose of PXDatabase Slots to reduce number of database queries to widely and very often used configurable data, like Segment Key or Attribute configurations.
With that said, using the RowSelecting handler is definitely a step in the right direction. Besides, the RowSelecting handler, you should additionally define a separate PrevVersionCase data view to store the original CRCase record(s) and also override the Persist method to report about changes. The Locate method used on PXCache objects searches the cache for a data record that has the same key fields as the provided data record. This approach allows to compare changes between the originally cached and modified CRCase records having identical key field values.
public class CRCaseMaintExt : PXGraphExtension<CRCaseMaint>
{
[Serializable]
public class CRPrevVersionCase : CRCase
{ }
public PXSelect<CRPrevVersionCase> PrevVersionCase;
protected virtual void CRCase_RowSelecting(PXCache sender, PXRowSelectingEventArgs e)
{
CRCase row = e.Row as CRCase;
if (row == null || e.IsReadOnly) return;
var versionCase = new CRPrevVersionCase();
var versionCache = PrevVersionCase.Cache;
sender.RestoreCopy(versionCase, row);
if (versionCache.Locate(versionCase) == null)
{
versionCache.SetStatus(versionCase, PXEntryStatus.Held);
}
}
[PXOverride]
public void Persist(Action del)
{
var origCase = Base.Case.Current;
var origCache = Base.Case.Cache;
CRPrevVersionCase versionCase;
if (origCache.GetStatus(origCase) == PXEntryStatus.Updated)
{
versionCase = new CRPrevVersionCase();
origCache.RestoreCopy(versionCase, origCase);
versionCase = PrevVersionCase.Cache.Locate(versionCase) as CRPrevVersionCase;
if (versionCase != null)
{
foreach (var field in Base.Case.Cache.Fields)
{
if (!Base.Case.Cache.FieldValueEqual(origCase, versionCase, field))
{
PXTrace.WriteInformation(string.Format(
"Field {0} was updated", field));
}
}
}
}
del();
if (origCase != null)
{
PrevVersionCase.Cache.Clear();
versionCase = new CRPrevVersionCase();
Base.Case.Cache.RestoreCopy(versionCase, origCase);
PrevVersionCase.Cache.SetStatus(versionCase, PXEntryStatus.Held);
}
}
}
public static class PXCacheExtMethods
{
public static bool FieldValueEqual(this PXCache cache,
object a, object b, string fieldName)
{
return Equals(cache.GetValue(a, fieldName), cache.GetValue(b, fieldName));
}
}

Linked list Advice

I've been given a task as follows:
I'd like to to build an implementation of a Linked List. Specifically I'd like it to be a doubly linked list.
My task:
Your program should use the linked list to model a train route using the linked list.
First the user will enter as many stops as they'd like the train to have, and the name of each stop.
The program should then print a map of the route.
Once finished they then enter the name of the stop they want to start at.
From there they can enter commands to move the train either forward to the next stop or backward to the previous one.
I've been told I'm not doing this task right but I don't really understand how not, I'd appreciate it if someone could explain what I'm not doing that I should be doing.
My Route class (it isn't finished but it would've been nearly finished if it was done correctly):
namespace TrainRoute
{
class Route
{
Stops root;
public LinkedList<Stops> linkedList = new LinkedList<Stops>();
public Stops MakeNewStop(string stopName)
{
Stops stopWithStopName = new Stops(stopName);
return stopWithStopName;
}
public void AddStops(Stops stopIWantToAdd)
{
if (linkedList.Count == 0)
{
linkedList.AddFirst(stopIWantToAdd);
}
else
{
//stopIWantToAdd.prevStop = linkedList.Last();
linkedList.AddLast(stopIWantToAdd);
}
}
public void StopRelationships()
{
for (int i = 0; i < linkedList.Count; i++)
{
if (linkedList.ElementAt<Stops>(i).nextStop == null && linkedList.ElementAt<Stops>((i + 1)) != null)
{
linkedList.ElementAt<Stops>(i).nextStop = linkedList.ElementAt<Stops>((i + 1));
}
if (linkedList.ElementAt<Stops>((i - 1)) != null)
{
linkedList.ElementAt<Stops>(i).prevStop = linkedList.ElementAt<Stops>(i - 1);
}
}
}
public void Print()
{
if (linkedList != null)
{
foreach (var item in linkedList)
{
Console.WriteLine("Stop name: " + item.stopName);
}
}
}
public int StopPosition(string usersInput)
{
int position = 0;
for (int i = 0; i < linkedList.Count; i++)
{
if (linkedList.ElementAt<Stops>(i).stopName == usersInput)
{
position = i;
break;
}
}
return position;
}
public int MoveForward(int indexPosition)
{
Console.WriteLine("The train is now at " +linkedList.ElementAt<Stops>(indexPosition).nextStop.stopName);
return (indexPosition + 1);
}
public int MoveBackwords(int indexPosition)
{
Console.WriteLine("The train is now at " + linkedList.ElementAt<Stops>(indexPosition).prevStop.stopName);
return (indexPosition - 1);
}
public bool VerifyRoute(int indexPosition, string prevOrForward)
{
if (prevOrForward.Contains("forward"))
{
if (linkedList.ElementAt<Stops>((indexPosition+1)) != null)
{
return true;
}
}
else
{
if (linkedList.ElementAt<Stops>((indexPosition-1)) != null)
{
return true;
}
}
return false;
}
}
}
I'm also not allowed to use the Linked list class but I'm to use a linked list (I'm not 100% sure what that means).
Any and all advice/help provided will be appreciated!
Let's piece together the breadcrumbs here:
I'd like to to build an implementation of a Linked List.
and this:
I'm also not allowed to use the Linked list class
Obviously the task here is for you to implement your own linked list (class), and not to use the existing one provided by .NET.
I'm assuming here the task is not to build the program handling the trains, but instead to learn how a linked list works, and how you would go about implementing one.
As such, your shortcut to simply grab the existing class is the wrong tool for the job. It would be perfect (probably) if your task was to build that program, but in this case the program is orthogonal to your task, it's there to create a context for what you're really asked to do:
Implement your own version of LinkedList<T> (though you probably don't need to make it generic).
Wikipedia has a very good article on linked lists if you're stumped on how such a data structure really works. There's undoubtedly other very good resources out on the net as well, and probably in your text book or other resources.
Additionally, I would urge you to find a classmate to peer with, from experience I can say that most of the really hard problems I've had in my programming career has (usually) been solved by having a sparring partner to work with.
Implementing a linked list isn't that difficult. I assume you have a textbook and it discusses linked list, read it, carefully. Also you want to clarify with your tutor exactly how much functionality your linked list needs to implement.
Basically, you'll start with a node class, if you don't need it to be generic, then you can create a StopNode class. The basics of your node class will be a reference to the next node in the list and, since this is a doubly linked list, a reference to the previous node:
public class StopNode
{
public StopNode Next { get; set; }
public StopNode Previous { get; set; }
// whatever other properties your stop class needs - such as name
}
Now your LinkedList class will manage the collection of stop nodes. It will need to keep a reference to the first or "head" node and probably the last node too (or "tail").
public class StopLinkedList
{
private StopNode Head { get; }
private StopNode Tail { get; }
}
And it will need to implement methods to add and remove nodes (at a minimum) and probably also insert.
Add is pretty easy - check if Head is null. If it is, just set Head and Tail both equal to your new node. If it's not, you will instead set the Next property of your Tail to your new node, then set the Previous of your new node to the Tail and then finally update your Tail to reference your new node.
To remove a node, if given the node to remove, you will need to check it's Previous and Next properties and (assuming one or both isn't null - you'll need to add logic for that), you set your nodes Previous.Next to your nodes Next and your nodes Next.Previous to your nodes Previous. This will cause your node to fall out of the list (you can set your nodes Next and Previous to null if required, but it's not strictly necessary unless your removed node is going to hang around).
Hopefully that gets you started. Clarify with your tutor, check your textbook (probably better to try and match their terminology if it differs from mine) and also search for "linked list" and "doubly linked list" on the internet. You should find plenty of resources.

Binding objects and cancel button

I've just read this question:
How to cancel an edit to an object using MVVM?
I have the exact same question and would like to have a simple solution. The first one looked very promising, however, I'm using entity framework and my classes are automatically generated, so that's not an option.
How can I do this easily with EF?
EDIT:
My ViewModel:
public List<Player> Players
{
get { return repository.Players.OrderBy(x => x.Firstname).ToList(); }
}
public Player CurrentPlayer
{
get { return currentPlayer; }
set
{
if (currentPlayer != value)
{
currentPlayer = value;
RaisePropertyChanged("CurrentPlayer");
}
}
}
Players is bound to a datagrid, CurrentPlayer to the selecteditem of that. Below the datagrid, I have textboxes where the user can edit the player info.
When the user presses the save button, this code is executed:
private void SaveExecute(object parameter)
{
repository.SavePlayer(currentPlayer);
Editing = false;
}
Very easy. When the user presses the cancel button, this is executed:
private void CancelExecute(object parameter)
{
if (currentPlayer.Id == 0) // id = 0 when a new player is being added
{
CurrentPlayer = null;
}
else
{
// here, the CurrentPlayer should be set back to it's previous state.
}
Editing = false;
}
CurrentPlayer is an object of Player, which is an entity class generated by EF.
I don't understand the problem. If the user is editing a new item (State == ObjectState.Added) then you discard that, (and maybe set the CurrentPlayer to what it was before pressing the "New" Button?), else just retrieve the entity from the database again and that's it...
A better way to solve this problem is to have your CRUD and your List VMs have separate instances of the entity.
For example, when I create a List view (Datagrid or otherwise), usually the data displayed in that is just a subset of the whole data displayed in the full CRUD View. So, in order to show the entity in the CRUD, I need to Get() the entity again using the necessary Includes. This resolves the whole cancel problem, because the entity instance you're modifying is actually not the same as the one shown in the List view. If the user presses Save, you can replace the instance shown in the list view with the edited one, and if the user presses cancel, don't do anything.
Edit: Also be aware that if your entities are being generated by a T4 Template such as the Entity Framework STE Template, you can modify the .tt file and customize it to generate whatever code you need in your entities.

Categories