2015-02-01 4 views
3

나는 다음과 같은 클래스가 :
항목만들기 클래스 구조는 문제

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

idName
하고있다 구성 요소
을 팔로우 인 데이터베이스에서 데이터를 가져오고 내 개체의 목록을 만들 g 기능 : 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; 
} 

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

GetAllByItemGetItems를 그것이 당신을 할 수 있기 때문에 mvvm를 사용하는 등의 datatable 대신 개체를 다루지 만, 이전 구조 도형을 사용할 때 다음 문제가 발생합니다.

  • 은 내가 GetAllItems를 호출 할 때 그래서 난 성능 저하가 데이터베이스에 Item 표에서 적어도 1,000 레코드가 특히하지 않을 경우 로컬 컴퓨터의 데이터베이스.
  • 는 내가 가지고있는 문제이고 그래서가 다시
    내 질문 둔화에 시작 화면, 그것은 시간을 소요 할 때 Items를로드하지만 난 GetAllItems을 기억해야 Item 테이블에 각 업데이트에 매체 성능
  • 을 시도 클래스, 그리고 생성이 나는 사용자가 조성되지도 많은 천, 한 눈에 모든 1000 개 항목을 볼 필요가 있다고 생각하지 말아 mvvm
+1

db에 많은 양의 데이터가있는 경우 모두 가져 오는 것이 항상 느려지므로 그 방법이 있습니다. 여기에있는 질문은 모든 데이터를 즉시 필요로 하는가? 필요할 때 데이터를로드 할 수 있습니다. 청크로 데이터를로드 할 수 있으므로 UI를 차단하지 않거나 성능을 너무 많이 올릴 수 없습니다. 당신은 당신이 가장 필요로하는 것을 여기에서 계산할 필요가 있고 당신이 더 프로그램하는 것에 따라 계산해야합니다. –

+0

답변 코드가 업데이트되었습니다. –

답변

2

몇 가지 :

  • 우리가 MedicalComposition는이 아이디어의 최고를하지 않을 수 있습니다 말을 감안할 때 nullable 고유 식별자
  • 만 구성된 여러 클래스가있는 경우 idname 당신이 KeyValuePair<> 또는 Tuple<> 대신
  • 기본 클래스를 구현 사용할 수, INotifyPropertyChanged
  • 을 구현하는 ModelBase 말 가능하면 데이터베이스 관련 작업, 캐시/페이지 결과를
  • 구현 repository pattern
  • 아직하지 않은 경우, 별도의 스레드 (들)
  • 그것은 Item에 당신이 가진 것을 조금 혼란에 데이터 - 및/또는 시간 집약적 인 작업을 이동 IEnumerable MedicineComposition s뿐만 아니라 MedicineComposition에는 Item도 있습니다. 어쩌면 당신은 전혀 그것을 필요하지 않거나 관련 Item.Id는 이젠 그만 일 것입니다?
  • 당신은 Lazy<>
  • 활용
  • 당신은 속성의 일부를 만들 수있는 유일한 <timestamp> 때문에 제거/추가, 수정 된 항목을/반환에 저장소에 메서드를 추가하고 만 Items 수집에 필요한 것을 업데이트 할 수 TAP (작업 기반 비동기 패턴) 아래

는 w/UI 스레드를 차단 O를 문제에 대해 "하나의 이동"입니다. 그것은 아직 완벽하지는 않지만 여전히입니다.저장소에서 Thread.Sleep의이 데이터베이스 쿼리를 모방하는 것은

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> 

뷰 모델 \ 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); 
    } 
} 
4

에 클래스를 구성하는 가장 좋은 방법이며, 구성 요소 관련.

  1. 필터 데이터 : 나는 것 같은

    나는 상황. 사용자에게 항목 이름, 카테고리 또는 기타 항목을 요청하십시오.

  2. 지연로드. 먼저 (필터링 된) 항목 만로드하십시오. 사용자가 항목을 선택하여 "항목 세부 사항"보기로 전환하고 관련 데이터 (구성 및 구성 요소)를로드합니다.
1

데이터 집합을 ObservableCollection 속성의 생성자에 할당하십시오. 그렇지 않으면 ObservableCollection에서 추가 작업을 수행하는 각 항목에 대한 PropertyChanged 알림을 통해보기가 업데이트됩니다.

이 시도 :

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

할당의이 유형은 한 번 대신 1000 배이다 구현이하는 현재 방식의보기를 통지합니다.

1

List를 반환하는 대신 IEnumerable을 반환하고 필요에 따라 결과를 산출하십시오. 분명히 모든 결과를 읽지 않을 때만 성능을 향상시킬 수 있습니다. 대부분의 경우 사실입니다. 그렇게하기 위해서는 양보를 제거해야합니다. 양보와 함께 잡을 수 없기 때문입니다. 캐치는 con.Open과가 ExecuteReader 주위에 갈 수 및 캐치에 당신은 휴식을 얻을 수이 더 이상 널 (null)을 반환 예외의 경우 지금

 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로 변경합니다.

다른 개선 사항은 getter를 호출 할 때마다 매번 연결되는 대신 연결을 한 번만 만들 수 있습니다. 당신이 그것이 해를 끼치 지 않을 것이라는 것을 안다면 그것은 가능합니다. 예를 들어 당신이 여기 개선 할 수