Using Rx in Silverlight for WCF calls doesn't work with TakeUntil - silverlight

I have the following bit of code to set up my Rx hookups:
Event related definitions:
public class QueryEventArgs : EventArgs
{
public SomeParametersType SomeParameters
{
get;
set;
}
public object QueryContext
{
get;
set;
}
};
public delegate void QueryDelegate(object sender, QueryEventArgs e);
public event QueryDelegate QueryEvent;
Initialization:
queryObservable = Observable.FromEvent<QueryEventArgs>(this, "QueryEvent");
queryObservable.Subscribe((e) =>
{
tbQueryProgress.Text = "Querying... ";
client.QueryAsync(e.EventArgs.SomeParameters, e.EventArgs.QueryContext);
});
queryCompletedObservable = from e in Observable.FromEvent<QueryCompletedEventArgs>(client, "QueryCompleted").TakeUntil(queryObservable) select e;
queryCompletedObservable.Subscribe((e) =>
{
tbQueryProgress.Text = "Ready";
SilverlightClientService_QueryCompleted(e.Sender, e.EventArgs);
},
(Exception ex) =>
{
SetError("Query error: " + ex);
}
);
"client" is the WCF client and everything else is fairly self-explanatory.
The "TakeUntil" is there to stop the user stomping on himself when doing a new query while in the middle of a currently running one. However, while the code works if I remove the "TakeUntil" clause, if I put it in, the query is never completed.
Is this the correct pattern? If so, am I doing something wrong?
Cheers,
-Tim

TakeUntil terminates the subscription when a value is received from its argument, so your first queryObservable starts up the query but also terminates the subscription to the complete events.
The simpler solution is to setup an IObservable around your actual query, and then use Switch to ensure that only one query runs at a time.
public static class ClientExtensions
{
public static IObservable<QueryCompletedEventArgs> QueryObservable(
this QueryClient client,
object[] someParameters, object queryContext)
{
return Observable.CreateWithDisposable<QueryCompletedEventArgs>(observer =>
{
var subscription = Observable.FromEvent<QueryCompletedEventArgs>(
h => client.QueryCompleted += h,
h => client.QueryCompleted -= h
)
.Subscribe(observer);
client.QueryAsync(someParameters, queryContext);
return new CompositeDisposable(
subscription,
Disposable.Create(() => client.Abort())
);
});
}
}
Then you can do this:
queryObservable = Observable.FromEvent<QueryEventArgs>(this, "QueryEvent");
queryObservable
.Select(query => client.QueryObservable(
query.EventArgs.SomeParameters,
query.EventArgs.QueryContext
))
.Switch()
.Subscribe(queryComplete =>
{
tbQueryProgress.Text = "Ready";
// ... etc
});
This sets up one continuous flow, whereby each "Query" event starts a query which emits the complete event from that query. New queries automatically teriminate the previous query (if possible) and start a new one.

Related

EF Core 6 "normal" update method doesn't respect RowVersion expected behavior?

