在mvvm问题中创建类结构

时间:2015-02-01 07:10:43

标签: c# wpf class oop 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;
            }
        }
    }
}
仅有idName

组件 以及从数据库中获取数据并生成我的对象列表的以下函数: Item

中的 GetItems
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;
}
MedicalCompositions

中的

GetAllByItem

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这么慢 我的问题是我在创建时遇到的问题,这是在mvvm
  • 中构建类的最佳方法

4 个答案:

答案 0 :(得分:4)

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

我这样的情况我会:

  1. 过滤数据。询问用户项目名称,类别或其他内容。
  2. 延迟加载。首先只加载(过滤的)项目。当用户选择项目切换到&#34;项目详细信息&#34;查看并加载相关数据(组合和组件)。

答案 1 :(得分:2)

您可以在此处改进一些事项,例如:

  • 鉴于我们在谈论MedicalComposition,拥有nullable唯一标识符
  • 可能不是最好的想法
  • 如果您有多个仅包含idname的课程,则可以使用KeyValuePair<>Tuple<>代替
  • 实现一个基类,比如实现ModelBase
  • INotifyPropertyChanged
  • 为数据库相关操作实施repository pattern,如果可能,实施缓存/页面结果
  • 如果尚未完成,请将数据和/或时间密集型操作移至单独的线程
  • 有一点令人困惑的是,Item你有MedicineComposition的IEnumerable,但MedicineComposition你也有Item吗?也许你根本不需要它或相关Item.Id就足够了?
  • 您可以向存储库添加一个方法,仅返回自<timestamp>以来已添加/修改/删除的项目,并仅更新Items集合中所需的内容
  • 您可以制作一些属性Lazy<>
  • 利用TAP(基于任务的异步模式)

以下是您的问题的“一气呵成”,无法阻止UI线程。它远非完整但仍然存在。存储库中的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);
    }
}

答案 2 :(得分:1)

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

试试这个:

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

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

答案 3 :(得分:1)

不是返回List,而是返回IEnumerable并在需要时生成结果。当你没有阅读所有结果时,显然它只会提高性能,这在大多数情况下都是如此。要做到这一点,你必须删除捕获,因为你不能有收益和集合。捕获可以绕过con.Open和ExecuteReader,在catch中你可以产生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();
        }
    } 

现在,如果发生异常,则不再返回null,但可以返回少量项甚至空枚举。我宁愿把捕获物移动到这个吸气剂的来电者身上。 如果由于某种原因需要返回项目的计数,请调用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。只有这样才有可能,如果你知道它不会造成任何伤害。