Threading problem when adding items to an ObservableCollection - c#

I'm updating an ObservableCollection of a WPF ViewModel in a WCF Data Service asynchronous query callback method:
ObservableCollection<Ent2> mymodcoll = new ObservableCollection<Ent2>();
...
query.BeginExecute(OnMyQueryComplete, query);
...
private void OnMyQueryComplete(IAsyncResult result)
{
...
var repcoll = query.EndExecute(result);
if (mymodcoll.Any())
{
foreach (Ent c in repcoll)
{
var myItem = mymodcoll.Where(p => p.EntID == c.EntID).FirstOrDefault();
if (myItem != null)
{
myItem.DateAndTime = c.DateAndTime; // here no problems
myItem.Description = c.Description;
...
}
else
{
mymodcoll.Add(new Ent2 //here I get a runtime error
{
EntID = c.EntID,
Description = c.Description,
DateAndTime = c.DateAndTime,
...
});
}
}
}
else
{
foreach (Ent c in repcoll)
{
mymodcoll.Add(new Ent2 //here, on initial filling, there's no error
{
EntID = c.EntID,
Description = c.Description,
DateAndTime = c.DateAndTime,
...
});
}
}
}
The problem is, when a query result collection contains an item which is not present in the target collection and I need to add this item, I get a runtime error: The calling thread cannot access this object because a different thread owns it. (I pointed out this line of code by a comment)
Nevertheless, if the target collection is empty (on initial filling) all items have been added without any problem. (This part of code I also pointed out by a comment). When an item just needs to update some of its fields, there are no problems as well, the item gets updated ok.
How could I fix this issue?

First case: Here you a modifying an object in the collection, not the collection itself - thus the CollectionChanged event isn't fired.
Second case: here you are adding a new element into the collection from a different thread, the CollectionChanged event is fired. This event needs to be executed in the UI thread due to data binding.
I encountered that problem several times already, and the solution isn't pretty (if somebody has a better solution, please tell me!). You'll have to derive from ObservableCollection<T> and pass it a delegate to the BeginInvoke or Invoke method on the GUI thread's dispatcher.
Example:
public class SmartObservableCollection<T> : ObservableCollection<T>
{
[DebuggerStepThrough]
public SmartObservableCollection(Action<Action> dispatchingAction = null)
: base()
{
iSuspendCollectionChangeNotification = false;
if (dispatchingAction != null)
iDispatchingAction = dispatchingAction;
else
iDispatchingAction = a => a();
}
private bool iSuspendCollectionChangeNotification;
private Action<Action> iDispatchingAction;
[DebuggerStepThrough]
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!iSuspendCollectionChangeNotification)
{
using (IDisposable disposeable = this.BlockReentrancy())
{
iDispatchingAction(() =>
{
base.OnCollectionChanged(e);
});
}
}
}
[DebuggerStepThrough]
public void SuspendCollectionChangeNotification()
{
iSuspendCollectionChangeNotification = true;
}
[DebuggerStepThrough]
public void ResumeCollectionChangeNotification()
{
iSuspendCollectionChangeNotification = false;
}
[DebuggerStepThrough]
public void AddRange(IEnumerable<T> items)
{
this.SuspendCollectionChangeNotification();
try
{
foreach (var i in items)
{
base.InsertItem(base.Count, i);
}
}
finally
{
this.ResumeCollectionChangeNotification();
var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
this.OnCollectionChanged(arg);
}
}
}

Related

Realm nested collection updating listview in transaction