I have a .NET6 API project that allows users to fetch resources from a database (SQL Server), and update them on a web client, and submit the updated resource back for saving to db. I need to notify users if another user has already updated the same resource during editing. I tried using EF IsRowVersion property for this concurrency check.
I noticed that "normal" update procedure (just getting the entity, changing properties and saving) does not respect the RowVersion expected behavior. But if I get the entity using AsNoTracking and use the db.Update method, the concurrency check works as expected. What could be the reason, and is the db.Update the only way to force the RowVersion check? That method has the downside that it tries to update every property, not just those that have changed. Simplified and runnable console app example below:
using Microsoft.EntityFrameworkCore;
Guid guid;
using (PeopleContext db = new())
{
Person p = new() { Name = "EF", Age = 30 };
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
db.People.Add(p);
await db.SaveChangesAsync();
guid = p.Id;
}
using (PeopleContext db = new())
{
Person p = await db.People.FirstAsync(x => x.Id == guid);
p.Name = "FE";
p.RowVersion = Convert.FromBase64String("AAAAAADDC9I=");
await db.SaveChangesAsync(); // Does not throw even though RowVersion is incorrect
}
using (PeopleContext db = new())
{
Person p = await db.People.AsNoTracking().FirstAsync(x => x.Id == guid);
p.Name = "EFFE";
p.RowVersion = Convert.FromBase64String("AAAAAAGGC9I=");
db.People.Update(p);
await db.SaveChangesAsync(); // Throws DbUpdateConcurrencyException as expected, but updates all properties
}
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
}
public class PeopleContext : DbContext
{
public PeopleContext(){}
public DbSet<Person> People => Set<Person>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=EFRowVersionDb;Integrated Security=True;");
optionsBuilder.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information);
optionsBuilder.EnableSensitiveDataLogging();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>(entity =>
{
entity.Property(e => e.RowVersion)
.IsRequired()
.IsRowVersion();
});
}
}
I solved the problem by overriding the SaveChangesAsync method like this:
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
foreach (var item in ChangeTracker.Entries().Where(x=>x.State == EntityState.Modified))
{
item.OriginalValues["RowVersion"] = item.CurrentValues["RowVersion"];
}
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
I override that signature method cause the one without boolean calls that method. Same thing on sync version.

Entity Framework Core not saving updates

I have a paintings web app that uses ASP.NET Core, Angular, EF Core, SQL Server, AutoMapper with a repository pattern.
The issue is that when I try to update a single painting from the painting table, it does not save to the database. I tried other tables in this same method to see if it was a problem with the flow but they save successfully.
Through swagger I call the put method, this calls the painting controller, goes into the repository, and the repository returns the updated object but when I go to the database nothing updates. If I call the get action from swagger I also DO NOT see the updates.
When I add breakpoints to see the data everything looks fine from start to end but it just does not save to the database. To test I even tried to remove auto mapper logic and manually created an object inside of the update method and set the existing object properties to these hard coded values to see it was the incoming data but still no luck. Again, for testing I tried updating other tables and those worked.
Controller
[HttpPut("{paintingId:int}")]
public async Task<IActionResult> UpdatePaintingAsync(int paintingId, [FromBody] UpdatePaintingRequest updatePaintingRequest)
{
try
{
if (await repository.Exists(paintingId))
{
var updatedPaiting = await repository.UpdatePainting(paintingId, mapper.Map<DataModels.Painting>(updatePaintingRequest));
if (updatedPaiting != null)
{
return Ok(updatePaintingRequest);
}
}
return NotFound();
}
catch (Exception ex)
{
logger.LogError($"Failed to update painting: {ex}");
return BadRequest("Failed to update painting");
}
}
Update method from repository
public async Task<Painting> UpdatePainting(int paintingId, Painting request)
{
var existingPainting = await GetPaintingByIdAsync(paintingId);
if (existingPainting != null)
{
existingPainting.Name = request.Name;
existingPainting.Description = request.Description;
existingPainting.ImageUrl = request.ImageUrl;
existingPainting.IsOriginalAvailable = request.IsOriginalAvailable;
existingPainting.IsPrintAvailable = request.IsPrintAvailable;
existingPainting.IsActive = request.IsActive;
await context.SaveChangesAsync();
return existingPainting;
}
return null;
}
Get painting to update
public async Task<Painting> GetPaintingByIdAsync(int paintingId)
{
return await context.Painting
.Include(x => x.PaintingCategories)
.ThenInclude(c => c.Category)
.AsNoTracking()
.Where(x => x.PaintingId == paintingId)
.FirstOrDefaultAsync();
}
Model (exact same on DAO and DTO)
public class Painting
{
public int PaintingId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string ImageUrl { get; set; }
public bool IsOriginalAvailable { get; set; }
public bool IsPrintAvailable { get; set; }
public bool IsActive { get; set; }
public ICollection<PaintingCategory> PaintingCategories { get; set; }
}
Context
public class JonathanKrownContext : DbContext
{
public JonathanKrownContext(DbContextOptions<JonathanKrownContext> options) : base(options)
{
}
public DbSet<Painting> Painting { get; set; }
}
ModelBuilder.Entity
modelBuilder.Entity("JonathanKrownArt.API.DataModels.Painting", b =>
{
b.Property<int>("PaintingId")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Description")
.HasColumnType("nvarchar(max)");
b.Property<string>("ImageUrl")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<bool>("IsOriginalAvailable")
.HasColumnType("bit");
b.Property<bool>("IsPrintAvailable")
.HasColumnType("bit");
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.HasKey("PaintingId");
b.ToTable("Painting");
});
Your problem is that you used AsNoTracking when fetching the entity, and thus, context doesn't keep track of the changes anymore. So you need either to attach it before saving or remove AsNoTracking.
If you don't want to attach the entity, you need to change GetPaintingByIdAsync to this:
public async Task<Painting> GetPaintingByIdAsync(int paintingId)
{
return await context.Painting
.Include(x => x.PaintingCategories)
.ThenInclude(c => c.Category)
.Where(x => x.PaintingId == paintingId)
.FirstOrDefaultAsync();
}
If you want to keep AsNoTracking then in your UpdatePainting you need to add:
context.Painting.Update(existingPainting);
before you call save.
Update method does the following:
Begins tracking the given entity in the Modified state such that it
will be updated in the database when SaveChanges() is called.
So change your method to this:
public async Task<Painting> UpdatePainting(int paintingId, Painting request)
{
var existingPainting = await GetPaintingByIdAsync(paintingId);
if (existingPainting != null)
{
existingPainting.Name = request.Name;
existingPainting.Description = request.Description;
existingPainting.ImageUrl = request.ImageUrl;
existingPainting.IsOriginalAvailable = request.IsOriginalAvailable;
existingPainting.IsPrintAvailable = request.IsPrintAvailable;
existingPainting.IsActive = request.IsActive;
context.Painting.Update(existingPainting);
await context.SaveChangesAsync();
return existingPainting;
}
return null;
}
I think using AsNoTracking() is a good practice and you should use it wherever you can but in case of Update you need to attach the entity to context by this EF will know this entity should be updated.
So for solve your problem just add one line to code like this:
//other lines
context.Attach(existingPainting); //<--- by this line you tell EF to track the entity
context.Painting.Update(existingPainting);
await context.SaveChangesAsync();

