在 mvvm 问题中创建 class 结构
creating class structure in mvvm issues
我有以下 Classes:
项目
public class Item : INotifyPropertyChanged, IDataErrorInfo
{
private int? id;
public int? ID
{
get
{ return id; }
set
{ id = value; }
}
private string name;
public string Name
{
get
{ return name; }
set
{
if (value != name)
{
ClearError("Name");
if (string.IsNullOrEmpty(value) || value.Trim() == "")
SetError("Name", "Required Value");
name = value;
}
}
}
private List<MedicineComposition> medicineCompositions;
public List<MedicineComposition> MedicineCompositions
{
set { medicineCompositions = value; }
get { return medicineCompositions; }
}
}
药物成分
public class MedicineComposition : INotifyPropertyChanged, IDataErrorInfo
{
private int? id;
public int? ID
{
get
{ return id; }
set
{ id = value; }
}
private Item item;
public Item Item
{
get
{ return item; }
set
{
if (item != value)
{
ClearError("Item");
if (value == null)
SetError("Item", "Required Value");
item = value;
}
}
}
private Component component;
public Component Component
{
get
{ return component; }
set
{
if (component != value)
{
ClearError("Component");
if (value == null)
SetError("Component", "Required Value");
component = value;
}
}
}
}
Component 只有 id
和 Name
以及以下从数据库中获取数据并列出我的对象的函数:
GetItems 在 Item
Class
public static List<Item> GetAllItems
{
get
{
List<Item> MyItems = new List<Item>();
SqlConnection con = new SqlConnection(BaseDataBase.ConnectionString);
SqlCommand com = new SqlCommand("sp_Get_All_Item", con);
com.CommandType = System.Data.CommandType.StoredProcedure;
try
{
con.Open();
SqlDataReader rd = com.ExecuteReader();
while (rd.Read())
{
Item i = new Item();
if (!(rd["ID"] is DBNull))
i.ID = System.Int32.Parse(rd["ID"].ToString());
i.Name = rd["Name"].ToString();
i.MedicineCompositions = MedicineComposition.GetAllByItem(i);
MyItems.Add(i);
}
rd.Close();
}
catch
{
MyItems = null;
}
finally
{
con.Close();
}
return MyItems;
}
GetAllByItem 在 MedicalCompositions
public static List<MedicineComposition> GetAllByItem(Item i)
{
List<MedicineComposition> MyMedicineCompositions = new List<MedicineComposition>();
SqlConnection con = new SqlConnection(BaseDataBase.ConnectionString);
SqlCommand com = new SqlCommand("sp_Get_ByItemID_MedicineComposition", con);
com.CommandType = System.Data.CommandType.StoredProcedure;
SqlParameter pr = new SqlParameter("@ID", i.ID);
com.Parameters.Add(pr);
try
{
con.Open();
SqlDataReader rd = com.ExecuteReader();
while (rd.Read())
{
MedicineComposition m = new MedicineComposition() { };
if (!(rd["ID"] is DBNull))
m.ID = Int32.Parse(rd["ID"].ToString());
if (!(rd["ComponentID"] is DBNull))
m.Component = Component.GetByID(Int32.Parse(rd["ComponentID"].ToString()));
m.Item = i;
MyMedicineCompositions.Add(m);
}
rd.Close();
}
catch
{
MyMedicineCompositions = null;
}
finally
{
con.Close();
}
return MyMedicineCompositions;
}
它喜欢使用 mvvm
因为它可以让你处理对象而不是 datatable
,但是当我使用以前的 class 结构时我有 以下问题:
- 我在数据库中
Item
Table 中至少有 1000 条记录,所以当我调用 GetAllItems
时,我的性能很慢,尤其是当数据库不在本地计算机上时。
- 我尝试在启动画面打开时加载
Items
,这需要一些时间,但性能一般
- 每次更新
Item
table 我应该记得 GetAllItems
这么慢回来
我的问题是我在创建class时遇到的问题,这是构建[=66的最佳方式吗? =] 在 mvvm
我认为您的用户不需要一眼就看到所有 1000 个项目,甚至不需要相关的数千个组成和组件。
遇到这种情况我会:
- 过滤数据。向用户询问项目名称、类别或其他内容。
- 延迟加载。首先只加载(过滤的)项目。当用户 select 一个 Item 切换到一个 "Item details" 查看并加载相关数据(组成和组件)时。
将数据集分配给 ObservableCollection
属性 的构造函数。否则,您的视图将通过 PropertyChanged
通知更新您的 ObservableCollection 执行添加操作的每个项目。
试试这个:
var items = services.LoadItems();
myObservableCollection = new ObservableCollection<somedatatype>(items);
这种类型的赋值将通知您的视图一次,而不是您实施的当前方式,即 1000 次。
而不是 returning 列表,return IEnumerable 并根据需要生成结果。显然,当您没有阅读所有结果时,它只会提高性能,这在大多数情况下确实如此。为此,您必须删除 catch,因为您不能将 yield 和 catch 放在一起。 catch 可以绕过 con.Open 和 ExecuteReader 并且在 catch 中你可以 yield break:
public static IEnumerable<MedicineComposition> GetAllByItem(Item i)
{
SqlConnection con = new SqlConnection(BaseDataBase.ConnectionString);
SqlCommand com = new SqlCommand("sp_Get_ByItemID_MedicineComposition", con);
com.CommandType = System.Data.CommandType.StoredProcedure;
SqlParameter pr = new SqlParameter("@ID", i.ID);
com.Parameters.Add(pr);
try
{
SqlDataReader rd;
try
{
con.Open();
rd = com.ExecuteReader();
}
catch { yield break;}
while (rd.Read())
{
MedicineComposition m = new MedicineComposition() { };
if (!(rd["ID"] is DBNull))
m.ID = Int32.Parse(rd["ID"].ToString());
if (!(rd["ComponentID"] is DBNull))
m.Component = Component.GetByID(Int32.Parse(rd["ComponentID"].ToString()));
m.Item = i;
yield return m;
}
rd.Close();
}
finally
{
con.Close();
}
}
现在,如果出现异常,这不再是 returning null,而是 return 一些项目甚至是空枚举。我宁愿将 catch 移至此 getter 的调用者。
如果出于某种原因需要计算 returned 项,请调用 GetAllByItem(item).ToArray()。这将枚举所有项目一次并为您获取长度。绝对不要调用枚举两次获取长度再枚举项:
var length = GetAllByItem(item).Count();// this will get all the items from the db
foreach(var i in GetAllByItem(item)) // this will get all the items from the db again
最好这样做:
var list = GetAllByItem(item); // this will get all the items and now you have the length and the items.
显然,如果您出于某种原因需要长度,则更改为 IEnumerable 没有意义,只是为了更好的抽象。
其他改进可能是,只创建一次连接而不是每次调用 getter。只有当你知道它不会造成任何伤害时,才有可能。
您可以在此处进行一些改进,例如:
- 考虑到我们正在谈论
MedicalComposition
,拥有 nullable
个唯一标识符可能不是最好的主意
- 如果您有多个仅包含
id
和 name
的 class,您可以使用 KeyValuePair<>
或 Tuple<>
代替
- 实现基础 class,比如
ModelBase
实现 INotifyPropertyChanged
- 为数据库相关操作实施
repository pattern
,如果可能,cache/page 结果
- 如果尚未完成,请将数据 - and/or 时间密集型操作移动到单独的线程中
- 有点令人困惑的是,在
Item
上您有 MedicineComposition
的 IEnumerable,但在 MedicineComposition
中您也有 Item
?也许您根本不需要它或相关 Item.Id
就足够了?
- 您可以将一种方法添加到您的存储库中,仅用于 return 自
<timestamp>
以来 added/modified/removed 的项目,并且仅更新 Items
集合中的必要内容
- 您可以制作一些属性
Lazy<>
- 利用
TAP
(基于任务的异步模式)
下面是 "one go" 针对您的 w/o 阻塞 UI 线程的问题。它远未完成,但仍然如此。 Thread.Sleep
存储库中的数据正在模仿您的数据库查询延迟
View\MainWindow.xaml
代码隐藏仅包含 InitializeComponents
。
<Window x:Class="WpfApplication1.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:WpfApplication1.ViewModel"
Title="MainWindow"
Height="300"
Width="250">
<Window.DataContext>
<viewModel:MainViewModel />
</Window.DataContext>
<!-- Layout root -->
<Grid x:Name="ContentPanel" Margin="12,0,12,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Status label -->
<Label Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Background="Bisque"
Margin="0,3,0,3"
Content="{Binding Status}" />
<!-- Controls -->
<StackPanel Grid.Row="1">
<Label Content="Items" />
<!-- Items combo -->
<ComboBox HorizontalAlignment="Stretch"
MaxDropDownHeight="120"
VerticalAlignment="Top"
Width="Auto"
Margin="0,0,0,5"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name" />
<!-- Medicine components -->
<ItemsControl ItemsSource="{Binding SelectedItem.MedicineCompositions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<!-- Components -->
<ItemsControl ItemsSource="{Binding Components}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text=" * " />
<Run Text="{Binding Name}" />
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</Window>
ViewModel\MainViewModel
public class MainViewModel : ViewModelBase
{
private string _status;
private Item _selectedItem;
private ObservableCollection<Item> _items;
public MainViewModel()
:this(new ItemRepository(), new MedicineCompositionRepository())
{}
public MainViewModel(IRepository<Item> itemRepository, IRepository<MedicineComposition> medicineCompositionRepository)
{
ItemRepository = itemRepository;
MedicineCompositionRepository = medicineCompositionRepository;
Task.Run(() => LoadItemsData());
}
public IRepository<Item> ItemRepository { get; set; }
public IRepository<MedicineComposition> MedicineCompositionRepository { get; set; }
public Item SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged();
Task.Run(() => LoadMedicineCompositionsData(_selectedItem));
}
}
public ObservableCollection<Item> Items
{
get { return _items; }
set { _items = value; OnPropertyChanged(); }
}
public string Status
{
get { return _status; }
set { _status = value; OnPropertyChanged(); }
}
private async Task LoadItemsData()
{
Status = "Loading items...";
var result = await ItemRepository.GetAll();
Items = new ObservableCollection<Item>(result);
Status = "Idle";
}
private async Task LoadMedicineCompositionsData(Item item)
{
if (item.MedicineCompositions != null)
return;
Status = string.Format("Loading compositions for {0}...", item.Name);
var result = await MedicineCompositionRepository.GetById(item.Id);
SelectedItem.MedicineCompositions = result;
Status = "Idle";
}
}
型号
public class Component : ModelBase
{}
public class MedicineComposition : ModelBase
{
private IEnumerable<Component> _component;
public IEnumerable<Component> Components
{
get { return _component; }
set { _component = value; OnPropertyChanged(); }
}
}
public class Item : ModelBase
{
private IEnumerable<MedicineComposition> _medicineCompositions;
public IEnumerable<MedicineComposition> MedicineCompositions
{
get { return _medicineCompositions; }
set { _medicineCompositions = value; OnPropertyChanged(); }
}
}
public abstract class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _id;
private string _name;
public int Id
{
get { return _id; }
set { _id = value; OnPropertyChanged(); }
}
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
存储库
public interface IRepository<T> where T : class
{
Task<IEnumerable<T>> GetAll();
Task<IEnumerable<T>> GetById(int id);
}
public class ItemRepository : IRepository<Item>
{
private readonly IList<Item> _mockItems;
public ItemRepository()
{
_mockItems = new List<Item>();
for (int i = 0; i < 100; i++)
_mockItems.Add(new Item { Id = i, Name = string.Format("Item #{0}", i), MedicineCompositions = null });
}
public Task<IEnumerable<Item>> GetAll()
{
Thread.Sleep(1500);
return Task.FromResult((IEnumerable<Item>) _mockItems);
}
public Task<IEnumerable<Item>> GetById(int id)
{
throw new NotImplementedException();
}
}
public class MedicineCompositionRepository : IRepository<MedicineComposition>
{
private readonly Random _random;
public MedicineCompositionRepository()
{
_random = new Random();
}
public Task<IEnumerable<MedicineComposition>> GetAll()
{
throw new NotImplementedException();
}
public Task<IEnumerable<MedicineComposition>> GetById(int id)
{
// since we are mocking, id is actually ignored
var compositions = new List<MedicineComposition>();
int compositionsCount = _random.Next(1, 3);
for (int i = 0; i <= compositionsCount; i++)
{
var components = new List<Component>();
int componentsCount = _random.Next(1, 3);
for (int j = 0; j <= componentsCount; j++)
components.Add(new Component {Id = j, Name = string.Format("Component #1{0}", j)});
compositions.Add(new MedicineComposition { Id = i, Name = string.Format("MedicalComposition #{0}", i), Components = components });
}
Thread.Sleep(500);
return Task.FromResult((IEnumerable<MedicineComposition>) compositions);
}
}
我有以下 Classes:
项目
public class Item : INotifyPropertyChanged, IDataErrorInfo
{
private int? id;
public int? ID
{
get
{ return id; }
set
{ id = value; }
}
private string name;
public string Name
{
get
{ return name; }
set
{
if (value != name)
{
ClearError("Name");
if (string.IsNullOrEmpty(value) || value.Trim() == "")
SetError("Name", "Required Value");
name = value;
}
}
}
private List<MedicineComposition> medicineCompositions;
public List<MedicineComposition> MedicineCompositions
{
set { medicineCompositions = value; }
get { return medicineCompositions; }
}
}
药物成分
public class MedicineComposition : INotifyPropertyChanged, IDataErrorInfo
{
private int? id;
public int? ID
{
get
{ return id; }
set
{ id = value; }
}
private Item item;
public Item Item
{
get
{ return item; }
set
{
if (item != value)
{
ClearError("Item");
if (value == null)
SetError("Item", "Required Value");
item = value;
}
}
}
private Component component;
public Component Component
{
get
{ return component; }
set
{
if (component != value)
{
ClearError("Component");
if (value == null)
SetError("Component", "Required Value");
component = value;
}
}
}
}
Component 只有 id
和 Name
以及以下从数据库中获取数据并列出我的对象的函数:
GetItems 在 Item
Class
public static List<Item> GetAllItems
{
get
{
List<Item> MyItems = new List<Item>();
SqlConnection con = new SqlConnection(BaseDataBase.ConnectionString);
SqlCommand com = new SqlCommand("sp_Get_All_Item", con);
com.CommandType = System.Data.CommandType.StoredProcedure;
try
{
con.Open();
SqlDataReader rd = com.ExecuteReader();
while (rd.Read())
{
Item i = new Item();
if (!(rd["ID"] is DBNull))
i.ID = System.Int32.Parse(rd["ID"].ToString());
i.Name = rd["Name"].ToString();
i.MedicineCompositions = MedicineComposition.GetAllByItem(i);
MyItems.Add(i);
}
rd.Close();
}
catch
{
MyItems = null;
}
finally
{
con.Close();
}
return MyItems;
}
GetAllByItem 在 MedicalCompositions
public static List<MedicineComposition> GetAllByItem(Item i)
{
List<MedicineComposition> MyMedicineCompositions = new List<MedicineComposition>();
SqlConnection con = new SqlConnection(BaseDataBase.ConnectionString);
SqlCommand com = new SqlCommand("sp_Get_ByItemID_MedicineComposition", con);
com.CommandType = System.Data.CommandType.StoredProcedure;
SqlParameter pr = new SqlParameter("@ID", i.ID);
com.Parameters.Add(pr);
try
{
con.Open();
SqlDataReader rd = com.ExecuteReader();
while (rd.Read())
{
MedicineComposition m = new MedicineComposition() { };
if (!(rd["ID"] is DBNull))
m.ID = Int32.Parse(rd["ID"].ToString());
if (!(rd["ComponentID"] is DBNull))
m.Component = Component.GetByID(Int32.Parse(rd["ComponentID"].ToString()));
m.Item = i;
MyMedicineCompositions.Add(m);
}
rd.Close();
}
catch
{
MyMedicineCompositions = null;
}
finally
{
con.Close();
}
return MyMedicineCompositions;
}
它喜欢使用 mvvm
因为它可以让你处理对象而不是 datatable
,但是当我使用以前的 class 结构时我有 以下问题:
- 我在数据库中
Item
Table 中至少有 1000 条记录,所以当我调用GetAllItems
时,我的性能很慢,尤其是当数据库不在本地计算机上时。 - 我尝试在启动画面打开时加载
Items
,这需要一些时间,但性能一般 - 每次更新
Item
table 我应该记得GetAllItems
这么慢回来
我的问题是我在创建class时遇到的问题,这是构建[=66的最佳方式吗? =] 在mvvm
我认为您的用户不需要一眼就看到所有 1000 个项目,甚至不需要相关的数千个组成和组件。
遇到这种情况我会:
- 过滤数据。向用户询问项目名称、类别或其他内容。
- 延迟加载。首先只加载(过滤的)项目。当用户 select 一个 Item 切换到一个 "Item details" 查看并加载相关数据(组成和组件)时。
将数据集分配给 ObservableCollection
属性 的构造函数。否则,您的视图将通过 PropertyChanged
通知更新您的 ObservableCollection 执行添加操作的每个项目。
试试这个:
var items = services.LoadItems();
myObservableCollection = new ObservableCollection<somedatatype>(items);
这种类型的赋值将通知您的视图一次,而不是您实施的当前方式,即 1000 次。
而不是 returning 列表,return IEnumerable 并根据需要生成结果。显然,当您没有阅读所有结果时,它只会提高性能,这在大多数情况下确实如此。为此,您必须删除 catch,因为您不能将 yield 和 catch 放在一起。 catch 可以绕过 con.Open 和 ExecuteReader 并且在 catch 中你可以 yield break:
public static IEnumerable<MedicineComposition> GetAllByItem(Item i)
{
SqlConnection con = new SqlConnection(BaseDataBase.ConnectionString);
SqlCommand com = new SqlCommand("sp_Get_ByItemID_MedicineComposition", con);
com.CommandType = System.Data.CommandType.StoredProcedure;
SqlParameter pr = new SqlParameter("@ID", i.ID);
com.Parameters.Add(pr);
try
{
SqlDataReader rd;
try
{
con.Open();
rd = com.ExecuteReader();
}
catch { yield break;}
while (rd.Read())
{
MedicineComposition m = new MedicineComposition() { };
if (!(rd["ID"] is DBNull))
m.ID = Int32.Parse(rd["ID"].ToString());
if (!(rd["ComponentID"] is DBNull))
m.Component = Component.GetByID(Int32.Parse(rd["ComponentID"].ToString()));
m.Item = i;
yield return m;
}
rd.Close();
}
finally
{
con.Close();
}
}
现在,如果出现异常,这不再是 returning null,而是 return 一些项目甚至是空枚举。我宁愿将 catch 移至此 getter 的调用者。 如果出于某种原因需要计算 returned 项,请调用 GetAllByItem(item).ToArray()。这将枚举所有项目一次并为您获取长度。绝对不要调用枚举两次获取长度再枚举项:
var length = GetAllByItem(item).Count();// this will get all the items from the db
foreach(var i in GetAllByItem(item)) // this will get all the items from the db again
最好这样做:
var list = GetAllByItem(item); // this will get all the items and now you have the length and the items.
显然,如果您出于某种原因需要长度,则更改为 IEnumerable 没有意义,只是为了更好的抽象。
其他改进可能是,只创建一次连接而不是每次调用 getter。只有当你知道它不会造成任何伤害时,才有可能。
您可以在此处进行一些改进,例如:
- 考虑到我们正在谈论
MedicalComposition
,拥有nullable
个唯一标识符可能不是最好的主意 - 如果您有多个仅包含
id
和name
的 class,您可以使用KeyValuePair<>
或Tuple<>
代替 - 实现基础 class,比如
ModelBase
实现INotifyPropertyChanged
- 为数据库相关操作实施
repository pattern
,如果可能,cache/page 结果 - 如果尚未完成,请将数据 - and/or 时间密集型操作移动到单独的线程中
- 有点令人困惑的是,在
Item
上您有MedicineComposition
的 IEnumerable,但在MedicineComposition
中您也有Item
?也许您根本不需要它或相关Item.Id
就足够了? - 您可以将一种方法添加到您的存储库中,仅用于 return 自
<timestamp>
以来 added/modified/removed 的项目,并且仅更新Items
集合中的必要内容 - 您可以制作一些属性
Lazy<>
- 利用
TAP
(基于任务的异步模式)
下面是 "one go" 针对您的 w/o 阻塞 UI 线程的问题。它远未完成,但仍然如此。 Thread.Sleep
存储库中的数据正在模仿您的数据库查询延迟
View\MainWindow.xaml
代码隐藏仅包含 InitializeComponents
。
<Window x:Class="WpfApplication1.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:WpfApplication1.ViewModel"
Title="MainWindow"
Height="300"
Width="250">
<Window.DataContext>
<viewModel:MainViewModel />
</Window.DataContext>
<!-- Layout root -->
<Grid x:Name="ContentPanel" Margin="12,0,12,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Status label -->
<Label Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Background="Bisque"
Margin="0,3,0,3"
Content="{Binding Status}" />
<!-- Controls -->
<StackPanel Grid.Row="1">
<Label Content="Items" />
<!-- Items combo -->
<ComboBox HorizontalAlignment="Stretch"
MaxDropDownHeight="120"
VerticalAlignment="Top"
Width="Auto"
Margin="0,0,0,5"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name" />
<!-- Medicine components -->
<ItemsControl ItemsSource="{Binding SelectedItem.MedicineCompositions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<!-- Components -->
<ItemsControl ItemsSource="{Binding Components}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text=" * " />
<Run Text="{Binding Name}" />
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</Window>
ViewModel\MainViewModel
public class MainViewModel : ViewModelBase
{
private string _status;
private Item _selectedItem;
private ObservableCollection<Item> _items;
public MainViewModel()
:this(new ItemRepository(), new MedicineCompositionRepository())
{}
public MainViewModel(IRepository<Item> itemRepository, IRepository<MedicineComposition> medicineCompositionRepository)
{
ItemRepository = itemRepository;
MedicineCompositionRepository = medicineCompositionRepository;
Task.Run(() => LoadItemsData());
}
public IRepository<Item> ItemRepository { get; set; }
public IRepository<MedicineComposition> MedicineCompositionRepository { get; set; }
public Item SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged();
Task.Run(() => LoadMedicineCompositionsData(_selectedItem));
}
}
public ObservableCollection<Item> Items
{
get { return _items; }
set { _items = value; OnPropertyChanged(); }
}
public string Status
{
get { return _status; }
set { _status = value; OnPropertyChanged(); }
}
private async Task LoadItemsData()
{
Status = "Loading items...";
var result = await ItemRepository.GetAll();
Items = new ObservableCollection<Item>(result);
Status = "Idle";
}
private async Task LoadMedicineCompositionsData(Item item)
{
if (item.MedicineCompositions != null)
return;
Status = string.Format("Loading compositions for {0}...", item.Name);
var result = await MedicineCompositionRepository.GetById(item.Id);
SelectedItem.MedicineCompositions = result;
Status = "Idle";
}
}
型号
public class Component : ModelBase
{}
public class MedicineComposition : ModelBase
{
private IEnumerable<Component> _component;
public IEnumerable<Component> Components
{
get { return _component; }
set { _component = value; OnPropertyChanged(); }
}
}
public class Item : ModelBase
{
private IEnumerable<MedicineComposition> _medicineCompositions;
public IEnumerable<MedicineComposition> MedicineCompositions
{
get { return _medicineCompositions; }
set { _medicineCompositions = value; OnPropertyChanged(); }
}
}
public abstract class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _id;
private string _name;
public int Id
{
get { return _id; }
set { _id = value; OnPropertyChanged(); }
}
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
存储库
public interface IRepository<T> where T : class
{
Task<IEnumerable<T>> GetAll();
Task<IEnumerable<T>> GetById(int id);
}
public class ItemRepository : IRepository<Item>
{
private readonly IList<Item> _mockItems;
public ItemRepository()
{
_mockItems = new List<Item>();
for (int i = 0; i < 100; i++)
_mockItems.Add(new Item { Id = i, Name = string.Format("Item #{0}", i), MedicineCompositions = null });
}
public Task<IEnumerable<Item>> GetAll()
{
Thread.Sleep(1500);
return Task.FromResult((IEnumerable<Item>) _mockItems);
}
public Task<IEnumerable<Item>> GetById(int id)
{
throw new NotImplementedException();
}
}
public class MedicineCompositionRepository : IRepository<MedicineComposition>
{
private readonly Random _random;
public MedicineCompositionRepository()
{
_random = new Random();
}
public Task<IEnumerable<MedicineComposition>> GetAll()
{
throw new NotImplementedException();
}
public Task<IEnumerable<MedicineComposition>> GetById(int id)
{
// since we are mocking, id is actually ignored
var compositions = new List<MedicineComposition>();
int compositionsCount = _random.Next(1, 3);
for (int i = 0; i <= compositionsCount; i++)
{
var components = new List<Component>();
int componentsCount = _random.Next(1, 3);
for (int j = 0; j <= componentsCount; j++)
components.Add(new Component {Id = j, Name = string.Format("Component #1{0}", j)});
compositions.Add(new MedicineComposition { Id = i, Name = string.Format("MedicalComposition #{0}", i), Components = components });
}
Thread.Sleep(500);
return Task.FromResult((IEnumerable<MedicineComposition>) compositions);
}
}