I'm getting the classic error:
'deleted object would be re-saved by cascade
(remove deleted object from associations)[DrillingContracts.Domain.TrackedField#3216'
But with an added twist, The error is originating entirely because I'm deleting one entity and adding a NEW one.
I empty all the pre-existing children with this method
public void RemoveChildren(TrackedNode parentNode)
{
foreach (TrackedField trackedField in parentNode.ChildNodes)
{
_trackedFieldRepository.Delete(trackedField);
parentNode.RemoveChildNode(trackedField);
}
}
Then add the new ones immediately next
public virtual void AddTrackedChildFieldsToTrackedCell(
params TrackedField[] nodes)
{
foreach (var field in nodes)
{
if (IsPath(field.Name))
{
throw new InvalidTrackedFieldNameException(
"The value " + field.Name + " is not a valid tracked field name.");
}
field.Supplement = this;
_trackedFields.Add(field);
}
}
For those of you who want to know what is happening in the repository the best answer I have is magic. 10,000% magic. The original developer used NCommon. Both of these methods are called from a method wrapped in the NCommon.UnitOfWork attribute.
It should be noted that each method performs as expected on its own. (IE, no fields to delete the add works, and likewise, no fields to add the delete works.)
EDIT
[HttpPost]
[UnitOfWork(Scope = FilterScope.Result)]
public ActionResult SaveEditMode(long id, AddTrackedRowViewModel model, string editMode, List<string> elementNames, string provisionData)
{
var supplement = _supplementCoordinator.GetSupplement(id);
var table = supplement.TrackedTables.First(x => x.Name == model.Name);
var valueAttributes = JsonSerializer.DeserializeFromString<List<ValueAttributeViewModel>>(provisionData);
foreach (var prop in valueAttributes)
{
supplement.Set(prop.Attribute, prop.Value, false);
}
var cell = table.TrackedRows.First(x => x.Ordinal == model.Ordinal).TrackedCells.First(x => x.Name == "Detail");
_supplementCoordinator.RemoveChildren(cell);
if (elementNames != null)
{
var childNodes = elementNames.Select((t, i) => new TrackedField(cell, t, i)).ToList();
supplement.AddTrackedChildFieldsToTrackedCell(childNodes.ToArray());
}
return SwitchEditMode(model, editMode, table);
}
Answer
public void AddChildren(Supplement supplement, TrackedNode parentNode, params TrackedField[] nodes)
{
foreach (TrackedField trackedField in nodes)
{
parentNode.AddChildNode(trackedField);
}
supplement.AddTrackedChildFieldsToTrackedCell();
}
I wasn't getting them added as new entitles to the parent node, just the associated supplement.
Just for completeness. The message 'deleted object would be re-saved..' was caused by the explicit call session.Delete(instance).
In this case, we only need to 1) remove such item from the old parent collection and 2) append it to new parent. The cascade mapping and session.Flush() will properly upate DB.
Final note: such a "movement" must be done inside one session/transaction, to avoid other issues (e.g 'Row was updated or deleted by another transaction...').
Related
After removing one TreeNode I want the View to be at the node after the one I deleted (the same functionality should later also be implemented for editing a node), but currently it always shows the top of the list again and I need to scroll down manually, which can be quite irritating for the user.
I'm using the EnsureVisible() method but unfortunately it doesn't work (I'm testing it with a TreeView which contains about 30 nodes that don't have sub-nodes).
The code of the function (Only the first line and the last 4/5 lines are relevant, I think):
public override void Remove()
{
TreeNode moveToThisNode = treeControl1.SelectedNodes.Last().NextVisibleNode;
// first, group them by whether they are ObjectGroups or ObjectTypes
var grouping = from node in treeControl1.SelectedNodes
where !node.IsUngroupedNode()
let isGroup = node.IsGroupNode()
group node by isGroup into g
select new { IsGroupNode = g.Key, Items = g };
foreach (var grp in grouping)
{
foreach (var selectedNode in grp.Items)
{
// Only allow removal FIRSTLY of ObjectGroups and SECONDLY that are NOT the "ungrouped" group.
if (grp.IsGroupNode)
{
// Removes the Group
var cmd = (Commands.DataCommand<string>)mApplicationData.CommandFactory.Create(string.Concat(CommandPrefix, "Remove"));
cmd.Data = selectedNode.Text;
cmd.Execute();
}
else // ObjectType node --> just move to ungrouped
{
var t = (ObjectType)selectedNode.Tag;
var parentNode = selectedNode.Parent;
if (parentNode?.IsGroupNode() == true &&
parentNode?.IsUngroupedNode() == false) // No removal of ObjectTypes from within "ungrouped"
{
var group = (ObjectGroup)parentNode.Tag;
// Remove the ObjectType from the ObjectGroup but does not delete it -> becomes "ungrouped".
var cmd = (Commands.IGroupTypeCommand)mApplicationData.CommandFactory.Create(string.Concat(CommandPrefix, "TypeRemove"));
cmd.ObjectClass = t.Class;
cmd.ObjectTypeName = t.Name;
cmd.Data = group.Name;
cmd.Execute();
}
}
}
}
UpdateData();
if (moveToThisNode!=null)
{
moveToThisNode.EnsureVisible();
MessageBox.Show("Dummy test if moveToThisNode isn't null");
}
}
I figured it out!
The problem was that after the Remove() function my UpdateData() function is called which redraws all nodes. So calling EnsureVisible() before that is total nonsense.
So what I did was that in the Remove() I stored the name of the node I want to jump to in a member variable and at the end of UpdateData() I get the node with that name out of the TreeNodeCollection and (FINALLY) call EnsureVisible() for it.
My object looks something like this:
public class Transaction
{
long Id;
long Amount;
DateTime Date;
string ReferenceNumber;
string ParentReferenceNumber;
}
It is already coming in sorted by Date, but what I need to do is arrange the list of these transactions so that those having a ParentReferenceNumber that matches the ReferenceNumber on another Transaction appear in the order of "Parent then Child".
Here is what I tried. It produces the error "Collection was modified; enumeration operation may not execute." That's what I was afraid of, hence the question.
foreach (var p in Model.PaymentInfo)
{
var child = Model.PaymentInfo.SingleOrDefault(x => x.ParentReferenceNumber == p.ReferenceNumber);
if (child != null)
{
var parentIndex = Model.PaymentInfo.IndexOf(p);
var childIndex = Model.PaymentInfo.IndexOf(child);
Model.PaymentInfo.RemoveAt(childIndex);
Model.PaymentInfo.Insert(parentIndex + 1, child);
}
}
Start by adding a list of children to each node. For example:
public class WorkingTransaction
{
public Transaction Details;
public List<WorkingTransaction> Children;
public bool HasParent;
}
Then, create a dictionary of WorkingTransactions, keyed on Id. Here, transactions is your sorted list of transactions.
var workDict = transactions
.Select(t => new WorkingTransaction
{
Details = t,
Children = new List<WorkingTransaction,
HasParent = false
})
.ToDictionary(t => t.Details.Id, t);
Now, go through the dictionary and add those that have parent references to the parent's list of children, and update the HasParent flag. At the same time, any item that doesn't have a parent is added to the "root level" list.
List<WorkingTransaction> rootParents = new List<WorkingTransaction>();
foreach (var kvp in workDict)
{
WorkingTransaction parent;
if (workDict.TryGetValue(kvp.Value.Details.ParentReferenceNumber, out parent)
{
parent.Children.Add(kvp.Value);
kvp.Value.HasParent = true;
}
else
{
rootParents.Add(kvp.Value);
}
}
Now, you can go through each of the parents and process each of the children:
foreach (var t in rootParents)
{
processChildren(t.Children);
}
void ProcessChildren(WorkingTransaction t)
{
t.Children = t.Children.OrderBy(child => child.Details.Id).ThenBy(child => child.Details.Date);
// and recursively process the children of this transaction
foreach (var child in t.Children)
{
ProcessChildren(child);
}
}
You can use a similar recursive method to output the children in the proper order.
I had to create a new list in order to implement this in a simple way. I didn't want to change the client's existing object structure. Here's how I accomplished it:
var sortedList = new List<Transaction>();
foreach (var p in ListOfTransactions)
{
if (sortedList.Contains(p))
{
continue; // Don't add duplicates
}
sortedList.Add(p); // Add the transaction
var child = ListOfTransactions.SingleOrDefault(x => x.ParentReferenceNumber == p.ReferenceNumber);
if (child != null) // Add the child, if exists
{
sortedList.Add(child);
}
}
You are looking for information on an object sort. Look here for more information.
http://www.codeproject.com/Tips/761292/How-to-sort-an-object-list-in-Csharp
list.Sort(delegate(Member x, Member y)
{
// Sort by total in descending order
int a = y.Total.CompareTo(x.Total);
// Both Member has the same total.
// Sort by name in ascending order
a = x.Name.CompareTo(y.Name);
return a;
});
I need confirmation of my approach for this, I'm using EF and ASP.NET MVC and I'm trying to remove entities based on user selection (i.e based on what they have checked/unchecked).
To do this I'm looking at the Ids that are passed from the form from the checkboxes, matching what I have in the database and then first adding any which are new and then removing any which don't match.
Following is the code that I originally had:
[HttpPost]
public ActionResult Edit(int id, FormCollection collection, VMinstanceRole vmodel)
{
try
{
var instancerole = db.instanceRoles.Find(id);
if (ModelState.IsValid)
{
UpdateModel<instanceRole>(instancerole, "instanceRole");
var keys = instancerole.rights.Select( c => c.Id);
foreach (var pid in vmodel.selectedId.Except(keys))
{
var right = new right { Id = pid };
db.rights.Attach(right);
instancerole.rights.Add(right);
}
foreach (var pid in keys.Except(vmodel.selectedId))
{
var right = instancerole.rights.Where(c => c.Id == pid).Single();
instancerole.rights.Remove(right);
}
db.SaveChanges();
}
// TODO: Add update logic here
return RedirectToAction("Index");
}
catch (InvalidCastException e)
{
return View();
}
}
However, the following error was presented "Collection was modified; enumeration operation may not execute."
So to try and resolve this I decided to keep a seperate list and remove it based on teh list afterwards to overcome the error:
[HttpPost]
public ActionResult Edit(int id, FormCollection collection, VMinstanceRole vmodel)
{
try
{
var instancerole = db.instanceRoles.Find(id);
List<right> removeList = new List<right>();
if (ModelState.IsValid)
{
UpdateModel<instanceRole>(instancerole, "instanceRole");
var keys = instancerole.rights.Select( c => c.Id);
foreach (var pid in vmodel.selectedId.Except(keys))
{
var right = new right { Id = pid };
db.rights.Attach(right);
instancerole.rights.Add(right);
}
foreach (var pid in keys.Except(vmodel.selectedId))
{
var right = instancerole.rights.Where(c => c.Id == pid).Single();
removeList.Add(right);
}
foreach (var right in removeList)
{
instancerole.rights.Remove(right);
}
db.SaveChanges();
}
// TODO: Add update logic here
return RedirectToAction("Index");
}
catch (InvalidCastException e)
{
return View();
}
}
This seems to work, however, I'm not sure whether I've done the right thing. Mainly becuase I'm doing another loop. Is there a better way to approach this or is this good enough ?
You found one standard solution. The other solution that works would be to call ToList on the LINQ operation that produces your keys object: doing so would disconnect keys from instanceroles collection, allowing for arbitrary independent modifications on the original collection.
Try this:
foreach (var pid in keys.Except(vmodel.selectedId).ToList())
{
var right = instancerole.rights.Where(c => c.Id == pid).Single();
instancerole.rights.Remove(right);
}
Enumerator you enumerate in foreach loop will be already disposed by the moment you delete your first item.
The reason for not being able to edit a collection when enumerating with foreach is well enough documented here alone (just check the 'related' links to the side), and in knowing you can't do that, you could use a simple for loop and amend to index upon removal of an item - this allows you to maintain one loop.
for (int i = 0; i < max; i++) {
//if removing an item
//manipulate the index as desired...
i--;
}
I tried to add some new items to entityset by creating new instance and add it using AddTo method then I called SaveChanges() method to update my database but no new recode had been added.
foreach (var newitem in newItemsList)
{
if (newitem.SomeValue > 0)
{
dbcontext.AddToMyEntityType(newitem);
}
}
dbcontext.SaveChanges();
but when I used MyEntityType.CreateMyEntityType() method all new items are saved into my database
foreach (var newitem in newItemsList)
{
if (newitem.SomeValue > 0)
{
MyEntityType _newEntity = MyEntityType.CreateMyEntityType();//I passed the required parameters
dbcontext.AddToMyEntityType(_newEntity);
}
}
dbcontext.SaveChanges();
why????
should I use Create method or there is another ways to do that?
Edit
I forget to mention that newitem have a navigation property(foreign key for a parent table) and I set this property with an a correct parent ID
There must be a difference between newItem and _newEntity on property level. CreateMyEntityType(...) doesn't do more then new MyEntityType and setting the properties you pass in. CreateMyEntityType(...) encourages to set properties for all required fields. Perhaps you set a required property (like a string) which isn't set in newItem. SaveChanges in your first example throws actually an exception because of the missing required field but you hid this exception (by try { ... } catch {} or something). Check in the debugger if SaveChanges passes without exception.
A hypothesis.
foreach (var newitem in newItemsList)
{
if (newitem.SomeValue > 0)
{
dbcontext.AddObject("entity set name", newitem);
}
}
dbcontext.SaveChanges();
or
var entitySet = dbcontext.CreateObjectSet<item type>();
foreach (var newitem in newItemsList)
{
if (newitem.SomeValue > 0)
{
entitySet.AddObject(newitem);
}
}
dbcontext.SaveChanges();
In first part you are not initializing the object of entity set
This will work if you initialize the object of entityset by doing some thing like this
foreach (var newitem in newItemsList)
{
if (newitem.SomeValue > 0)
{
MyEntityType obj = new MyEntityType
{
value1 = newitem.somevalue,
value2 = newitem.someothervalue
};
myContext.AddToMyEntityType(obj);
}
}
dbcontext.SaveChanges();
In your seceod part you have deifned the enititySet in which u wany add new object and also initilazes the object of the entiity by this
MyEntityType _newEntity = MyEntityType.CreateMyEntityType();
myContext.AddToMyEntityType(_newEntity);
Here you are telling that add _newEntity to my MyEntityType
AddToMyEntityType is a depreceated method for adding a new object to MyEntityType Entity Set..
Say, I have the following conceptual model, there are strories that have tags (more than one, so it's a many-to-many relationship), plus each tag belongs to a particular category.
My data comes from an external source and before inserting it I want to make sure that no duplicated tags are added.
Updated code snippet:
static void Main(string[] args)
{
Story story1 = new Story();
story1.Title = "Introducing the Entity Framework";
story1.Tags.Add(new Tag { Name = ".net", });
story1.Tags.Add(new Tag { Name = "database" });
Story story2 = new Story();
story2.Title = "Working with Managed DirectX";
story2.Tags.Add(new Tag { Name = ".net" });
story2.Tags.Add(new Tag { Name = "graphics" });
List<Story> stories = new List<Story>();
stories.Add(story1);
stories.Add(story2);
EfQuestionEntities db = new EfQuestionEntities();
Category category = (from c in db.Categories
where c.Name == "Programming"
select c).First();
foreach (Story story in stories)
{
foreach (Tag tag in story.Tags)
{
Tag currentTag = tag;
currentTag = GetTag(tag.Name, category, db);
}
db.Stories.AddObject(story);
}
db.SaveChanges();
}
public static Tag GetTag(string name, Category category, EfQuestionEntities db)
{
var dbTag = from t in db.Tags.Include("Category")
where t.Name == name
select t;
if (dbTag.Count() > 0)
{
return dbTag.First();
}
var cachedTag = db.ObjectStateManager.GetObjectStateEntries(EntityState.Added).
Where(ose => ose.EntitySet == db.Tags.EntitySet).
Select(ose => ose.Entity).
Cast<Tag>().Where(x => x.Name == name);
if (cachedTag.Count() != 0)
{
return cachedTag.First();
}
Tag tag = new Tag();
tag.Name = name;
tag.Category = category;
db.Tags.AddObject(tag);
return tag;
}
However, I get an exception about an object with the same EntityKey that is already present in the ObjectContext.
Also, if I remove the else statement I will get an exception about violating an FK constraint, so it seems like its Category attribute is set to null.
I 've had the same problem with EF. Here's what I ended up doing:
Instead of doing story1.Tags.Add(new Tag { Name = ".net", }) yourself, routed all Tag creation through a helper method like this: story1.Tags.Add(GetTag(".net")).
The GetTag method checks the tags in the context to see if it should return an existing entity, like you do. If it does, it returns that.
If there is no existing entity, it checks the ObjectStateManager to see if there are Tag entities added to the context but not already written to the db. If it finds a matching Tag, it returns that.
If it still has not found the Tag, it creates a new Tag, adds it to the context, and then returns it.
In essence this will make sure that no more than one instance of any Tag (be it already existing or just created) will be used throughout your program.
Some example code lifted from my project (uses InventoryItem instead of Tag, but you get the idea).
The check in step 3 is done like this:
// Second choice: maybe it's not in the database yet, but it's awaiting insertion?
inventoryItem = context.ObjectStateManager.GetObjectStateEntries(EntityState.Added)
.Where(ose => ose.EntitySet == context.InventoryItems.EntitySet)
.Select(ose => ose.Entity)
.Cast<InventoryItem>()
.Where(equalityPredicate.Compile())
.SingleOrDefault();
if (inventoryItem != null) {
return inventoryItem;
}
If the Tag is not found in step 3, here's the code for step 4:
inventoryItem = new InventoryItem();
context.InventoryItems.AddObject(inventoryItem);
return inventoryItem;
Update:
It should be used like this:
Story story1 = new Story();
story1.Title = "Introducing the Entity Framework";
story1.Tags.Add(GetTag(".net", category, db));
story1.Tags.Add(GetTag("database", category, db));