Is there a more efficient way to read a value from Serial Port and Update a Chart in realtime - WPF

Using Live Charts, I am creating a realtime graph which updates with values read in from the serial port. Now I can get this to work but I don't think I am doing this as efficiently as I could as I am inexperienced using C# and WPF.
I am storing the data I am reading in from the serial port in a SerialCommunication class. I am then using a button to start a new task which opens the serial port and updates my graph.
My issue is that I want to be able to update the graph everytime the Serial class receives a new value, however, my chart is updated in the Read() function which is called from starting a new task and I feel this may cause threading issues.
Any advice would be appreciated.
Serial class
public class SerialCommunication
{
private string _value;
SerialPort serialPort = null;
public SerialCommunication()
{
InitializeComms();
}
private void InitializeComms()
{
try
{
serialPort = new SerialPort("COM6", 115200, Parity.None, 8, StopBits.One); // Update this to avoid hard coding COM port and BAUD rate
serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
}
catch (Exception ex) { MessageBox.Show(ex.Message); }
}
~SerialCommunication()
{
if(serialPort.IsOpen)
serialPort.Close();
}
public void ReceiveData()
{
try
{
if (!serialPort.IsOpen)
serialPort.Open();
}
catch(Exception ex) { MessageBox.Show(ex.Message); }
}
public void StopReceivingData()
{
try
{
if (serialPort.IsOpen)
serialPort.Close();
}
catch(Exception ex) { MessageBox.Show(ex.Message); }
}
public event EventHandler DataReceived;
private void OnDataReceived(EventArgs e)
{
DataReceived?.Invoke(this, e);
}
// read the data in the DataReceivedHandler
// Event Handler
public void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
try
{
SerialPort sp = (SerialPort)sender;
_value = sp.ReadLine();
}
catch (Exception ex) { MessageBox.Show(ex.Message); }
OnDataReceived(EventArgs.Empty);
}
}
Time of Flight class which updates chart from sensor values read from serial port, using code taken from LiveCharts by beto-rodriguez
public TimeOfFlight()
{
InitializeComponent();
// attach an event handler to update graph
serial.DataReceived += new EventHandler(UpdateChart);
// Use PlotData class for graph data which will use this config every time
var mapper = Mappers.Xy<PlotData>()
.X(model => model.DateTime.Ticks)
.Y(model => model.Value);
// Save mapper globally
Charting.For<PlotData>(mapper);
chartValues = new ChartValues<PlotData>();
//lets set how to display the X Labels
XFormatter = value => new DateTime((long)value).ToString("hh:mm:ss");
YFormatter = x => x.ToString("N0");
//AxisStep forces the distance between each separator in the X axis
AxisStep = TimeSpan.FromSeconds(1).Ticks;
//AxisUnit forces lets the axis know that we are plotting seconds
//this is not always necessary, but it can prevent wrong labeling
AxisUnit = TimeSpan.TicksPerSecond;
SetAxisLimits(DateTime.Now);
//ZoomingMode = ZoomingOptions.X;
IsReading = false;
DataContext = this;
}
public ChartValues<PlotData> chartValues { get; set; }
public Func<double, string> XFormatter { get; set; }
public Func<double, string> YFormatter { get; set; }
public double AxisStep { get; set; }
public double AxisUnit { get; set; }
public double AxisMax
{
set
{
_axisXMax = value;
OnPropertyChanged("AxisMax");
}
get { return _axisXMax; }
}
public double AxisMin
{
set
{
_axisXMin = value;
OnPropertyChanged("AxisMin");
}
get { return _axisXMin; }
}
public bool IsReading { get; set; }
private void StartStopGraph(object sender, RoutedEventArgs e)
{
IsReading = !IsReading;
if (IsReading)
{
serial.ReceiveData();
}
else
serial.StopReceivingData();
}
public void UpdateChart(object sender, EventArgs e) // new task
{
try
{
var now = DateTime.Now;
// can chartValues.Add be called from INotifyPropertyChanged in
// SerialCommunication.cs and would this cause an issue with the
chartValues.Add(new PlotData
{
DateTime = now,
Value = 0 // update this
});
SetAxisLimits(now);
//lets only use the last 150 values
if (chartValues.Count > 1000) chartValues.RemoveAt(0);
}
catch (Exception ex) { MessageBox.Show(ex.Message); }
}
private void SetAxisLimits(DateTime now)
{
AxisMax = now.Ticks + TimeSpan.FromSeconds(1).Ticks; // lets force the axis to be 1 second ahead
AxisMin = now.Ticks - TimeSpan.FromSeconds(8).Ticks; // and 8 seconds behind
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
PlotData class
public class PlotData
{
public DateTime DateTime { get; set; }
public int Value { get; set; }
}
Yeah, not the best way IMO.
First of all I wouldn't put anything to do with INotifyPropertyChanged or business/view logic in your SerialCommunication class, from an architectural perspective all it should do manage the opening and closing of the serial device, and any data that does arrive should be passed on other parts of your app via an event.
Secondly, why are you using a loop? You've already subscribed to DataReceived, so just read the data in your DataReceivedHandler and pass it on to said event. That handler will be called on a different thread, but the handlers that subscribed to your event can dispatch to the main GUI thread if they need to.
Which brings me to the third point: if you're data binding correctly (as opposed to updating your controls directly) then you shouldn't need to, just update your data values and leave it to the WPF data binding engine to update as needed.
In a real application you may want to do the updates at a certain interval, and for that you'll want to push your data values to a queue and then process them all at once. The correct way to do this is in C# with an asynchronous Task. Don't use threads, and definitely don't use Thread.Sleep....threads are an outdated technology which has no place in C# except for a few very specific circumstances.
The cleanest way would be to implement your serial communications as a standalone module using Reactive Extensions (IObservable).
That way, your WPF application could subscribe to the message observable on an appropriate threading context, and update the UI each time a new message is received.

Akka.Net Streams and remoting (Sink.ActorRefWithAck)

I've made quite a simple implementation with Akka.net Streams using Sink.ActorRefWithAck: a subscriber asks for a large string to a publisher which sends it by slices.
It works perfectly fine locally (UT) but not remotely. And I cannot understand what's wrong? Concretly: the subscriber is able to send the request to the publisher which responds with an OnInit message but then the OnInit.Ack will never goes back to the publisher. This Ack message ends up as a dead letter:
INFO Akka.Actor.EmptyLocalActorRef - Message Ack from akka.tcp://OutOfProcessTaskProcessing#localhost:12100/user/Streamer_636568240846733287 to akka://OutOfProcessTaskProcessing/user/StreamSupervisor-0/StageActorRef-0 was not delivered. 1 dead letters encountered.
Note that the log is from the destination actor so the message is handled in the right process. There is no obvious path error.
Looking at the publisher code which does not handle this message, I really don't know what I'm doing wrong:
public static void ReplyWithStreamedString(IUntypedActorContext context, string toStream, int chunkSize = 2000)
{
Source<string, NotUsed> source = Source.From(toStream.SplitBy(chunkSize));
source.To(Sink.ActorRefWithAck<string>(context.Sender, new StreamMessage.OnInit(),
new StreamMessage.OnInit.Ack(),
new StreamMessage.Completed(),
exception => new StreamMessage.Failure(exception.Message)))
.Run(context.System.Materializer());
}
Here is the subscriber code:
public static Task<string> AskStreamedString(this ICanTell self, object message, ActorSystem context, TimeSpan? timeout = null)
{
var tcs = new TaskCompletionSource<string>();
if (timeout.HasValue)
{
CancellationTokenSource ct = new CancellationTokenSource(timeout.Value);
ct.Token.Register(() => tcs.TrySetCanceled());
}
var props = Props.Create(() => new StreamerActorRef(tcs));
var tempActor = context.ActorOf(props, $"Streamer_{DateTime.Now.Ticks}");
self.Tell(message, tempActor);
return tcs.Task.ContinueWith(task =>
{
context.Stop(tempActor);
if(task.IsCanceled)
throw new OperationCanceledException();
if (task.IsFaulted)
throw task.Exception.GetBaseException();
return task.Result;
});
}
internal class StreamerActorRef : ReceiveActor
{
readonly TaskCompletionSource<string> _tcs;
private readonly StringBuilder _stringBuilder = new StringBuilder();
public StreamerActorRef(TaskCompletionSource<string> tcs)
{
_tcs = tcs;
Ready();
}
private void Ready()
{
ReceiveAny(message =>
{
switch (message)
{
case StreamMessage.OnInit _:
Sender.Tell(new StreamMessage.OnInit.Ack());
break;
case StreamMessage.Completed _:
string result = _stringBuilder.ToString();
_tcs.TrySetResult(result);
break;
case string slice:
_stringBuilder.Append(slice);
Sender.Tell(new StreamMessage.OnInit.Ack());
break;
case StreamMessage.Failure error:
_tcs.TrySetException(new InvalidOperationException(error.Reason));
break;
}
});
}
}
With messages:
public class StreamMessage
{
public class OnInit
{
public class Ack{}
}
public class Completed { }
public class Failure
{
public string Reason { get; }
public Failure(string reason)
{
Reason = reason;
}
}
}
In general sources and sinks working with actor refs have not been designed to work over remote connections - they don't cover message retries, which can cause deadlocks in your system if some stream control message won't be passed in.
The feature you're looking for is called StreamRefs (which works like actor refs, but for streams), and will be shipped as part of v1.4 release (see github pull request for more details).

Pros / Cons to Different Binding Approaches using MVVM and RIA Services

I have been building an application, which uses the LoadOperation's Entities to return an IEnumerable which becomes the source of a CollectionViewSource in my View Model. I am now discovering the potential pitfall to this approach, when adding Entities in my Silverlight client, I cannot see these entities, unless I either submit the New Entity, then reload, or Maintain a separate collection of items, which I am binding to.
What I really see as my options are:
Add an ObservableCollection to use as the Source of the CollectionViewSource property in my ViewModel - this way I can add to both the DomainContext and the ObservableCollection at the same time to keep the collections in sync.
Change the Binding to the EntitySet directly, and add a filtering event handler to provide the filtering on the CollectionViewSource.
If anyone has tips or thoughts about pros/cons of each, I would greatly appreciate it. In particular, I am wondering, if there are performance and/or programming benefits in favor of one or the other?
I am taking this one approach at a time. First, I am going to show a point of reference to dicuss this with, then I will highlight the different changes necessary to support each methodology.
The basis for my demo is a single, authenticated domain service which returns a single entity of Resource. I will expose 4 commands (save, undo, add, and delete), plus a Collection, and a Property to hold the SelectedResource.
2 Different classes implement this interface (1 for blending, 1 for production). The production is the only one I will discuss here. Notice the action(lo.Entities) in the GetMyResources function:
public class WorkProvider
{
static WorkContext workContext;
public WorkProvider()
{
if (workContext == null)
workContext = new WorkContext();
}
public void AddResource(Resource resource)
{
workContext.Resources.Add(resource);
}
public void DelResource(Resource resource)
{
workContext.Resources.Remove(resource);
}
public void UndoChanges()
{
workContext.RejectChanges();
}
public void SaveChanges(Action action)
{
workContext.SubmitChanges(so =>
{
if (so.HasError)
// Handle Error
throw so.Error;
else
action();
}, null);
}
public void GetMyResources(Action<IEnumerable<Resource>> action)
{
var query = workContext.GetResourcesQuery()
.Where(r => r.UserName == WebContext.Current.User.Name);
workContext.Load(query, LoadBehavior.MergeIntoCurrent, lo =>
{
if (lo.HasError)
// Handle Error
throw lo.Error;
else
action(lo.Entities);
}, null);
}
}
In the ViewModel, I have the following Implementation:
public class HomeViewModel : ViewModelBase
{
WorkProvider workProvider;
public HomeViewModel()
{
workProvider = new WorkProvider();
}
// _Source is required when returning IEnumerable<T>
ObservableCollection<Resource> _Source;
public CollectionViewSource Resources { get; private set; }
void setupCollections()
{
Resources = new CollectionViewSource();
using (Resources.DeferRefresh())
{
_Source = new ObservableCollection<Resource>();
Resources.Source = _Source;
Resources.GroupDescriptions.Add(new PropertyGroupDescription("Title"));
Resources.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
Resources.SortDescriptions.Add(new SortDescription("Rate", ListSortDirection.Ascending));
}
}
void loadMyResources()
{
workProvider.GetMyResources(results =>
{
using (Resources.DeferRefresh())
{
// This is required when returning IEnumerable<T>
_Source.Clear();
foreach (var result in results)
{
if (!_Source.Contains(result))
_Source.Add(result);
}
}
});
}
Resource _SelectedResource;
public Resource SelectedResource
{
get { return _SelectedResource; }
set
{
if (_SelectedResource != value)
{
_SelectedResource = value;
RaisePropertyChanged("SelectedResource");
}
}
}
public RelayCommand CmdSave { get; private set; }
public RelayCommand CmdUndo { get; private set; }
public RelayCommand CmdAdd { get; private set; }
public RelayCommand CmdDelete { get; private set; }
void setupCommands()
{
CmdSave = new RelayCommand(() =>
{
workProvider.SaveChanges(() =>
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
System.Windows.MessageBox.Show("Saved");
});
});
});
CmdUndo = new RelayCommand(() =>
{
workProvider.UndoChanges();
// This is required when returning IEnumerable<T>
loadMyResources();
});
CmdAdd = new RelayCommand(() =>
{
Resource newResource = new Resource()
{
ResourceID = Guid.NewGuid(),
Rate = 125,
Title = "Staff",
UserName = "jsmith"
};
// This is required when returning IEnumerable<T>
_Source.Add(newResource);
workProvider.AddResource(newResource);
});
CmdDelete = new RelayCommand(() =>
{
// This is required when returning IEnumerable<T>
_Source.Remove(_SelectedResource);
workProvider.DelResource(_SelectedResource);
});
}
}
The alternate method would involve changing the WorkProvider class as follows (notice the action(workContext.Resources) that is returned:
public void GetMyResources(Action<IEnumerable<Resource>> action)
{
var query = workContext.GetResourcesQuery()
.Where(r => r.UserName == WebContext.Current.User.Name);
workContext.Load(query, LoadBehavior.MergeIntoCurrent, lo =>
{
if (lo.HasError)
// Handle Error
throw lo.Error;
else
// Notice Changed Enumeration
action(workContext.Resources);
}, null);
}
And the changes to the viewmodel are as follows (notice the removal of the _Source ObservableCollection):
public class HomeViewModel : ViewModelBase
{
WorkProvider workProvider;
public HomeViewModel()
{
workProvider = new WorkProvider();
}
public CollectionViewSource Resources { get; private set; }
void setupCollections()
{
Resources = new CollectionViewSource();
using (Resources.DeferRefresh())
{
Resources.Filter += (s,a) =>
{
a.Accepted = false;
if (s is Resource)
{
Resource res = s as Resource;
if (res.UserName == WebContext.Current.User.Name)
a.Accepted = true;
}
};
Resources.GroupDescriptions.Add(new PropertyGroupDescription("Title"));
Resources.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
Resources.SortDescriptions.Add(new SortDescription("Rate", ListSortDirection.Ascending));
}
}
void loadMyResources()
{
workProvider.GetMyResources(results =>
{
using (Resources.DeferRefresh())
{
Resources.Source = results;
}
});
}
Resource _SelectedResource;
public Resource SelectedResource
{
get { return _SelectedResource; }
set
{
if (_SelectedResource != value)
{
_SelectedResource = value;
RaisePropertyChanged("SelectedResource");
}
}
}
public RelayCommand CmdSave { get; private set; }
public RelayCommand CmdUndo { get; private set; }
public RelayCommand CmdAdd { get; private set; }
public RelayCommand CmdDelete { get; private set; }
void setupCommands()
{
CmdSave = new RelayCommand(() =>
{
workProvider.SaveChanges(() =>
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
System.Windows.MessageBox.Show("Saved");
});
});
});
CmdUndo = new RelayCommand(() =>
{
workProvider.UndoChanges();
Resources.View.Refresh();
});
CmdAdd = new RelayCommand(() =>
{
Resource newResource = new Resource()
{
ResourceID = Guid.NewGuid(),
Rate = 125,
Title = "Staff",
UserName = "jsmith"
};
workProvider.AddResource(newResource);
});
CmdDelete = new RelayCommand(() =>
{
workProvider.DelResource(_SelectedResource);
});
}
}
While the second approach definately requires adding the filter event handler in the configuration of the CollectionViewSource, and could be seen as filtering data 2 times (1 time at the server, and the second time by the CollectionViewSource), it does off the following benefits: There is a single collection - which makes management of collection notifications simpler and easier. The collection is the actual collection which will be submitted to the server, which makes managing adds/deletes simpler, since there are not opportunities for forgetting to add/remove entities from the correct collection to initiate the add/delete function when submitting back.
The one last thing I need to confirm is the following: On a collectionviewsource, it is my understanding that you should use DeferRefresh() when making multiple changes that affect the view. This just prevents unnecessary refreshes from occuring when internal changes may cause refreshes such as configuring sorting, grouping, etc. It is also important to call .View.Refresh() when we expect the UI to process some update changes. The .View.Refresh() is probably more important to note than the DeferRefresh(), since it actually causes a UI update, as opposed to a prevent unexpected UI updates.
I don't know if this will help others, but I hope so. I definately spent some time working through these and trying to understand this. If you have clarifications or other things to add, please feel free to do so.
Ryan, it might be worth your while to take a look through this post on collection binding (and some of the related ones). Your implementation is certainly a reasonable one, but I can see it wrestles with a few of the issues that have already been resolved at the framework level.

Resources