I'm having some issues with UI updating within a BeginWrite transaction in Realm .NET / Xamarin.Forms
I have 2 models, ModelA and ModelB. ModelA contains an IList { get; } of ModelB.
I have 2 listview pages, the first populated with bound ModelA. Navigating through an entry takes you to the 2nd page with a listview bound to the ModelBs that ModelA contains and a button to add new ModelBs to ModelA.
If I do this within a Write transaction it will all work as intended. When you add a ModelB to ModelA you can see this in the ModelB list immediately and everything is persisted.
What I want is to do this within a BeginWrite and only commit when a save button is pressed. This works, but the ModelB list UI won't update. Only when navigating back into the page do the ModelB list entries appear.
Can this work the way I want?
Attached is my code from the 2nd page (listview of ModelA.ModelB) viewmodel (FreshMVVM being used).
public class SecondPageModel : FreshMvvm.FreshBasePageModel
{
Transaction _transaction;
public ModelA ModelA { get; set; }
public SecondPageModel() { }
public override void Init(object initData)
{
base.Init(initData);
ModelA = initData as ModelA;
var db = Realm.GetInstance();
_transaction = db.BeginWrite();
}
public Command NewModelB
{
get
{
return new Command(() =>
{
var newB = new ModelB();
newB.Name = "B";
ModelA.ModelBs.Add(newB);
});
}
}
public Command SaveModelA
{
get
{
return new Command(async () =>
{
_transaction.Commit();
await CoreMethods.PopPageModel();
});
}
}
protected override void ViewIsDisappearing(object sender, EventArgs e)
{
base.ViewIsDisappearing(sender, e);
_transaction.Dispose();
}
}
The collection change notifications are emitted from the database itself. So until something is saved to the database, nothing has changed (thus you only get the notifications after the commit). One way to work around it is to instead create the object and save it eagerly, but mark it as in-progress. Then on save, remove the flag and on disappearing, delete all incomplete objects:
public Command NewModelB
{
get
{
return new Command(() =>
{
db.Write(() =>
{
ModelA.ModelBs.Add(new ModelB
{
Name = "B",
InProgress = true
});
});
});
}
}
public Command SaveModelA
{
get
{
return new Command(async () =>
{
db.Write(() =>
{
var toCommit = ModelA.ModelBs.Where(m => m.InProgress);
foreach (var modelB in toCommit)
{
modelB.InProgress = false;
}
});
await CoreMethods.PopPageModel();
});
}
}
protected override void ViewIsDisappearing(object sender, EventArgs e)
{
base.ViewIsDisappearing(sender, e);
db.Write(() =>
{
var toDelete = ModelA.ModelBs.Where(m => m.InProgress).ToArray();
foreach (var modelB in toDelete)
{
db.Remove(modelB);
}
});
}
Obviously, this may leave some dangling objects if the app crashes or if the user quits it before navigating away, so you'll probably want to add some cleanup logic in your application startup.

Update a specific column of a List <T>

First they forgive me for my English since it is not my native language.
I have a method which receives a generic list List<T>. what I want is to foreach through the whole list and be able to update a column called Eliminated of each class T and which is of the boolean type, is it possible to do? can anybody help me.
This is what I have so far:
// Delete (Change status delete = true)
public void Delete<T>(List<T> data)
{
if (data != null)
{
data.ForEach(x =>
{
...
});
}
}
Thanks in advance!
Instead of T i would use an interface, because otherwise in the foreach you cannot access the property Eliminated.
Here the interface:
interface IExample {
bool IsEliminated { get; set; }
}
and here the method with the ForEach loop.
public void Delete<T>(List<T> data) where T : IExample
{
if (data != null)
{
data.ForEach(x =>
{
x.Eliminated = true;
});
}
}
If you want a generic method to update a list of any type, you could do something like this:
public void Update<T>(List<T> data, Action<T> func)
{
if (data == null)
throw new ArgumentNullException(nameof(data));
data.ForEach(func);
}
Note I've change the null check to throw if you pass in a null list. You could just return here instead, this way eliminates some nesting.
This allows you to pass in an action that you apply to every item in a collection. You would use it like this:
var data = new List<YourClass> = GetData();
Update(data, item => item.Eliminated = true);
Your T has no property called Eliminated. Your compiler cannot guarantee that any T you will ever use with this method will have that member, so you are not allowed to compile it that way.
You could put a constraint on your T that allows the compiler to make sure the property exists:
public interface Eliminatable
{
bool Eliminated { get; set; }
}
public void Delete<T>(List<T> data) where T : Eliminatable
{
if (data != null)
{
data.ForEach(x => { x.Eliminated = true; });
}
}
Or (and some may say this is a hack) you can just trust your users that they will in fact pass something as T that confirms to your pattern:
public void Delete<T>(List<T> data)
{
if (data != null)
{
data.ForEach(x => { dynamic d = x; d.Eliminated = true; });
}
}
Now this will fail if the property is not there. At runtime. Not nice. But it "works".

