【问题标题】:creating class structure in mvvm issues在 mvvm 问题中创建类结构
【发布时间】:2015-03-31 08:59:36
【问题描述】:

我有以下类:
Item

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

组件只有idName
以及以下从数据库中获取数据并列出我的对象列表的函数: GetItemsItem 类中

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

GetAllByItemMedicalCompositions

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,但是当我使用以前的类结构形状时,我遇到了以下问题:

  • 我在Item 数据库中的表中至少有 1000 条记录,所以当我调用GetAllItems 时,我的性能很慢,尤其是当数据库不在本地计算机上时。
  • 我尝试在启动画面打开时加载Items,这需要一些时间,但性能中等
  • Item 表的每次更新中,我都应该记得 GetAllItems 这么慢
    我的问题是我在创建 class 时遇到的问题,这是在mvvm 中构建课程的最佳方式吗?

【问题讨论】:

  • 如果数据库中有很多数据,那么获取它们总是很慢,就是这样。这里的问题是,您真的需要立即获取所有数据吗?您可以在需要时按需加载数据。您可以分块加载数据,因此您不会阻塞 ui,或对性能造成太大影响。你需要在这里计算出你最需要的东西,并据此进一步编程。
  • 用代码更新了答案。

标签: c# wpf class oop mvvm


【解决方案1】:

我认为您的用户不需要一目了然地查看所有 1000 个项目,甚至不需要查看数千个相关的组成和组件。

我会遇到这样的情况:

  1. 过滤数据。向用户询问项目名称、类别或其他内容。
  2. 延迟加载。起初只加载(过滤的)项目。当用户选择一个Item时切换到一个“Item details”视图并加载相关数据(成分和组件)。

【讨论】:

    【解决方案2】:

    您可以在这里改进一些事情,例如:

    • 鉴于我们谈论的是 MedicalComposition,拥有 nullable 唯一标识符可能不是最好的主意
    • 如果您有多个仅由 idname 组成的类,则可以改用 KeyValuePair&lt;&gt;Tuple&lt;&gt;
    • 实现一个基类,例如实现INotifyPropertyChangedModelBase
    • 对数据库相关操作、缓存/页面结果(如果可能)实施 repository pattern
    • 如果尚未完成,请将数据和/或时间密集型操作移到单独的线程中
    • Item 上有MedicineCompositions 的IEnumerable 有点令人困惑,但在MedicineComposition 中你也有Item?也许您根本不需要它或相关的Item.Id 就足够了?
    • 您可以向您的存储库添加一个方法,以仅返回自 &lt;timestamp&gt; 以来已添加/修改/删除的项目,并且仅更新您的 Items 集合中必要的内容
    • 您可以创建一些属性Lazy&lt;&gt;
    • 利用TAP(基于任务的异步模式)

    以下是针对您的问题的“一次性”解决方案,不会阻塞 UI 线程。它远未完成,但仍然如此。存储库中的Thread.Sleeps 正在模仿您的数据库查询延迟

    查看\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);
        }
    }
    

    【讨论】:

      【解决方案3】:

      返回 IEnumerable 并在需要时产生结果,而不是返回 List。显然,当您没有读取所有结果时,它只会提高性能,这在大多数情况下实际上是正确的。为此,您必须删除 catch,因为您不能同时拥有 yield 和 catch。捕获可以绕过 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();
              }
          } 
      

      现在如果出现异常,这不再返回 null,而是可以返回少量项目甚至是空枚举。我宁愿将捕获物移至此 getter 的调用者。 如果您出于某种原因需要返回项目的计数,请调用 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。这是可能的,前提是您知道它不会造成任何伤害。

      【讨论】:

        【解决方案4】:

        将数据集分配到 ObservableCollection 属性的构造函数中。否则,您的视图将通过 PropertyChanged 通知更新您的 ObservableCollection 执行添加操作的每个项目。

        试试这个:

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

        这种类型的分配将通知您的视图一次,而不是您当前的实现方式,即 1000 次。

        【讨论】:

          猜你喜欢
          • 2021-08-18
          • 1970-01-01
          • 1970-01-01
          • 2020-12-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多