I have a set of classes that I am using to deserialize JSON into. My program will periodically look for changes to this JSON file, and if it finds any, will push the new data to the properties of these classes using reflection.
I need to find any new items added to the collection of the Item2 class (SocialExportJSON.SocialExportData.Item2) after a successful update.
My JSON classes look like this (there are more but I want to avoid too big a wall of code):
public class Item2 : INotifyPropertyChanged
{
[JsonProperty("type")]
private string type;
public string Type
{
get
{
return type;
}
set
{
if (type != value)
{
type = value;
RaisePropertyChanged("Type");
}
}
}
[JsonProperty("id")]
private string id;
public string ID
{
get
{
return id;
}
set
{
if (id != value)
{
id = value;
RaisePropertyChanged("ID");
}
}
}
[JsonProperty("postedIso8601")]
private string postedIso8601;
public string PostedIso8601
{
get
{
return postedIso8601;
}
set
{
if (postedIso8601 != value)
{
postedIso8601 = value;
RaisePropertyChanged("PostedIso8601");
}
}
}
[JsonProperty("postedTimestamp")]
private object postedTimestamp;
public object PostedTimestamp
{
get
{
return postedTimestamp;
}
set
{
if (postedTimestamp != value)
{
postedTimestamp = value;
RaisePropertyChanged("PostedTimestamp");
}
}
}
[JsonProperty("engagement")]
private Engagement engagement;
public Engagement Engagement
{
get
{
return engagement;
}
set
{
if (engagement != value)
{
engagement = value;
RaisePropertyChanged("Engagement");
}
}
}
[JsonProperty("source")]
private Source2 source;
public Source2 Source
{
get
{
return source;
}
set
{
if (source != value)
{
source = value;
RaisePropertyChanged("Source");
}
}
}
[JsonProperty("author")]
private Author author;
public Author Author
{
get
{
return author;
}
set
{
if (author != value)
{
author = value;
RaisePropertyChanged("Author");
}
}
}
[JsonProperty("content")]
private Content content;
public Content Content
{
get
{
return content;
}
set
{
if (content != value)
{
content = value;
RaisePropertyChanged("Content");
}
}
}
[JsonProperty("location")]
private Location location;
public Location Location
{
get
{
return location;
}
set
{
if (location != value)
{
location = value;
RaisePropertyChanged("Location");
}
}
}
[JsonProperty("publication")]
private Publication publication;
public Publication Publication
{
get
{
return publication;
}
set
{
if (publication != value)
{
publication = value;
RaisePropertyChanged("Publication");
}
}
}
[JsonProperty("metadata")]
private Metadata metadata;
public Metadata Metadata
{
get
{
return metadata;
}
set
{
if (metadata != value)
{
metadata = value;
RaisePropertyChanged("Metadata");
}
}
}
//Event handling
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
//Console.WriteLine("Updated");
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public class SocialExportData : INotifyPropertyChanged
{
[JsonProperty("dataType")]
private string dataType;
public string DataType
{
get
{
return dataType;
}
set
{
if (dataType != value)
{
dataType = value;
RaisePropertyChanged("DataType");
}
}
}
[JsonProperty("id")]
private int id;
public int ID
{
get
{
return id;
}
set
{
if (id != value)
{
id = value;
RaisePropertyChanged("ID");
}
}
}
[JsonProperty("story")]
private Story story;
public Story Story
{
get
{
return story;
}
set
{
if (story != value)
{
story = value;
RaisePropertyChanged("Story");
}
}
}
[JsonProperty("order")]
private string order;
public string Order
{
get
{
return order;
}
set
{
if (order != value)
{
order = value;
RaisePropertyChanged("Order");
}
}
}
[JsonProperty("lifetime")]
private string lifetime;
public string Lifetime
{
get
{
return lifetime;
}
set
{
if (lifetime != value)
{
lifetime = value;
RaisePropertyChanged("Lifetime");
}
}
}
[JsonProperty("maxAge")]
private int maxAge;
public int MaxAge
{
get
{
return maxAge;
}
set
{
if (maxAge != value)
{
maxAge = value;
RaisePropertyChanged("MaxAge");
}
}
}
[JsonProperty("maxSize")]
private int maxSize;
public int MaxSize
{
get
{
return maxSize;
}
set
{
if (maxSize != value)
{
maxSize = value;
RaisePropertyChanged("MaxSize");
}
}
}
[JsonProperty("consumeCount")]
private int consumeCount;
public int ConsumeCount
{
get
{
return consumeCount;
}
set
{
if (consumeCount != value)
{
consumeCount = value;
RaisePropertyChanged("ConsumeCount");
}
}
}
[JsonProperty("consumeInterval")]
private int consumeInterval;
public int ConsumeInterval
{
get
{
return consumeInterval;
}
set
{
if (consumeInterval != value)
{
consumeInterval = value;
RaisePropertyChanged("ConsumeInterval");
}
}
}
[JsonProperty("items")]
private ObservableCollection<Item2> items;
public ObservableCollection<Item2> Items
{
get
{
return items;
}
set
{
if (items != value)
{
items = value;
RaisePropertyChanged("Items");
}
}
}
//Event handling
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
//Console.WriteLine("Updated");
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public class SocialExportJSON : INotifyPropertyChanged
{
[JsonProperty("id")]
private string id;
public string ID
{
get
{
return id;
}
set
{
if (id != value)
{
id = value;
RaisePropertyChanged("ID");
}
}
}
[JsonProperty("ttl")]
private int ttl;
public int TTL
{
get
{
return ttl;
}
set
{
if (ttl != value)
{
ttl = value;
RaisePropertyChanged("TTL");
}
}
}
[JsonProperty("serial")]
private long serial;
public long Serial
{
get
{
return serial;
}
set
{
if (serial != value)
{
serial = value;
RaisePropertyChanged("Serial");
}
}
}
[JsonProperty("formatType")]
private string formatType;
public string FormatType
{
get
{
return formatType;
}
set
{
if (formatType != value)
{
formatType = value;
RaisePropertyChanged("FormatType");
}
}
}
[JsonProperty("modifiedIso8601")]
private string modifiedIso8601;
public string ModifiedIso8601
{
get
{
return modifiedIso8601;
}
set
{
if (modifiedIso8601 != value)
{
modifiedIso8601 = value;
RaisePropertyChanged("ModifiedIso8601");
}
}
}
[JsonProperty("modifiedTimestamp")]
private long modifiedTimestamp;
public long ModifiedTimestamp
{
get
{
return modifiedTimestamp;
}
set
{
if (modifiedTimestamp != value)
{
modifiedTimestamp = value;
RaisePropertyChanged("ModifiedTimestamp");
}
}
}
[JsonProperty("timezone")]
private string timezone;
public string Timezone
{
get
{
return timezone;
}
set
{
if (timezone != value)
{
timezone = value;
RaisePropertyChanged("Timezone");
}
}
}
[JsonProperty("dataType")]
private string dataType;
public string DataType
{
get
{
return dataType;
}
set
{
if (dataType != value)
{
dataType = value;
RaisePropertyChanged("DataType");
}
}
}
[JsonProperty("exports")]
private ObservableCollection<SocialExportData> exports;
public ObservableCollection<SocialExportData> Exports
{
get
{
return exports;
}
set
{
if (exports != value)
{
exports = value;
RaisePropertyChanged("Exports");
}
}
}
//Event handling
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
//Console.WriteLine("Updated");
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
In another class, I have a method to deserialize to a global instance of my JSON class. It looks like this:
public SocialExportJSON socialExportData;
private async void DownloadAndDeserializeJSONAsync()
{
try
{
//Create a web client with the supplied credentials
var exportClient = new WebClient { Credentials = new NetworkCredential(uName, pw), Encoding = Encoding.UTF8};
//Create a task to download the JSON string and wait for it to finish
var downloadTask = Task.Run(() => exportClient.DownloadString(new Uri(eURL)));
downloadTask.Wait();
//Get the string from the task
var JSONString = await downloadTask;
//Create a task to deserialize the JSON from the last task
var DeserializeTask = Task.Run(() => JsonConvert.DeserializeObject<SocialExportJSON>(JSONString));
DeserializeTask.Wait();
SocialExportJSON sej = await DeserializeTask;
//Check the timestamp first to see if we should change the data
if(socialExportData == null)
{
//Get the data from the task
socialExportData = await DeserializeTask;
}
else if(sej.ModifiedTimestamp != socialExportData.ModifiedTimestamp)
{
//Get the data from the task
SocialExportJSON newData = await DeserializeTask;
GetNewItems(newData);
SetNewData(newData);
//Call the exportUpdated event when the task has finished
exportUpdated();
}
}
catch (Exception e)
{
MessageBox.Show(e.Message.ToString());
}
}
In my SetNewData function, shown below, I use reflection to set the properties of my global class. Because I'm setting the whole collection rather than iterating through each of the properties in each of the classes, I can't use the CollectionChanged event to find new items.
public void SetNewData(SocialExportJSON newData)
{
//Loop through each of the properties and copy from source to target
foreach (PropertyInfo pi in socialExportData.GetType().GetProperties())
{
if (pi.CanWrite)
{
pi.SetValue(socialExportData, pi.GetValue(newData, null), null);
}
}
}
Is there a way I can modify my SetNewData function in such a way that it calls CollectionChanged? If not, what would be the best way to go about getting any new additions to my collection of Item2?
In my Main function. I create an instance of my class called SocialExport like so:
SocialExport s = new SocialExport("http://example.json", "example", "example");.
This class is where the global instance of my JSON class is contained, and my event handler is added like so
s.socialExportData.Exports[0].Items.CollectionChanged += CollectionChanged;
Then you are hooking up an event handler for the CollectionChanged event for that particular instance of ObservableCollection<Item2>.
If you create a new ObservableCollection<Item2>, you obviously must hook up an event handler to this one as well. The event handler that is associated with the old object won't be invoked when new items are added to the new instance.
So whenever a new ObservableCollection<Item2> is created, using deserialization or not, you should hook up a new event handler.
You could probably do this in your DownloadAndDeserializeJSONAsync method. The other option would be to create only one instance of the collection and remove and add items from/to this one.
Related
I have a simple app that stores different stock data, my add stock button opens a new window with a form and when I press save I add the stock to the database but the datagrid doesn't get updated
I access the static instance of my main Viewmodel from my EntryViewModel via:
Main View Model(StockViewModel)
public class StockViewModel:INotifyPropertyChanged
{
#region Fields
private IList<Stock> _stockList;
private StatsBuilder _statsBuilder = new StatsBuilder();
public Stock stock = new Stock();
public Stock10 stock10 = new Stock10();
public Stock20 stock20 = new Stock20();
private ICommand _searchCommand;
private static StockViewModel _instance = new StockViewModel();
public static StockViewModel Instance { get { return _instance; } }
public IList<Stock> Stocks
{
get { return _stockList; }
set { _stockList = value; OnPropertyChanged(nameof(Stocks)); }
}
#endregion
#region Constructor
public StockViewModel()
{
using (var stocks = new AppDbContext())
{
Stocks = stocks.LowFloatStocks.ToList();
}
}
#endregion
#region Day1's
private string _ticker;
public string Ticker
{
get { return _ticker; }
set
{
_ticker = value;
OnPropertyChanged("Ticker");
}
}
private DateTime _date = DateTime.Now;
public DateTime Date
{
get { return _date; }
set { _date = value; }
}
private decimal _preivousClose;
public decimal PreviousClose
{
get { return _preivousClose; }
set { _preivousClose = value; OnPropertyChanged("PreviousClose"); }
}
private decimal _pmOpeningPrice;
public decimal PM_OpeningPrice
{
get { return _pmOpeningPrice; }
set { _pmOpeningPrice = value; OnPropertyChanged("PM_OpeningPrice"); }
}
private decimal _openingPrice;
public decimal OpeningPrice
{
get { return _openingPrice; }
set { _openingPrice = value; OnPropertyChanged("OpeningPrice"); }
}
private decimal _high;
public decimal High
{
get { return _high ; }
set { _high = value; OnPropertyChanged("High"); }
}
private decimal _low;
public decimal Low
{
get { return _low; }
set { _low = value; OnPropertyChanged("Low"); }
}
private decimal _close;
public decimal Close
{
get { return _close; }
set { _close = value; OnPropertyChanged("Close"); }
}
private string _catalyst;
public string Catalyst
{
get { return _catalyst; }
set { _catalyst = value; OnPropertyChanged("Catalyst"); }
}
private decimal _float;
public decimal Float
{
get { return _float; }
set {_float = value; OnPropertyChanged("Float"); }
}
private string _dilution;
public string Dilution
{
get { return _dilution; }
set { _dilution = value; OnPropertyChanged("Dilution"); }
}
#endregion
#region Day2's
//public decimal Day2Open
//{
// //get { return stock.Day2Open; }
// set { stock.Day2Open = value; OnPropertyChanged("Day2Open"); }
//}
//public decimal Day2High
//{
// get { return stock.Day2High; }
// set { stock.Day2High = value; OnPropertyChanged("Day2High"); }
//}
//public decimal Day2Low
//{
// get { return stock.Day2Low; }
// set { stock.Day2Low = value; OnPropertyChanged("Day2Low"); }
//}
//public decimal Day2Close
//{
// get { return stock.Day2Close; }
// set { stock.Day2Close = value; OnPropertyChanged("Day2Close"); }
//}
#endregion
#region Stats
private Stock _selectedstock;
public Stock SelectedStock
{
private get
{
return _selectedstock;
}
set
{
_selectedstock = value;
OnPropertyChanged("SelectedStock");
GetStats(_selectedstock.Ticker);
}
}
public int Gaps10 { get { return stock10.Gaps10; } set { stock10.Gaps10 = value; OnPropertyChanged("Gaps10"); } }
public decimal AvgGap10 { get {return stock10.AvgGap10; } set { stock10.AvgGap10 = value; OnPropertyChanged("AvgGap10"); } }
public int CloseGreen10 { get { return stock10.CloseGreen10; } set { stock10.CloseGreen10 = value; OnPropertyChanged("CloseGreen10"); } }
public decimal CloseGreenPercent10 { get { return stock10.CloseGreenPercent10; } set { stock10.CloseGreenPercent10 = value; OnPropertyChanged("CloseGreenPercent10"); } }
public int CloseRed10 { get { return stock10.CloseRed10; } set { stock10.CloseRed10 = value; OnPropertyChanged("CloseRed10"); } }
public decimal CloseRedPercent10 { get { return stock10.CloseRedPercent10; } set { stock10.CloseRedPercent10 = value; OnPropertyChanged("CloseRedPercent10"); } }
public decimal AvgSpike10 { get; set; }
public decimal Downside10 { get; set; }
public int PMFade10 { get { return stock10.PMFade10; } set { stock10.PMFade10 = value; OnPropertyChanged("PMFade10"); } }
public decimal PMFadePercent10 { get { return stock10.PMFadePercent10; } set { stock10.PMFadePercent10 = value; OnPropertyChanged("PMFadePercent10"); } }
public int Gaps20 { get {return stock20.Gaps20 ; } set { stock20.Gaps20 = value; OnPropertyChanged("Gaps20"); } }
public decimal AvgGap20 { get { return stock20.AvgGap20; } set { stock20.AvgGap20 = value; OnPropertyChanged("AvgGap20"); } }
public int CloseGreen20 { get { return stock20.CloseGreen20; } set { stock20.CloseGreen20 = value; OnPropertyChanged("CloseGreen20"); } }
public decimal CloseGreenPercent20 { get { return stock20.CloseGreenPercent20; } set { stock20.CloseGreenPercent20 = value; OnPropertyChanged("CloseGreenPercent20"); } }
public int CloseRed20 { get { return stock20.CloseRed20; } set { stock20.CloseRed20 = value; OnPropertyChanged("CloseRed20"); } }
public decimal CloseRedPercent20 { get { return stock20.CloseRedPercent20; } set { stock20.CloseRedPercent20 = value; OnPropertyChanged("CloseRedPercent20"); } }
public decimal AvgSpike20 { get; set; }
public decimal Downside20 { get; set; }
public int PMFade20 { get { return stock20.PMFade20; } set { stock20.PMFade20 = value; OnPropertyChanged("PMFade20"); } }
public decimal PMFadePercent20 { get { return stock20.PMFadePercent20; } set { stock20.PMFadePercent20 = value; OnPropertyChanged("PMFadePercent20"); } }
#endregion
#region PropertyChange
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region Commands
public ICommand SearchCommand => _searchCommand ?? (_searchCommand = new RelayCommand(param => this.SearchStock(param)));
#endregion
#region Actions
private void SearchStock(object _ticker)
{
try
{
var stock = Stocks.First(x => x.Ticker.Trim() == _ticker.ToString());
_selectedstock = (Stock)stock;
GetStats(_ticker.ToString());
}
catch (Exception)
{
MessageBox.Show("No matching Ticker");
}
}
private void GetStats(string ticker)
{
//Stocks with 10% gap ups
stock10 = _statsBuilder.GetStats10(ticker);
stock20 = _statsBuilder.GetStats20(ticker);
Gaps10 = stock10.Gaps10;
AvgGap10 = stock10.AvgGap10;
CloseGreen10 = stock10.CloseGreen10;
CloseGreenPercent10 = stock10.CloseGreenPercent10;
CloseRed10 = stock10.CloseRed10;
CloseRedPercent10 = stock10.CloseRedPercent10;
PMFade10 = stock10.PMFade10;
PMFadePercent10 = stock10.PMFadePercent10;
//Stock with 20% gap ups
Gaps20 = stock20.Gaps20;
AvgGap20 = stock20.AvgGap20;
CloseGreen20 = stock20.CloseGreen20;
CloseGreenPercent20 = stock20.CloseGreenPercent20;
CloseRed20 = stock20.CloseRed20;
CloseRedPercent20 = stock20.CloseRedPercent20;
PMFade20 = stock20.PMFade20;
PMFadePercent20 = stock20.PMFadePercent20;
}
#endregion
}
When I click the add stock button it fires the AddCommand which in turn fires the Addstock method, this adds the stock to the database and updates the list in the StockViewModel instance but it does not update the datagrid OnPropertyChanged for some reason
EntryViewModel:
public ICommand AddCommand => _addCommand ?? (_addCommand = new RelayCommand(param => this.AddStock()));
#endregion
#region PropertyChange
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region Methods
private void AddStock()
{
using (var stocks = new AppDbContext())
{
stock.Id = 0;
stock.Ticker = _ticker;
stock.PreviousClose = _preivousClose;
stock.PM_OpeningPrice = _pmOpeningPrice;
stock.OpeningPrice = _openingPrice;
stock.High = _high;
stock.Low = _low;
stock.Close = _close;
stock.Catalyst = _catalyst;
stock.Float = _float;
stock.Dilution = _dilution;
stocks.LowFloatStocks.Add(stock);
stocks.SaveChanges();
StockViewModel.Instance.Stocks = stocks.LowFloatStocks.ToList();
Clear();
}
}
Data Grid Binding;
<DataGrid Name="StockGrid" Margin="5,5,5,5" Height="250" ItemsSource="{Binding Stocks}" IsReadOnly="True" AutoGeneratingColumn ="StockGrid_AutoGeneratingColumn" Grid.ColumnSpan="3" VerticalScrollBarVisibility="Auto" SelectedItem="{Binding SelectedStock, Mode=TwoWay}">
</DataGrid>
Data Context of StockView Model:
<Window.DataContext> <local1:StockViewModel/> </Window.DataContext>
Any suggestions?
The following sets the DataContext to an new instance of the StockViewModel which is not the same as the instance returned by the Instance property:
<Window.DataContext> <local1:StockViewModel/> </Window.DataContext>
The fact that you can do this means that StockViewModel is not really a singleton. You should add a private constuctor to it to make it a real singleton if this is what you want:
private StockViewModel() { }
You would then set the DataContext to the instance returned by the Instance property:
<Window.DataContext>
<x:Static Member="this:StockViewModel.Instance" />
</Window.DataContext>
I am right now using a nested ObservableCollection to fill in the rows of a DataGrid with the inner ObservableCollection holding information regarding each cell as follows:
public class MemoryTable : INotifyPropertyChanged
{
string _Address;
public string Address
{
get
{
return _Address;
}
set
{
if (_Address != value)
{
_Address = value;
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
bool _NextRowOverflow;
public bool NextRowOverflow
{
get
{
return _NextRowOverflow;
}
set
{
if (_NextRowOverflow != value)
{
_NextRowOverflow = value;
}
}
}
private ObservableCollection<DataAssets> _DataSpace;
public ObservableCollection<DataAssets> DataSpace
{
get
{
return _DataSpace;
}
set
{
_DataSpace = value;
RaisePropertyChanged("DataSpace");
}
}
public MemoryTable()
{
DataSpace = new ObservableCollection<DataAssets>();
}
public class DataAssets : INotifyPropertyChanged
{
string _Addresses;
public string Addresses
{
get
{
return _Addresses;
}
set
{
if (_Addresses != value)
{
_Addresses = value;
RaisePropertyChanged("Addresses");
}
}
}
string _Values;
public string Values
{
get
{
return _Values;
}
set
{
if (_Values != value)
{
_Values = value;
RaisePropertyChanged("Values");
}
}
}
string _ToolTip;
public string ToolTip
{
get
{
return _ToolTip;
}
set
{
if (_ToolTip != value)
{
_ToolTip = value;
RaisePropertyChanged("ToolTip");
}
}
}
Brush _Color;
public Brush Color
{
get
{
return _Color;
}
set
{
if (_Color != value)
{
_Color = value;
RaisePropertyChanged("Color");
}
}
}
string _ConvertedValue;
public string ConvertedValue
{
get
{
return _ConvertedValue;
}
set
{
if (_ConvertedValue != value)
{
_ConvertedValue = value;
RaisePropertyChanged("ConvertedValue");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
}
}
}
And want it to update whenever I change one of the values in the DataAsset class, however whenever I change a value by such a means:
NextRow.DataSpace[i].Color = Brushes.Yellow;
None of the cells update, the workaround i made for doing this is to clear and rewrite the entire ObservableCollection like this:
ObservableCollection<MemoryTable> Temp = new ObservableCollection<MemoryTable>();
foreach (var item in MemoryTable)
{
if (Temp.IndexOf(item) < 0)
{
Temp.Add(item);
}
}
MemoryTableDisplay.Clear();
foreach (var item in Temp)
{
if (MemoryTableDisplay.IndexOf(item) < 0)
{
MemoryTableDisplay.Add(item);
}
}
By this method I am able to force the UI to display the changes, however when moving further to working on a larger set of data, this method takes too long to accomplish, is it possible to have the inner properties to cause an update for the entire ObservableCollection?
Thank you!
Please try this.
NextRow.DataSpace[i].Color = Brushes.Yellow;
After writing down one more statement like this.
NextRow.DataSpace= NextRow.DataSpace;
I populate a data grid with a list of objects that come from a repository like this:
public static IEnumerable<Order> GetOrdersForDataGrid()
{
IEnumerable<Order> query;
using (RSDContext = new RSDContext())
{
query = context.Orders.Include(o=>o.OrderDetails).ToList();
}
return query;
}
When I want to edit an order I pass the selected row to a new window like this:
OrderEditWindow orderEdit = new OrderEditWindow();
orderEdit.SelectedOrder = SelectedOrder;
orderEdit.ShowDialog();
Here I set the DataContext of the Window to:
DataContext = SelectedOrder;
In this window I have another data grid that binds to OrderDetails collection property of Order. The problem is on CRUD operations on OrderDetails. For example, after I add a new orderDetail like this:
private void AddProductDetailButton_OnClick(object sender, RoutedEventArgs e)
{
if (!ValidateProductDetail())
return;
var _selectedProduct = ProductAutoCompleteBox.SelectedItem as Product;
var selectedProduct = ProductsRepository.GetProductById(_selectedProduct.ProductId);
OrderDetail orderDetail = new OrderDetail();
orderDetail.Price = selectedProduct.Price;
orderDetail.ProductCode = selectedProduct.Code;
orderDetail.ProductName = selectedProduct.Name;
orderDetail.Quantity = int.Parse(QuantityNumericUpDown.Value.ToString());
orderDetail.Um = selectedProduct.Um;
orderDetail.Total = selectedProduct.Price * int.Parse(QuantityNumericUpDown.Value.ToString());
orderDetail.Group = selectedProduct.Subgroup.Group.Name;
orderDetail.Subgroup = selectedProduct.Subgroup.Name;
orderDetail.SupplierName = selectedProduct.Supplier.Name;
//orderDetail.Order=SelectedOrder;
//orderDetail.OrderId = SelectedOrder.OrderId;
SelectedOrder.OrderDetails.Add(orderDetail);
ProductAutoCompleteBox.Text = string.Empty;
QuantityNumericUpDown.Value = 1;
ProductAutoCompleteBox.Focus();
}
and then I call the update method from repository:
public static void UpdateOrder(Order order)
{
using (RSDContext context = new RSDContext())
{
context.Orders.Attach(order);
context.Entry(order).State = EntityState.Modified;
context.SaveChanges();
}
}
I get an error about OrderId. If i set manualy the navigation property and the id I don't get an error but changes dont get saved into db.
My Order model look like this:
public class Order : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Order()
{
_OrderDetails = new ObservableCollection<OrderDetail>();
_OrderDetails.CollectionChanged += _OrderDetails_CollectionChanged;
}
void _OrderDetails_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
AttachProductChangedEventHandler(e.NewItems.Cast<OrderDetail>());
if (e.OldItems != null)
CalcualteTotals();
}
[NotMapped]
public decimal CalculatedTotal
{
get
{
return OrderDetails.Sum(x => x.Total);
}
}
public int OrderId { get; set; }
private int _Number;
public int Number
{
get { return _Number; }
set
{
_Number = value;
NotifyPropertyChanged("Number");
}
}
private DateTime _Date;
public DateTime Date
{
get { return _Date; }
set
{
_Date = value;
NotifyPropertyChanged("Date");
}
}
private bool _Canceled;
public bool Canceled
{
get { return _Canceled; }
set
{
_Canceled = value;
NotifyPropertyChanged("Canceled");
}
}
private string _ClientName;
public string ClientName
{
get { return _ClientName; }
set
{
_ClientName = value;
NotifyPropertyChanged("ClientName");
}
}
private string _ClientPhone;
public string ClientPhone
{
get { return _ClientPhone; }
set
{
_ClientPhone = value;
NotifyPropertyChanged("ClientPhone");
}
}
private string _DeliveryAddress;
public string DeliveryAddress
{
get { return _DeliveryAddress; }
set
{
_DeliveryAddress = value;
NotifyPropertyChanged("DeliveryAddress");
}
}
private decimal _Transport;
public decimal Transport
{
get { return _Transport; }
set
{
_Transport = value;
NotifyPropertyChanged("Transport");
}
}
private decimal _Total;
public decimal Total
{
get { return _Total; }
set
{
_Total = value;
NotifyPropertyChanged("Total");
}
}
private ObservableCollection<OrderDetail> _OrderDetails;
public virtual ObservableCollection<OrderDetail> OrderDetails
{
//get { return _OrderDetails ?? (_OrderDetails = new ObservableCollection<OrderDetail>()); }
get
{
return _OrderDetails;
}
set
{
_OrderDetails = value;
NotifyPropertyChanged("OrderDetails");
}
}
private void AttachProductChangedEventHandler(IEnumerable<OrderDetail> orderDetails)
{
foreach (var p in orderDetails)
{
p.PropertyChanged += (sender, e) =>
{
switch (e.PropertyName)
{
case "Quantity":
case "Price":
case "Total":
CalcualteTotals();
break;
}
};
}
CalcualteTotals();
}
public void CalcualteTotals()
{
NotifyPropertyChanged("CalculatedTotal");
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
And my OrderDetail model look like this:
public class OrderDetail : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int OrderDetailId { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; }
private int _ProductCode;
public int ProductCode
{
get { return _ProductCode; }
set
{
_ProductCode = value;
NotifyPropertyChanged("ProductCode");
}
}
private string _ProductName;
public string ProductName
{
get { return _ProductName; }
set
{
_ProductName = value;
NotifyPropertyChanged("ProductName");
}
}
private string _Um;
public string Um
{
get { return _Um; }
set
{
_Um = value;
NotifyPropertyChanged("Um");
}
}
private decimal _Price;
public decimal Price
{
get { return _Price; }
set
{
_Price = value;
NotifyPropertyChanged("Price");
NotifyPropertyChanged("Total");
}
}
private int _Quantity;
public int Quantity
{
get { return _Quantity; }
set
{
_Quantity = value;
NotifyPropertyChanged("Quantity");
NotifyPropertyChanged("Total");
}
}
private string _SupplierName;
public string SupplierName
{
get { return _SupplierName; }
set
{
_SupplierName = value;
NotifyPropertyChanged("SupplierName");
}
}
private string _Subgroup;
public string Subgroup
{
get { return _Subgroup; }
set
{
_Subgroup = value;
NotifyPropertyChanged("Subgroup");
}
}
private string _Group;
public string Group
{
get { return _Group; }
set
{
_Group = value;
NotifyPropertyChanged("Group");
}
}
public decimal _Total;
public decimal Total
{
get { return Quantity * Price; }
set
{
_Total = value;
NotifyPropertyChanged("Total");
}
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
I'm really trying to use some sort of unit of work and I don't understand how i'm supposed to apply CRUD on objects with child collections and keep the UI updated in the same time (by working in a ObservableCollection and using Binding ClientPhone, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged my parent window is updated as I type)
A final working solution:
using (RSDContext context = new RSDContext())
{
var details = order.OrderDetails;
order.OrderDetails = null;
List<int> OriginalOrderDetailsIds =
context.OrderDetails.Where(o => o.OrderId == order.OrderId).Select(o => o.OrderDetailId).ToList();
List<int> CurrentOrderDetailsIds = details.Select(o => o.OrderDetailId).ToList();
List<int> DeletedOrderDetailsIds = OriginalOrderDetailsIds.Except(CurrentOrderDetailsIds).ToList();
context.Entry(order).State = EntityState.Modified;
foreach (var deletedOrderDetailId in DeletedOrderDetailsIds)
{
context.Entry(context.OrderDetails.Single(o => o.OrderDetailId == deletedOrderDetailId)).State = EntityState.Deleted;
}
foreach (OrderDetail detail in details)
{
// Add.
if (detail.OrderDetailId == 0)
{
detail.OrderId = order.OrderId;
context.Entry(detail).State = EntityState.Added;
}
// Update.
else
{
context.Entry(detail).State = EntityState.Modified;
}
}
context.SaveChanges();
}
You could do this way for adding and updating the child, but not sure about deleted order details in the ui. If you don't want to get the order from entity, you need some kind of marking in the OrderDetail for deleted OrderDetail.
using (RSDContext context = new RSDContext())
{
var details = order.OrderDetails;
order.OrderDetails = null;
context.Entry(order).State = EntityState.Modified;
foreach (var detail in details)
{
if (detail.Id == 0)
{
// Adds.
detail.OrderId = order.Id;
context.Entry(detail).State = EntityState.Added;
}
else if (detail.IsDeleted)
// Adds new property called 'IsDeleted'
// and add [NotMapped] attribute
// then mark this property as true from the UI for deleted items.
{
// Deletes.
context.Entry(detail).State = EntityState.Deleted;
}
else
{
// Updates.
context.Entry(detail).State = EntityState.Modified;
}
}
order.OrderDetails = details;
context.SaveChanges();
}
I have problem with windows phone 8 local database. That's how my database code looks:
[Table(Name = "Folders")]
public class FolderTable : INotifyPropertyChanged, INotifyPropertyChanging, IEqualityComparer<FolderTable>
{
private string _folderName;
private string _description;
private string _password;
private string _tileColorName;
[Column(IsPrimaryKey = true)]
public string FolderName
{
get { return _folderName; }
set
{
if (_folderName != value)
{
NotifyPropertyChanging();
_folderName = value;
NotifyPropertyChanged();
}
}
}
[Column(CanBeNull = false)]
public string Description
{
get { return _description; }
set
{
if (_description != value)
{
NotifyPropertyChanging();
_description = value;
NotifyPropertyChanged();
}
}
}
[Column]
public string Password
{
get { return _password; }
set
{
if (_password != value)
{
NotifyPropertyChanging();
_password = value;
NotifyPropertyChanged();
}
}
}
[Column]
public string TileColorName
{
get { return _tileColorName; }
set
{
if (_tileColorName != value)
{
NotifyPropertyChanging();
_tileColorName = value;
NotifyPropertyChanged();
}
}
}
private void NotifyPropertyChanging([CallerMemberName] string propertyName = "")
{
if ( PropertyChanging != null)
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if ( PropertyChanged != null )
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangingEventHandler PropertyChanging;
public bool Equals(FolderTable x, FolderTable y)
{
return x.FolderName == y.FolderName;
}
public int GetHashCode(FolderTable obj)
{
return obj.FolderName.GetHashCode();
}
}
Folder add:
public Task Add(Folder folder)
{
var folderTable = GetFolderTable(folder);
_databaseContext.Folders.InsertOnSubmit(folderTable);
return Task.FromResult(true);
}
Commit:
public Task Commit()
{
try
{
_databaseContext.Folders.Context.SubmitChanges();
}
catch (DuplicateKeyException duplicateKeyException)
{
_databaseContext.Dispose();
_databaseContext = new FolderDbContext(FolderDbContext.DBConnectionString);
throw new FolderAlreadyExistsException(duplicateKeyException);
}
return Task.FromResult(true);
}
When I Add new Folder (folder with such name didn't exist before), Commit and then I try add folder with same name DuplicateKeyException is thrown and handled correctly. However after context gets recreated in catch section the next call to Add and Commit folder with same name throws SqlCeException with message: "A duplicate value cannot be inserted into a unique index. [ Table name = Folders,Constraint name = PK_Folders ]". Is that normal behaviour or am I doing something wrong? In what cases DuplicateKeyException is thrown? How to catch SqlCeException? I can't even see System.Data.SqlServerCe namespace.
use reflection to get the type name of the ex eption, if you wish to catch the SqlCeException explicitly:
ex.GetType().Name == "SqlCeException"
What is the best way to create a class with an event that fires when one of its Properties is changed? Specifically, how do you convey to any subscribers which Property was changed?
Ex:
public class ValueChangedPublisher
{
private int _prop1;
private string _prop2;
public static event ValueChangedHandler(/*some parameters?*/);
public int Prop1
{
get { return _prop1; }
set
{
if (_prop1 != value)
{
_prop1 = value;
ValueChangedHandler(/*parameters?*/);
}
}
}
public string Prop2
{
get { return _prop2; }
set
{
if (_prop2 != value)
{
_prop2 = value;
ValueChangedHandler(/*parameters?*/);
}
}
}
}
public class ValueChangedSubscriber
{
private int _prop1;
private string _prop2;
public ValueChangedSubscriber()
{
ValueChangedPublisher.ValueChanged += ValueChanged;
}
private void ValueChanged(/*parameters?*/)
{
/*how does the subscriber know which property was changed?*/
}
}
My goal is to make this as extensible as possible (e.g. I don't want a bunch of huge if/else if/switch statements lumbering around). Does anybody know of a technique to achieve what I'm looking for?
EDIT:
What I'm really looking for is how to utilize the INotifyPropertyChanged pattern on the subscriber side. I don't want to do this:
private void ValueChanged(string propertyName)
{
switch(propertyName)
{
case "Prop1":
_prop1 = _valueChangedPublisher.Prop1;
break;
case "Prop2":
_prop2 = _valueChangedPublisher.Prop2;
break;
// the more properties that are added to the publisher, the more cases I
// have to handle here :/ I don't want to have to do it this way
}
}
.NET provides the INotifyPropertyChanged interface. Inherit and implement it:
public class ValueChangedPublisher : INotifyPropertyChanged
{
private int _prop1;
private string _prop2;
public event PropertyChangedEventHandler ValueChangedHandler;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public int Prop1
{
get { return _prop1; }
set
{
if (_prop1 != value)
{
_prop1 = value;
NotifyPropertyChanged();
}
}
}
public string Prop2
{
get { return _prop2; }
set
{
if (_prop2 != value)
{
_prop2 = value;
NotifyPropertyChanged();
}
}
}
}
Here is what I did to solve my problem. It's a bit large, so maybe not performant, but works for me.
Edit
See this question for performance details: C# using properties with value types with Delegate.CreateDelegate
Base Class for publishing property change stuff:
public abstract class PropertyChangePublisherBase : INotifyPropertyChanged
{
private Dictionary<string, PropertyInfo> _properties;
private bool _cacheProperties;
public bool CacheProperties
{
get { return _cacheProperties; }
set
{
_cacheProperties = value;
if (_cacheProperties && _properties == null)
_properties = new Dictionary<string, PropertyInfo>();
}
}
protected PropertyChangePublisherBase(bool cacheProperties)
{
CacheProperties = cacheProperties;
}
public bool ContainsBinding(PropertyChangedEventHandler handler)
{
if (PropertyChanged == null)
return false;
return PropertyChanged.GetInvocationList().Contains(handler);
}
public object GetPropertValue(string propertyName)
{
if (String.IsNullOrEmpty(propertyName) || String.IsNullOrWhiteSpace(propertyName))
throw new ArgumentException("Argument must be the name of a property of the current instance.", "propertyName");
return ProcessGetPropertyValue(propertyName);
}
protected virtual object ProcessGetPropertyValue(string propertyName)
{
if (_cacheProperties)
{
if (_properties.ContainsKey(propertyName))
{
return _properties[propertyName].GetValue(this, null);
}
else
{
var property = GetType().GetProperty(propertyName);
_properties.Add(propertyName, property);
return property.GetValue(this, null);
}
}
else
{
var property = GetType().GetProperty(propertyName);
return property.GetValue(this, null);
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Base Class for receiving property change stuff:
public abstract class PropertyChangeSubscriberBase
{
protected readonly string _propertyName;
protected virtual object Value { get; set; }
protected PropertyChangeSubscriberBase(string propertyName, PropertyChangePublisherBase bindingPublisher)
{
_propertyName = propertyName;
AddBinding(propertyName, this, bindingPublisher);
}
~PropertyChangeSubscriberBase()
{
RemoveBinding(_propertyName);
}
public void Unbind()
{
RemoveBinding(_propertyName);
}
#region Static Fields
private static List<string> _bindingNames = new List<string>();
private static List<PropertyChangeSubscriberBase> _subscribers = new List<PropertyChangeSubscriberBase>();
private static List<PropertyChangePublisherBase> _publishers = new List<PropertyChangePublisherBase>();
#endregion
#region Static Methods
private static void PropertyChanged(object sender, PropertyChangedEventArgs args)
{
string propertyName = args.PropertyName;
if (_bindingNames.Contains(propertyName))
{
int i = _bindingNames.IndexOf(propertyName);
var publisher = _publishers[i];
var subscriber = _subscribers[i];
subscriber.Value = publisher.GetPropertValue(propertyName);
}
}
public static void AddBinding(string propertyName, PropertyChangeSubscriberBase subscriber, PropertyChangePublisherBase publisher)
{
if (!_bindingNames.Contains(propertyName))
{
_bindingNames.Add(propertyName);
_publishers.Add(publisher);
_subscribers.Add(subscriber);
if (!publisher.ContainsBinding(PropertyChanged))
publisher.PropertyChanged += PropertyChanged;
}
}
public static void RemoveBinding(string propertyName)
{
if (_bindingNames.Contains(propertyName))
{
int i = _bindingNames.IndexOf(propertyName);
var publisher = _publishers[i];
_bindingNames.RemoveAt(i);
_publishers.RemoveAt(i);
_subscribers.RemoveAt(i);
if (!_publishers.Contains(publisher))
publisher.PropertyChanged -= PropertyChanged;
}
}
#endregion
}
Actual class to use for subscribing to property change stuff:
public sealed class PropertyChangeSubscriber<T> : PropertyChangeSubscriberBase
{
private PropertyChangePublisherBase _publisher;
public new T Value
{
get
{
if (base.Value == null)
return default(T);
if (base.Value.GetType() != typeof(T))
throw new InvalidOperationException(String.Format("Property {0} on object of type {1} does not match the type Generic type specified {2}.", _propertyName, _publisher.GetType(), typeof(T)));
return (T)base.Value;
}
set { base.Value = value; }
}
public PropertyChangeSubscriber(string propertyName, PropertyChangePublisherBase bindingPublisher)
: base(propertyName, bindingPublisher)
{
_publisher = bindingPublisher;
}
}
Here is an example of the Class with properties that you wish to be notified about:
public class ExamplePublisher: PropertyChangedPublisherBase
{
private string _id;
private bool _testBool;
public string Id
{
get { return _id; }
set
{
if (value == _id) return;
_id = value;
RaisePropertyChanged("Id");
}
}
public bool TestBool
{
get { return _testBool; }
set
{
if (value.Equals(_testBool)) return;
_testBool = value;
RaisePropertyChanged("TestBool");
}
}
}
Here is an example of the Class that will be notified when the properties in the class above change:
public class ExampleReceiver
{
public PropertyChangeSubscriber<string> Id { get; set; }
public PropertyChangeSubscriber<bool> TestBool { get; set; }
public MyExampleClass(PropertyChangePublisherBase publisher)
{
Id = new PropertyChangeSubscriber<string>("Id", publisher);
TestBool = new PropertyChangeSubscriber<bool>("TestBool", publisher);
}
}