How to reload collection in EF Core 2.x? - c#

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();

Related

Sitecore How to save items in different languages when saved via pipeline

Using the Sitecore item:saved event handler, I am trying to add items in other languages. So if you say, add a item in one language, it should automatically add the item in the other given languages when saved.
Right now, I can get all the languages from the master database. The problem is when adding a new version of the new language, it triggers itself and therefore recursive adds "infinite" new elements until it crashes. How is it possible to bypass this?
public void OnItemSaved(object sender, EventArgs args)
{
Item savedItem = Event.ExtractParameter(args, 0) as Item;
if (savedItem.Versions.Count == 1)
{
// Allow only non null items and allow only items from the master database |
if (savedItem != null && savedItem.Database.Name.ToLower() == "master")
{
// Do some kind of template validation to limit only the items
if (savedItem.TemplateID == ID.Parse("{template id}"))
{
// Get all the installed languages from the master database
var installedLanguages = LanguageManager.GetLanguages(Database.GetDatabase("master"));
// Go through every language in the list
foreach (var language in installedLanguages)
{
// Copy item to the new language
if (savedItem.Language.Name != language.Name)
{
using (new LanguageSwitcher(language.Name))
{
// Save the new item, but it fails since it triggers itself (the event)
savedItem.Versions.AddVersion();
}
}
}
}
}
}
Try this code:
foreach (var language in installedLanguages)
{
if (savedItem.Language.Name != language.Name)
{
var otherLanguageItem = savedItem.Database.GetItem(savedItem.ID, language);
if (otherLanguageItem.Versions.Count == 0)
{
otherLanguageItem.Versions.AddVersion();
}
}
}
It's been quite a while since I have done any Sitecore work and I don't currently have access to a Sitecore instance for testing out a code sample, but one way to approach this would be to put a collection of updated item ids on the context. The approach (in pseudo code) could be inserted immediately after your template check (to keep less expensive checks earlier than more expensive ones) and would go something like:
- retrieve collection of updated items from context or create
- if collection contains current item id, return immediately
- add current item id to collection
I'd be happy to provide more clarity on how this would look syntactically if needed, but honestly if I write something in C# I prefer to be able to actually see it work

Why does my sort behavior fire with only one item in the list, and only the first time the collection is added to?

This question is a result of the fix to this problem. After getting the sort behavior to properly sort the ObservableCollection, I now notice that the first time the collection is added to, the CustomSorter handler fires with only the first item in it, and that same item is passed in as both parameters to the method. That is producing a duplicate item in the list.
Here are the pertinent parts of the view model code:
public ObservableCollection<PermissionItemViewModel> PermissionItems { get; private set; }
private void FetchRoleData()
{
PermissionItems.Clear();
if (SelectedRole != null)
{
using (var context = new myDataContext(new myDbFactory().GetConnectionString()))
{
foreach (PermissionsEnum permission in Enum.GetValues(typeof(PermissionsEnum)))
PermissionItems.Add(new PermissionItemViewModel(permission, SelectedRole[permission]));
}
}
}
All subsequent manipulations of that collection do not do this...it only happens the first time through the FetchRoleData method. Why?
EDIT:
Some additional information. The CustomSort property is set when the CollectionViewSource fires its Filter event (the only event it has AFAIK). I couldn't come up with any better trigger to get it set. The OnAttached override is too soon, as the View member of the CollectionViewSource is not set yet by that point. Catch 22, I guess. That is happening immediately after that first collection item is added. If this is due to the order in which things are being set, then are there any recommendations for a change?
I don't know how or where you're setting up the filter handler. Here's an example of how to set a custom sort on a CollectionViewSource when its View property changes. That's when you want to do it. This assumes that it's in the resources for a Window (or at least someplace where the Window can find it). But once you have cvs, wherever it comes from and however you got your mitts on it, the rest is the same.
public MainWindow()
{
InitializeComponent();
var cvs = FindResource("MyCollectionViewSource1") as CollectionViewSource;
var dpdView = DependencyPropertyDescriptor.FromProperty(
CollectionViewSource.ViewProperty, typeof(CollectionViewSource));
dpdView.AddValueChanged(cvs, (s, e) =>
{
if (cvs.View is ListCollectionView lvc)
{
lvc.CustomSort = new CustomSorter();
}
});
}
I'm baffled by your claim that the first item in the collection is being duplicated. No code you've shown, and no code I've given you, could have that effect. You'll have to share code that demonstrates that issue.

Mark a collection of an entity to be reloaded

I am using EF 6.1 to track changes to all objects inheriting from the abstract base class HistoryData and keep each of its Versions in a collection of a parent object.
They are hooked up like this:
modelBuilder.Entity<Foo>()
.HasMany(f => f.Versions)
.WithRequired(fv => fv.Parent)
.HasForeignKey(fv => fv.ParentID);
Instead of modifying an entry, I hook into the DbContext and do this:
public override async Task<int> SaveChangesAsync()
{
foreach (var entry in ChangeTracker.Entries<HistoryData>().Where(e => e.State == EntityState.Modified))
{
InsertInsteadOfUpdate(entry);
}
return await base.SaveChangesAsync();
}
private void InsertInsteadOfUpdate(DbEntityEntry<HistoryData> entry)
{
// ValidFrom is not set manually -> Set it automatically to now
if (entry.OriginalValues["ValidFrom"].Equals(entry.CurrentValues["ValidFrom"]))
{
entry.Entity.ValidFrom = DateTime.Now;
}
// Insert instead of Update
entry.State = EntityState.Added;
}
This works fine so far and the entity is inserted instead of updated.
However, the new HistoryData entity is not loaded automatically when I call Foo.Versions.
So now I do something like this at the end of InsertInsteadOfUpdate:
// Mark collection of parent as not loaded so that it gets lazy loaded on the next call
var parent = entry.Reference("Parent").CurrentValue;
Entry(parent).Collection("Versions").Load();
That works, but it reloads the Collection instantly. If I insert several HistoryData objects before querying Versions, this is a lot of unnecessary work.
All I really want to do is mark the Collection() of the parent to be reloaded the next time I query it. Is there some way to do this?
I tried
Entry(parent).Collection("Versions").IsLoaded = false;
but for some reason that did not cause the Collection to be lazy loaded on the next call.

How to set a property on a set of controls

So, I am doing this, wanting to be cool:
List<CheckButton> cbuttons = new List<CheckButton>(
new CheckButton[] {
cbShowAll,
cbShowApproved,
cbShowNew,
cbShowQC,
cbShowSent });
cbuttons.ForEach(b => b.LookAndFeel.UseDefaultLookAndFeel = false);
Is there a more cool way to do it?
EDIT:
There might be CheckButtons that won't be affected. There might be multiple property and/or method calls per group.
To make it applicable for many such controls like TextBox or CheckBox, create an extension method on the Form like so:
public static class FormExtensions
{
public static void ChangeAll<T>(this Form form, string propName, object value) where T : Control
{
foreach (Control c in form.Controls.OfType<T>())
{
PropertyInfo myPropInfo = typeof(T).GetProperty(propName);
if (myPropInfo != null)
{
myPropInfo.SetValue(c, value, null);
}
}
}
}
and we can call it:
this.ChangeAll<CheckBox>("Checked", false);
this.ChangeAll<Button>("Text", "DefaultButtonName");
this.ChangeAll<TextBox>("Text", "DefaultText");
where this is Form1 : Form
Unless that list is dynamic, doing it the “boring” way will be likely more efficient:
cbShowAll.LookAndFeel.UseDefaultLookAndFeel = false;
cbShowApproved.LookAndFeel.UseDefaultLookAndFeel = false;
cbShowNew.LookAndFeel.UseDefaultLookAndFeel = false;
cbShowQC.LookAndFeel.UseDefaultLookAndFeel = false;
cbShowSent.LookAndFeel.UseDefaultLookAndFeel = false;
Other than that, you could subclass the CheckButton and make that the default value too.
Also, in general “readable and possibly efficient” is much better than “cool and fancy”.
If you're performing some one-off initialization on a particular subset of controls (rather than all of them), then you could create an array containing that subset and then enumerate the array:
foreach (CheckButton button in
new[] { cbShowAll, cbShowApproved, cbShowNew, cbShowQC, cbShowSent })
{
button.LookAndFeel.UseDefaultLookAndFeel = false;
}
Using a loop allows you to avoid duplicate code (as in poke's answer).
Note: Eric Lippert wrote a blog post explaining why he prefers foreach over ForEach. Excerpt:
[Providing a ForEach extension method] lets you rewrite this perfectly clear code:
foreach (Foo foo in foos) { statement involving foo; }
into this code:
foos.ForEach((Foo foo) => { statement involving foo; });
which uses almost exactly the same characters in slightly different order. And yet the second version is harder to understand, harder to debug, and introduces closure semantics, thereby potentially changing object lifetimes in subtle ways.
Also, not all versions of the .NET Framework support List<T>.ForEach. For example, if you're writing a Windows Store app, you need to use foreach instead.
If you want to do that with all the CheckBoxes you have inside of a form or control, you don't need to hard code the array. If you're hard coding everything, then I agree with poke's answer.
However, you could be totally dynamic and if this is something you want to be reusing in many places, you could have a utility method (or something in a base form or control class that fires after the InitializeComponents() method).
If you want that, you could do something like this:
this.Controls.OfType<CheckBox>()
.ForEach(b => b.LookAndFeel.UseDefaultLookAndFeel = false);
If you don't want to do that with every CheckBox, you could create a subclass of those, and substitute that class name in the OfType generic declaration.

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

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.

Categories