Observable DataContracts and Two-Way bindings
In my WPF app, I use a WCF service to fetch data.
So naturally, at some point i had "complex" objects that require a DataContract
to be passed as a whole to the WPF app.
Now of course, I need to respond to changes, and I implement INotifyPropertyChanged
on my ViewModels, however since some objects are actually DataContracts, I would have to recompose those so that they implement INotifyPropertyChanged.
I feel like it's messy.
What i tried to do is implement the interface directly on the DataContract definition, but I can't react properly to a change.
For example, if a Two-Way databoundTextBox
has its text changed, my ViewModel should react to it by changing the value in the corresponding SQL table (through the WCF service), but since the object is defined on WCF side, I can't do that in the setter of the property.
What I do for now, is subscribe to the PropertyChanged event of the DataContracts, and use reflection to know which property changed and its new value.
But those objects are held in anObservableCollection<T>
, that's a lot of events, and it feels very brittle... what if i add/remove an eleme开发者_如何学JAVAnt from the collection for example?
I do it like this (this is bad I think):
foreach (ImageInfo imgi in (param.Images as ObservableCollection<ImageInfo>))
{
imgi.PropertyChanged += (sender, args) =>
{
object newValue = Tools.GetProperty((sender as ImageInfo), args.PropertyName);
};
}
And then I'd send it back to the WCF service.
Is there a more elegant solution to this? should I implement INotifyPropertyChanged on the ViewModel only, and recompose the DataContracts instead?
Thanks!
So you want a real-time system that is linked to the database by the WCF server?
What about creating Model objects, whose Get/Set methods go to/from the database?
public class MyModel : INotifyPropertyChanged
{
private IMyModelService service;
public int Id { get; set; }
public MyModel (IMyModelService wcfService, int id)
{
this.Id = id;
this.service = wcfService;
AutoMapper.Map<MyModelDTO, MyModel>(service.GetMyModel(this.Id), this);
}
public int SomeValue
{
get
{
return service.GetSomeValue(this.Id);
}
set
{
service.SetSomeValue(this.Id, value);
RaisePropertyChanged("SomeValue");
}
}
}
You could also cache object properties locally and make the WCF service calls async, which would probably improve performance, although if more than one person can be modifying a single object you might need some kind of messaging system to alert the client that MyObject
has changed when another user updates a property.
The ViewModel on the client would be responsible for creating the Model, not the WCF service.
public class LoadObject(int id)
{
CurrentObject = new MyModel(serviceReference, id);
}
Alright so after some time thinking it through, I implemented it like this for now but it might change because I meet with an expert next monday (I'll update if he gives me a better idea).
1) The WCF service has a DataContract such as:
[DataContract]
public class MyWcfData : INotifyPropertyChanged
{
public MyWcfData()
{
MyField = "";
}
[DataMember]
public string MyField;
[DataMember]
public string MyFieldModified;
}
Note: INotifyPropertyChanged is implemented the usual way, i just left it out for readability, and because i dont have my snippets on this computer.
When GetMyData() is called on the service, it returns an instance of that class, with only "MyField" populated. MyFieldModified is left to null.
On my DAL (so client-side), where MyWcfData is recieved:
public class MyDAL
{
//... some init code
public MyWcfData GetMyWcfData()
{
MyWcfData newData = m_WcfService.GetMyData();
newData.MyFieldModified = newData.MyField;
return newData;
}
}
The point here is that the data needs to be duplicated so that i can keep track of changes, and only update a property once in the end, even if the user changed it 10 times (i just compare everytime to the original value). But I don't want to send duplicated data over the wire, so instead i do the duplication in the DAL, before any business object has access to it.
On the ViewModel of my view:
public class MyViewModel
{
//.. some init code
DelegateCommand<MyWcfData> _GetDataCommand;
public DelegateCommand<MyWcfData> GetDataCommand
{
get
{
if (_GetDataCommand == null)
_GetDataCommand = new DelegateCommand<MyWcfData>(GetData);
return _GetDataCommand;
}
}
public void GetData(MyWcfData param)
{
m_WcfData = m_DAL.GetMyWcfData();
}
}
And then last but not least, in my XAML, i bind to MyFieldModified
(in Two-Way binding) in a TextBox
.
I then use System.Windows.Interactivity
to call a DelegateCommand
upon the TextChanged
event.
When that last command is fired, i put the change in a Queue (to keep track of the changes order) and i send it back to the Wcf service for data persistency when the user presses a Save button.
Note: I actually use a custom-made ObservableQueue<T>
so that i can keep track of the number of changes and enable the save button accordingly through binding. I will make a blog post about it so stay tuned ;-)
Note2: I gave up on making it save every tiny change made instantly, even though in this case it would have worked because it's not a feature much used, and few changes are expected; but as pointed out in other answers it's bad practice.
精彩评论