Proper use of DifferentDatabaseScope when it's within another SessionScope - castle-activerecord

I have the following code (simplified for brevity)
[ActiveRecord(Table = "[Order]"),
Serializable]
public class Order : ActiveRecordLinqBase<Order>, INotifyPropertyChanged
{
//Other properties omitted for brevity
private IList<Reading> _readings;
[HasMany(Inverse = true, Cascade = ManyRelationCascadeEnum.All)]
public virtual IList<Reading> Readings
{
get { return _readings; }
set
{
_readings = value;
DoPropertyChanged(() => this.Readings);
}
}
}
using (new SessionScope(FlushAction.Never) {
Order o = _repository.GetOrder(OrderId);
bool result = ShowReadings(this, o);
if (result) s.Flush();
}
bool ShowReadings(Window owner, Order o) {
//populate a datagrid with Reading objects from Order
this.Readings = Order.Readings //datagrid binds to Readings property of the form
if (cbxHistoricalChecked) {
using(new DifferentDatabaseScope(SomeOtherConnectionString)) {
//bind datagrid to historical readings
this.Readings = GetHistoricalReadings(Order.OrderId);
}
}
if (SignedOff) {
using(var t = new TransactionScope()) {
//update some properties on the Order
t.VoteCommit();
}
}
return true;
}
So what I'm doing is having a SessionScope open while the readings form is open, and flushed once it is closed. If the user wants to view historical readings for the order, I have to open a different database for the historical readings.
Things work fine as long as I don't open the DifferentDatabaseScope, but when I do, the creation of a new TransactionScope fails:
Castle.ActiveRecord.Framework.Scopes.ScopeMachineryException {"Tried to unregister a scope that is not the active one"}
Is it possible to do what I'm trying to do, and if so what is the proper way to do it?

Related

How to refresh Context in Entity framework

I cannot get updated item in ListView after modifying existing database item. Though, once I reload the application one can see updated item in ListView.
I have binded to an ObservableCollection for the ListView
This is my interface
public interface IService
{
IEnumerable<Employee> GetDetails();
IEnumerable<Employee> GetDetailsById(int MatchID);
}
I have implemented IService IEmployeeServiceData class.
public class IEmployeeServiceData:IService
{
private EmployeeContext Context
{
get;
set;
}
public IEmployeeServiceData()
{
Context = new EmployeeContext();
}
public IEnumerable<Model.Employee> GetDetails()
{
return Context.Employees;
}
public IEnumerable<Model.Employee> GetDetailsById(int MatchID)
{
var q = from f in Context.Employees
where f.EMPLOYEE_ID == MatchID
select f;
return q.AsEnumerable();
}
}
This is my VM
public void RefereshData()
{
var e = EmployeeService.GetDetails();
if (SelectedIndexValue == 1)
{
var Data = from e1 in e
where e1.LOCATION == "Pune"
select e1;
EmployeeMasterData = new ObservableCollection<Model.Employee>(Data);
}
else if(SelectedIndexValue==2)
{
var Data = from e1 in e
where e1.LOCATION == "Bangalore"
select e1;
EmployeeMasterData = new ObservableCollection<Model.Employee>(Data);
}
else
{
EmployeeMasterData = new ObservableCollection<Model.Employee>(e);
}
}
Updating Exiting Item:
public void UpdateEmployee()
{
try
{
Context = new EmployeeContext();
Employee Emp = Context.Employees.First(i => i.EMPLOYEE_ID == FindId1);
Emp.FIRST_NAME = this.FIRSTNAME;
Emp.FAMILY_NAME = this.FAMILY_NAME;
Emp.EXTERNAL_ID = this.EXTERNALID;
Emp.DB_SPONSOR = this.DBSPONSOR;
Emp.LEGAL_ENTITY = this.LEGAL_ENTITY;
Emp.COST_CENTER = this.COST_CENTER1;
Emp.STATUS = this.StatusCategory;
Emp.ENTRY_DATE = this.ENTRYDATE;
Emp.LOCATION = this.LocationCategory1;
Context.SaveChanges();
Clear();
AlertMessage1 = "Employee Record is Updated Sucessfulyy !!!";
IsVisible1 = true;
timer.Start();
}
catch(Exception ex)
{
Console.WriteLine(ex.InnerException);
}
}
Existing Item
Updated Item
Changes done to an entity in Entity Framework will not be reflected on screen because the two instances are not related in your example. Yes the have the same values, but they are two different distinct reference locations in memory. **For the ObservableCollection is a copy of the list and not the actual list being manipulated in your example.
Hence they are not related.
To show a change you have these options:
Change the actual object's property(ies) held by the observable collection to mirror the change done in to the other EF entity. Also the EF entity must adhere to INotifyPropertyChange or also the data property change won't be seen on the screen.
Or delete the screen entity and add the changed entity into the list.
Or delete the whole observable list and re-add it with the latest version from EF. (You mention that you do this and that is an option as well).

How to refresh the binding again using mvvmlight

The CurrentSelectedBall is updated whenever I changed its value due to two-way binding.
When I click to update the function UpdateRedBall is called so the redball in the database is updated. But the view list of balls is not updated as the ObservableCollection<RedBall> RedBalls is not changed at all.
How to fix this problem? I guess something needs to be done after _context.SaveChanges();
Also I can not simply do DataGridA.itemsSource = RedBalls to make a hard refresh here as first DataGridA is not accessible in the MainviewModel.
Some of the methods:
public ObservableCollection<RedBall> RedBalls
{
get { return new ObservableCollection<RedBall>(_context.RedBalls.OrderBy(m=>m.DateNumber));}
set
{
_redBalls = value;
RaisePropertyChanged("RedBalls");
}
}
public RedBall CurrentSelectedRedBall
{
get { return _currentSelectedRedBall; }
set
{
_currentSelectedRedBall = value;
RaisePropertyChanged("CurrentSelectedRedBall");
}
}
private void UpdateRedBall()
{
if (CurrentSelectedRedBall != null)
{
var ballToUpdate = _context.RedBalls.SingleOrDefault(x => x.Id == CurrentSelectedRedBall.Id);
ballToUpdate.DateNumber = CurrentSelectedRedBall.DateNumber;
ballToUpdate.First = CurrentSelectedRedBall.First;
ballToUpdate.Second = CurrentSelectedRedBall.Second;
ballToUpdate.Third = CurrentSelectedRedBall.Third;
ballToUpdate.Fourth = CurrentSelectedRedBall.Fourth;
ballToUpdate.Fifth = CurrentSelectedRedBall.Fifth;
ballToUpdate.Sixth = CurrentSelectedRedBall.Sixth;
}
_context.SaveChanges();
//RedBalls = RedBalls
}
I have a strong dislike for getters that 'do stuff'. It should be impossible for a get to fail. I would consider moving the logic in your RedBalls getter elsewhere. I'm also concerned that you're newing up the RedBalls ObservableCollection on every get as it can cause problems with bindings. Bound ObservableCollections should be instantiated once, then altered using Add, Remove and Clear. Working with them this way should also solve the problem of updating your collection appropriately.
public ObservableCollection<RedBall> RedBalls
{
get { return _redBalls; }
set
{
_redBalls = value;
RaisePropertyChanged("RedBalls");
}
}
public RedBall CurrentSelectedRedBall
{
get { return _currentSelectedRedBall; }
set
{
_currentSelectedRedBall = value;
RaisePropertyChanged("CurrentSelectedRedBall");
}
}
private void UpdateCurrentSelectedRedBall()
{
UpdateRedBall();
var redBalls = _context.RedBalls.OrderBy(m=>m.DateNumber);
if(redBalls != null)
{
RedBalls.Clear();
foreach(RedBall rb in redBalls)
{
RedBalls.Add(rb);
}
}
}
private void UpdateRedBall()
{
if (CurrentSelectedRedBall != null)
{
var ballToUpdate = _context.RedBalls.SingleOrDefault(x => x.Id == CurrentSelectedRedBall.Id);
ballToUpdate.DateNumber = CurrentSelectedRedBall.DateNumber;
ballToUpdate.First = CurrentSelectedRedBall.First;
ballToUpdate.Second = CurrentSelectedRedBall.Second;
ballToUpdate.Third = CurrentSelectedRedBall.Third;
ballToUpdate.Fourth = CurrentSelectedRedBall.Fourth;
ballToUpdate.Fifth = CurrentSelectedRedBall.Fifth;
ballToUpdate.Sixth = CurrentSelectedRedBall.Sixth;
}
_context.SaveChanges();
}
Use something like this: https://gist.github.com/itajaja/7507120 - basically you need to subscribe for PropertyChanged and raise CollectionChanged events from that.

How to search in a lazy-loading WPF MVVM TreeView?

I need a TreeView to represent some hierarchical data from multiple tables stored in a SQL Server CE database. Before, the data was stored in xml and was simple deserialized on startup and everything was good. Now I was asked to move data to a database and I've faced a several problems.
My first problem was that it takes quite a long time to retrieve many items from DB and build a TreeView ViewModel from this items (still not sure what is longer - to get items or to construct this tree). So I implemented lazy loading and now I'm getting items only when a TreeViewItem is expanding.
Now, I need to perform a text search over all the nodes, but to make it work, all nodes must be loaded.
I tried to load all of them but the UI freezes while the tree is loading. Doing this inside a BackgroundWorker is also impossible for me because the items are stored in an ObservableCollection and I'm getting "InvalidOperationException". Using Dispatcher helps with this but it is also freezes UI...
The excerpt from my TreeViewItem VM is below, if more code is needed please let me know. Maybe I am totally wrong with my design, so any comments are very appreciated!
public class TreeViewItemViewModel: DisplayableItem, IItemsHost
{
internal static DummyTreeViewItemViewModel _dummy = new DummyTreeViewItemViewModel();
public TreeViewItemViewModel(){}
public TreeViewItemViewModel(IDisplayableItem displayableItem)
{
Data = displayableItem;
}
public TreeViewItemViewModel(IDisplayableItem displayableItem, IDisplayableItem parent)
:this(displayableItem)
{
Parent = parent as TreeViewItemViewModel;
}
private TreeViewItemViewModel _parent;
public TreeViewItemViewModel Parent
{
get { return _parent; }
set { _parent = value; InvokePropertyChanged(new PropertyChangedEventArgs("Parent")); }
}
private IDisplayableItem _data;
public new IDisplayableItem Data
{
get { return _data; }
set { _data = value; InvokePropertyChanged(new PropertyChangedEventArgs("Data")); }
}
private bool _isSelected;
public new bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; InvokePropertyChanged(new PropertyChangedEventArgs("IsSelected")); }
}
private bool _isEnabled=true;
public new bool IsEnabled
{
get { return _isEnabled; }
set { _isEnabled = value; InvokePropertyChanged(new PropertyChangedEventArgs("IsEnabled")); }
}
private bool _isVisible = true;
public new bool IsVisible
{
get { return _isVisible; }
set { _isVisible = value; InvokePropertyChanged(new PropertyChangedEventArgs("IsVisible")); }
}
private void FillItems()
{
if (Items.Contains(_dummy))
{
Items.Remove(_dummy);
var itemshost = Data as IItemsHost;
if (itemshost != null)
{
_items = new ObservableCollection<IDisplayableItem>();
foreach (var item in itemshost.Items)//getting 'Items' actually requesting them from a database
{
var treeItem = new TreeViewItemViewModel(item, this);
_items.Add(treeItem);
}
InvokePropertyChanged(new PropertyChangedEventArgs("Items"));
}
}
}
protected bool _isExpanded;
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if(value)
{
FillItems();
}
_isExpanded = value;
InvokePropertyChanged(new PropertyChangedEventArgs("IsExpanded"));
}
}
protected SObservableCollection<IDisplayableItem> _items = new SObservableCollection<IDisplayableItem>();
public SObservableCollection<IDisplayableItem> Items
{
get
{
var itemshost = Data as IItemsHost;
if (itemshost != null)
{
if (_items.Count == 0 && itemshost.Items.Count > 0)
_items.Add(_dummy);
}
return _items;
}
set { _items = value; InvokePropertyChanged(new PropertyChangedEventArgs("Items")); }
}
UPDATE: for those who would search for a similar solution - my problem was in my query method. I shouldn't open a new SQL Server CE connection each time I need to perform a query...
What about a new DB table that holds a flattened representation of the entire hierarchy, and have your search logic query this table? You'll obviously need to keep this table updated as you insert/update/delete records in the other tables.
Each record in the new table would need to include some information about where the item sits in the hierarchy, so that when you get the search results back you can load and populate just those tree nodes containing the "hits".
Since reading from database is being done asynchronously so the performance bottle-neck should be constructing View from ViewModel. I suggest the following method:
Read all essential Model data from database in one async call and store them in an object called SearchHelper.
Add a a simple property (Model.Id or Model's hash code) to every ViewModel that you create in order to find the equivalent view model of an specific model.
Create only visible ViewModels. (lazy loading for ViewModel only)
Use the SearchHelper to find matches for the search query, then using the Id or hash code of the results, you can easily locate their equivalent view models.
Please Consider:
Once loaded, SearchHelper does not update itself, so you might want to manually update it.
For this method to have optimal performance, try avoiding iteration of all nodes. instead, store the sequence of traced items (their index or Id) in order to find them in view model. if each Model item knows its parent, then the back-tracking should be easy.

Binding ComboBox and ObservableCollection<KeyValue> in wpf

in My OpenViewModel i collect data:
private ObservableCollection<KeyValue> availableData;
public ObservableCollection<KeyValue> AvailableDatas
{
get { return availableData; }
set
{
if (value != availableData)
{
availableData= value;
NotifyOfPropertyChange("AvailableDatas");
}
}
}
method for collecting data:
public ObservableCollection<KeyValue> CollectData()
{
ConnectorClient client = null;
try
{
client = webservice.GetClient();
AvailableDatas = client.GetDatas();
client.Close();
}
catch (Exception ex)
{
webservice.HandleException(ex, client);
}
return AvailableDatas;
}
How to call the method CollectData in wpf and fill my COmboBox?
thx
You might simply call the method the first time the AvailableDatas property is accessed (e.g. from a binding in XAML):
private ObservableCollection<KeyValue> availableData;
public ObservableCollection<KeyValue> AvailableDatas
{
get
{
if (availableData == null)
{
availableData = CollectData();
}
return availableData;
}
set
{
if (value != availableData)
{
availableData = value;
NotifyOfPropertyChange("AvailableDatas");
}
}
}
Then you should change the CollectData method in a way that is does not also set the property:
public ObservableCollection<KeyValue> CollectData()
{
ConnectorClient client = null;
ObservableCollection<KeyValue> data = null;
try
{
client = webservice.GetClient();
data = client.GetDatas();
client.Close();
}
catch (Exception ex)
{
webservice.HandleException(ex, client);
}
return data;
}
You could override the OnActivated() event assuming you are using an IScreen implementation and load data in there, or just do it in the constructor or a custom Initialise method if you want to roll your own (or in the property accessor as someone has already said).
You can also use coroutines if you want some visual context for the user and a better tie in with CM actions
There is a nice simple implementation of a Loader class here which helps provide visual context to the user:
https://caliburnmicro.codeplex.com/wikipage?title=IResult%20and%20Coroutines&referringTitle=Documentation
This searches the visual tree for a BusyIndicator control and activates it whilst the content is loading e.g. ...
public class SomeViewModel : Screen
{
protected override void OnActivate()
{
RefreshData();
}
public void RefreshData()
{
Coroutine.BeginExecute(LoadData(), new ActionExecutionContext() { Target = this });
}
public IEnumerable<IResult> LoadData()
{
yield return Loader.Show("Loading Data...");
yield return new LoadSomeDataRoutine(client.GetDatas);
yield return Loader.Hide();
}
}
The reason to have a RefreshData method is that this also allows you to bind CM actions and allows the coroutine can grab more contextual information.
Obviously you have less need to worry about the async->sync benefits this gives in Silverlight because you are using WPF (but it still applies to async web service calls), however it still has many benefits and it also helps you to write reusable routines which become part of your application framework (e.g. some level of error handling/logging encapsulated in the IResult implementation etc)
You also mentioned filling the combobox... all you would need to do in CM is place a combobox on your control, and set it's Name property to the name of the property on your VM:
public class SomeViewModel : Screen
{
public ObservableCollection<MyObject> MyProperty { //blah blah... }
}
<UserControl .... blah>
<ComboBox x:Name="MyProperty" />
</UserControl>
This will fill the combobox with the items. You will still need to set the binding for SelectedItem/SelectedValue
I assume you know this already though - if not CM has some decent documentation:
https://caliburnmicro.codeplex.com/wikipage?title=Basic%20Configuration%2c%20Actions%20and%20Conventions&referringTitle=Documentation

WPF-Multiple Views on ObservableCollection

I have a viewmodel containing two CollectionViews defined.
One I am using for navigation and data entry/edit.
Another I want to use for filtering purpose and show the filteration in some Listview on the form.
I don't want the main view(used for DataEntry purpose) to get affected while I applying filteration on observablecollection.
Thanks in Advance!
As long as you're using separate collection views, changing one won't affect the other. That is the point of collection views - they're independent views on the same collection.
ok, Got it! and went ahead with the same idea. But when I did so, I get Error = "The calling thread cannot access this object because a different thread owns it.". Hence my filteration doesn't work.. Following is the code-
public ICollectionView Clients { get; set; } //Used for Data-navigation/modification
public ListCollectionView CodeView { get; set; } // to be used for filteration purpose on form.
string searchText = String.Empty;
public string CompanyCodeSearch
{
get { return searchText; }
set
{
try
{
searchText = value;
OnPropertyChanged("CompanyCodeSearch");
CodeView.Filter = new Predicate<object>(cmFilterData);
}
catch (Exception ex)
{
}
}
}
private bool cmFilterData(object item)
{
bool _filteredData = false;
try
{
var value = (item as cntClient);
if (value == null || value.CompanyCode == null)
return false;
_filteredData = value.CompanyCode.StartsWith(this.CompanyCodeSearch);
return _filteredData;
}
catch (Exception ex)
{
return false;
}
}

Resources