2015-02-01 3 views
3

У меня есть следующие классы:
ПунктСоздание класса в MVVM вопросы

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; } 
    } 
} 

MedicineComposition

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; 
      } 
     } 
    } 
} 

Компонент, который имеет только id и Name
и тому followin г функции, которые приносят данные из базы данных и сделать список моих объектов: GetItems в Item класса

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, но когда я использую предыдущую форму структуры класса, у меня есть следующие проблемы:

  • я, по крайней мере, 1000 записей в Item таблицы в базе данных так, когда я называю GetAllItems я иметь низкую производительность, особенно когда база данных в не на локальном компьютере.
  • я пытался загрузить Items когда экран выплеска, это занимает время, но взять среднюю производительность
  • на каждом обновлении на Item столе я должен вспомнить GetAllItems так медленно назад
    мои вопросы, где есть проблема, что у меня есть в создании класса, и это лучший способ структурировать класс в mvvm
+1

Если в db есть много данных, их получение всегда будет медленным, вот как оно. вопрос здесь в том, действительно ли вам нужны все данные немедленно? вы можете загружать данные по запросу, когда вам это нужно. вы можете загружать данные в куски, поэтому вы не будете блокировать ui или слишком сильно пострадали от производительности. вам нужно рассчитать, что вам нужно больше всего и в соответствии с тем, что вы программируете дальше. –

+0

Обновлен ответ с кодами. –

ответ

2

Несколько вещей, которые вы могли бы улучшить здесь, например:

  • Учитывая мы говорим о MedicalComposition это не может быть лучше идеи, чтобы иметь nullable уникальный идентификатор
  • Если у вас есть несколько классов, состоящих только из id и name вы могли бы использовать KeyValuePair<> или Tuple<> вместо
  • Реализация базового класса, скажем ModelBase, который реализует INotifyPropertyChanged
  • Реализовать repository pattern для действий, кэш результатов базы данных, связанных с/страниц, если возможно
  • Если еще не сделано, двигаться данных- и/или время интенсивных операций в отдельном потоке (ов)
  • Это немного сбивает с толку, что Item у вас есть IEnumerable от MedicineComposition s, но также, в MedicineComposition у вас есть Item, тоже? Может быть, вам это совсем не нужно или связать Item.Id будет достаточно?
  • Вы можете добавить метод в репозиторий, чтобы возвращать только элементы, которые были добавлены/изменены/удалены, так как <timestamp> и обновлять только то, что необходимо в вашей Items коллекции
  • Вы могли бы сделать некоторые из свойств Lazy<>
  • Использование TAP (на основе задач Asynchronous Pattern)

Ниже «один идти» для вашей проблемы без блокировки потока пользовательского интерфейса. Это далеко не полный, но все же.Thread.Sleep s в репозитории имитируя запрос базы данных задерживает

imgur

Вид \ MainWindow.xaml

Codebehind содержит только 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)); 
    } 
} 

Repository

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); 
    } 
} 
4

Я не думаю, что ваш пользователь должен видеть все 1000 пунктов на первый взгляд, даже многие тысячи состава и связанные компоненты.

I ситуации, как это, я бы:

  1. Фильтр данных. Задайте пользователю имя, категорию или что-то еще.
  2. Задержка нагрузки. Сначала загружайте только элементы (фильтрованные). Когда пользователь выбирает переключатель Item в «Item details» View и загружает связанные данные (состав и компоненты).
1

Назначьте набор данных в конструктор вашего ObservableCollection. Кроме того, ваше представление будет обновляться через уведомление PropertyChanged для каждого элемента, который ваш ObservableCollection выполняет операцию добавления.

Попробуйте это:

var items = services.LoadItems(); 
myObservableCollection = new ObservableCollection<somedatatype>(items); 

Этот тип присвоения уведомит Ваш взгляд один раз вместо текущего пути ваша реализация делает это в 1000 раз.

1

Вместо того, чтобы возвращать список, возвращайте IEnumerable и приведите результаты по мере необходимости. Очевидно, что это только улучшит производительность, когда вы не прочтете все результаты, что на самом деле верно в большинстве случаев. Для этого вам придется удалить улов, потому что вы не можете уступить и поймать друг друга. Выгода может ходить con.Open и ExecuteReader и в улове вы можете дать перерыв:

 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(); 
     } 
    } 

Теперь в случае исключения этого больше не возвращается нуль, но может вернуть несколько предметов или даже пустое перечисление. Я предпочел бы переместить улов в вызывающую сторону этого геттера. Если вам нужно по какой-либо причине подсчитать возвращенные элементы, вызовите 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, только для лучшей абстракции.

Другие улучшения могут быть, чтобы создать соединение только один раз, а не каждый раз при вызове получателя. Это возможно только, если вы знаете, что это не причинит никакого вреда.

Смежные вопросы