INotifyPropertyChanged updating properties that refer to it via Linq

I have a collection of classes contained in a ObservibaleCollection<MyObj> and MyObj implements INotifyPropertyChanged, but I need a property located outside of it that references a property in the collection via linq and creates its own collection to update on both the collection change and any of its content linq bound properties changing.
For sake of argument and simplicity lets say my class MyObj contains a property called IsVisible. I want a property that implements its own INotifyPropertyChanged to get a list of MyObj where IsVisible == true and keep it up to date regardless id the collection of MyObj changes or the IsVisible property does.
Is this possible without attaching to the collection changed event and subsequently just directly attaching to each child MyObj.IsVisible property? Is there a way to get INotify to bubble up through linq?
public class MyObj:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool IsVisible
{
get { return _IsVisible; }
protected set { if (value != _IsVisible) { _IsVisible= value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsVisible")); } }
}
private bool _IsVisible;
}
public class Foo
{
ObservableCollection<MyObj> myObjs = new ObservableCollection<MyObj>();
ObservableCollection<MyObj> myVisibleObjs {
get{
return myObjs.where(o => o.IsVisible);
}
}
}
I hope what I'm asking makes sense.
You could make use of Reactive Extensions, but for this specific requirement - maintaining a myVisibleObjs - I would use the dynamic data lib.
Try out the following:
static void Main(string[] args)
{
Foo f = new Foo();
f.BindToVisibleObjects();
// add more dummy data
f.Add(false);
f.Add(true);
// There will be 2 visible objects in MyVisibleObjects
foreach (var d in f.MyVisibleObjects)
{
Console.WriteLine(d.IsVisible);
}
Console.ReadKey();
}
public class Foo
{
ObservableCollection<MyObj> myObjs = new ObservableCollection<MyObj>();
public ReadOnlyObservableCollection<MyObj> MyVisibleObjects;
public Foo()
{
// add some dummy data
myObjs.Add(new MyObj() { IsVisible = true });
myObjs.Add(new MyObj() { IsVisible = false });
}
public void BindToVisibleObjects()
{
myObjs.ToObservableChangeSet()
.Filter(o => o.IsVisible)
.Bind(out MyVisibleObjects)
.DisposeMany()
.Subscribe();
}
public void Add(bool vis)
{
myObjs.Add(new MyObj() { IsVisible = vis });
}
}
The key here is that we bind the filtered observable changeset to a new collection that will be updated as your myObjs changes.

ISupportIncrementalLoading retrieving exception

I have implemented a ISupportIncrementalLoading interface to perform the incremental loading of a ListView.
The interface has the following code:
public interface IIncrementalSource<T>
{
Task<IEnumerable<T>> GetPagedItems(int pageIndex, int pageSize);
}
public class IncrementalLoadingCollection<T, I> : ObservableCollection<I>,
ISupportIncrementalLoading where T : IIncrementalSource<I>, new()
{
private T source;
private int itemsPerPage;
private bool hasMoreItems;
private int currentPage;
public IncrementalLoadingCollection(int itemsPerPage = 10)
{
this.source = new T();
this.itemsPerPage = itemsPerPage;
this.hasMoreItems = true;
}
public void UpdateItemsPerPage(int newItemsPerPage)
{
this.itemsPerPage = newItemsPerPage;
}
public bool HasMoreItems
{
get { return hasMoreItems; }
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
return Task.Run<LoadMoreItemsResult>(
async () =>
{
uint resultCount = 0;
var dispatcher = Window.Current.Dispatcher;
var result = await source.GetPagedItems(currentPage++, itemsPerPage);
if(result == null || result.Count() == 0)
{
hasMoreItems = false;
} else
{
resultCount = (uint)result.Count();
await Task.WhenAll(Task.Delay(10), dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
foreach (I item in result)
this.Add(item);
}).AsTask());
}
return new LoadMoreItemsResult() { Count = resultCount };
}).AsAsyncOperation<LoadMoreItemsResult>();
}
}
The instance of the interface is this one:
var collection = new IncrementalLoadingCollection<LiveTextCode, LiveText>();
this.LTextLW.ItemsSource = collection;
Where LiveText is a UserForm and LiveTextCode is a class that, among other functionalities, sets the previous UserForm up.
The UserForm is filled by reading XML files located in a server so the code must perform async operations and, for that, the containing scope has to be also. Due to an unknown reason, the instance of the custom interface is called before it's filling so, I'm getting a NullReferenceException (or at least that the hypothesis that makes most sense to me...).
I'm pretty lost and I don't know how to fix it, if anyone could help it would be much appreciated.
Thanks in advance!
Instead of using this.LTextLW.ItemsSource = collection;
Specify an ObservableCollection item say collection. Now bind this to your listview by binding it to your ItemsSource="{Binding collection}".
Since its an ObservableCollection type as soon as your collection value gets updated it will be reflected in your View also.
Else you can also specify a collection with a RaisePropertyChanged Event
private IncrementalLoadingCollection<LiveTextCode, LiveText> _collection;
public IncrementalLoadingCollection<LiveTextCode, LiveText> collection
{
get { return _collection; }
set
{
_collection = value;
RaisePropertyChanged();
}
}
This will handle updation of UI whenever the value changes.

C# handle instance of an object after continue in foreach loop

Let's assume I have an instance of this class.
public class MyClass
{
public string LocationCode;
public string PickUpCode
}
There is another class that takes a List<MyClass> as input and saves to the DB.
Now I need to apply some business rules:
For example if LocationCode is null this item in the List<MyClass> must be skipped and the foreach loop must continue to the next item in the list.
I've written the following code and the items with null LocationCode are indeed skipped but the var instance = new SomeClass(); somehow remains in memory so when the loop reaches an valid item and proceeds to save it in the DB, it also saves all the previously skipped instances of var instance = new SomeClass();. Which means I have null entries in the DB.
I'm using NHibernate and Evictdoesn't seam to be doing the trick. Any suggestions?
public void Save(List<MyClass> listOfItems)
{
using (UnitOfWork.Start())
{
var repository = new Repository();
try
{
foreach (var item in listOfItems.Select(i => i.Item).Where(item => item != null))
{
var instance = new SomeClass();
if (pickUpCode != null)
{
instance.PickUpCode = pickUpCode;
}
else
{
instance.PickUpCode = null;
}
if (locationCode != null)
{
instance.StartLocation = locationCode
}
else
{
UnitOfWork.CurrentSession.Evict(instance);
continue;
}
repository.SaveSomeClass(instance);
}
}
catch (Exception ex)
{
_log.Error(" Unhandled error", ex);
}
}
}
** Because someone asked, here's some code on UnitOfWork.Start()
public static class UnitOfWork
{
public static IUnitOfWork Start();
}
public interface IUnitOfWork : IDisposable
{
bool IsInActiveTransaction { get; }
IUnitOfWorkFactory SessionFactory { get; }
IGenericTransaction BeginTransaction();
IGenericTransaction BeginTransaction(IsolationLevel isolationLevel);
void Flush();
void TransactionalFlush();
void TransactionalFlush(IsolationLevel isolationLevel);
}
Why don't you fail first and avoid all of this?
Example being:
foreach (var item in listOfItems.Select(i => i.Item).Where(item => item != null))
{
if (item.LocationCode == null){
continue;
}
var instance = new SomeClass();
if (pickUpCode != null)
{
instance.PickUpCode = pickUpCode;
}
else
{
instance.PickUpCode = null;
}
// if we reach here, location code is definitley not null, no need for the check
instance.StartLocation = locationCode
repository.SaveSomeClass(instance);
}
Alternatively, you could add the check to you LINQ where clause
foreach (var item in listOfItems.where(item=> item != null && item.LocationCode != null)
Without more code on how UnitofWork.Start works its hard to suggest. But, It's worth trying by implementing IDisposable on SomeClass.

Categories

Resources