65.9K
CodeProject 正在变化。 阅读更多。
Home

C# WPF 依赖注入数据演示

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (19投票s)

2014年3月21日

CPOL

9分钟阅读

viewsIcon

58910

downloadIcon

1479

带延迟加载和数据模拟的依赖注入数据演示。

引言

写这篇文章的原因是我阅读了许多关于依赖注入 (DI) 和控制反转 (IOC) 的文章,觉得在实际实现方面缺乏实际的例子。因此,我决定创建一个项目,使用依赖注入 (DI) 在两个数据源之间切换,一个来自数据库,一个来自模拟数据源。我认为我的想法能够为寻找 DI 实际用法的开发者提供一个简单的示例,这或许在某种程度上看起来像一个真实的场景。

该项目使用 Visual Studio 2013 创建,并在 Visual Studio 2012 中进行过测试,可以运行和编译,但会有一个来自 Entity Framework 6 的错误消息 168。据称这是一个已知 bug。

另外,你还会收到一条关于 Team Foundation Server 的消息,请忽略它。我已经删除了 .vssscc 文件,但它仍然存在。

注意: 关于如何从包含的数据库项目中创建数据库的说明,请参阅文章末尾附近的 - 创建数据库的说明 部分。

项目描述

该项目包含四个类库、一个 WPF 宿主应用程序和一个用于创建“实时数据”模式所用数据库的数据库项目。

项目结构如下所示

Solution

首先,我将介绍 Dependency.Injection.Database 项目。此项目在发布时会创建数据库,并在你的本地 SQL Server 数据库实例中填充数据。使用的数据库是微软在 20 世纪 90 年代提供的 Northwind 数据库,作为测试数据库。

注意: 我只用 SQL Server 2012 DBMS 测试过数据库项目,尽管我很确定它在较早版本中使用也应该没问题。

接下来,我将介绍 Dependency.Injection.DataModel 类库。这个类库包含一个 Entity Framework 6 实体数据模型以及相关的类。实体数据模型是从我本地 SQL Server 2012 DBMS 上安装的 Northwind 数据库创建的。

接下来,我将介绍 Dependency.Injection.DataAccess 类库。这里是通过访问 Dependency.Injection.DataModel 类库中创建的 NorthWindEntities 类来执行数据库操作。

接下来,我将介绍 Dependency.Injection.DataMocking 类库。这个类库用于创建模拟数据,具体方法将在“使用代码”部分进行描述。

接下来,我将介绍 Dependency.Injection.DataClient.。这里会使用 Dependency.Injection.DataAccess 类。此外,还有一个类用于访问上面描述的 Dependency.Injection.DataMocking 类库。

最后,我将介绍 Dependency.Injection.WPFHost。这是该项目的 WPF 宿主应用程序。

Using the Code

DataModel 类库开始,该项目中的所有代码都是 Visual Studio 在创建实体数据模型时自动生成的。这包括了所有用于模仿数据库结构的类。edmx 数据结构如下所示

DB Structure

上图准确地表示了数据库。创建的类之一如下所示

    public partial class Order
    {
        public Order()
        {
            this.Order_Details = new HashSet<Order_Detail>();
        }

        public int OrderID { get; set; }
        public string CustomerID { get; set; }
        public Nullable<int> EmployeeID { get; set; }
        public Nullable<System.DateTime> OrderDate { get; set; }
        public Nullable<System.DateTime> RequiredDate { get; set; }
        public Nullable<System.DateTime> ShippedDate { get; set; }
        public Nullable<int> ShipVia { get; set; }
        public Nullable<decimal> Freight { get; set; }
        public string ShipName { get; set; }
        public string ShipAddress { get; set; }
        public string ShipCity { get; set; }
        public string ShipRegion { get; set; }
        public string ShipPostalCode { get; set; }
        public string ShipCountry { get; set; }

        public virtual Customer Customer { get; set; }
        public virtual Employee Employee { get; set; }
        public virtual ICollection<Order_Detail> Order_Details { get; set; }
        public virtual Shipper Shipper { get; set; }
    } 

这些类提供了合适的类型和集合来访问 Northwind 数据库。

DataAccess 项目仅包含一个类,如下所示

    public class NorthwindDataAccess
    {
        NorthwindEntities context = null;

        public NorthwindDataAccess()
        {
            context = new NorthwindEntities();
        }

        public List<Category> GetCategories()
        {
            try
            {
                List<Category> categories = context.Categories.ToList();
                return categories;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        public List<Customer> GetCustomers()
        {
            try
            {
                List<Customer> customers = context.Customers.ToList();
                return customers;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        public List<CustomerDemographic> GetCustomerDemographics()
        {
            try
            {
                List<CustomerDemographic> customerDemographics = context.CustomerDemographics.ToList();
                return customerDemographics;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        public List<Employee> GetEmployees()
        {
            try
            {
                List<Employee> employees = context.Employees.ToList();
                return employees;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        public List<Order> GetOrders()
        {
            try
            {
                List<Order> orders = context.Orders.ToList();
                return orders;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        public List<Order_Detail> GetOrderDetails()
        {
            try
            {
                List<Order_Detail> orderDetails = context.Order_Details.ToList();
                return orderDetails;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        public List<Product> GetProducts()
        {
            try
            {
                List<Product> products = context.Products.ToList();
                return products;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        public List<Region> GetRegions()
        {
            try
            {
                List<Region> regions = context.Regions.ToList();
                return regions;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        public List<Shipper> GetShippers()
        {
            try
            {
                List<Shipper> shippers = context.Shippers.ToList();
                return shippers;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        public List<Supplier> GetSuppliers()
        {
            try
            {
                List<Supplier> suppliers = context.Suppliers.ToList();
                return suppliers;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        public List<Territory> GetTerritories()
        {
            try
            {
                List<Territory> territories = context.Territories.ToList();
                return territories;
            }
            catch (Exception ex)
            {
                return null;
            }
        }
    }

NorthwindDataAccess 类使用 NorthwindEntities 类的实例来访问数据库。数据库中的每个表都转换为泛型列表并返回。

注意: 返回整个数据列表的原因可能看起来很奇怪,但由于使用了延迟加载,数据直到需要时才从数据库中获取。

DataClient 类库提供了两种类型的数据:来自数据库的“实时数据”和模拟数据。

提供“实时数据”的类如下所示

    public class DatabaseDirectClient : IData
    {
        NorthwindDataAccess dBAccess = null;
        Northwind data = null;

        public DatabaseDirectClient(Boolean getAllData)
        {
            dBAccess = new NorthwindDataAccess();
            if (getAllData) Data = GetNorthwindData();
        }

        public Northwind Data
        {
            get
            {
                if (data == null)
                    data = GetNorthwindData();

                return data;
            }
            set
            {
                data = value;
            }
        }

        private Northwind GetNorthwindData()
        {
            try
            {
                Northwind northwindData = new Northwind();

                northwindData.CustomerData = new Lazy<List<Customer>>(() => dBAccess.GetCustomers());
                northwindData.CategoryData = new Lazy<List<Category>>(() => dBAccess.GetCategories());
                northwindData.CustomerDemographicData = new Lazy<List<CustomerDemographic>>(() => dBAccess.GetCustomerDemographics());
                northwindData.EmployeeData = new Lazy<List<Employee>>(() => dBAccess.GetEmployees());
                northwindData.OrderData = new Lazy<List<Order>>(() => dBAccess.GetOrders());
                northwindData.OrderDetailData = new Lazy<List<Order_Detail>>(() => dBAccess.GetOrderDetails());
                northwindData.ProductData = new Lazy<List<Product>>(() => dBAccess.GetProducts());
                northwindData.RegionData = new Lazy<List<Region>>(() => dBAccess.GetRegions());
                northwindData.ShipperData = new Lazy<List<Shipper>>(() => dBAccess.GetShippers());
                northwindData.SupplierData = new Lazy<List<Supplier>>(() => dBAccess.GetSuppliers());
                northwindData.TerritoryData = new Lazy<List<Territory>>(() => dBAccess.GetTerritories());

                return northwindData;
            }
            catch (Exception ex)
            {
                return null;
            }
        }
    }

该类继承自 IData 接口,该接口用于解析宿主应用程序中 IOC 容器的类型。

可以看到,使用 NorthwindDataAccess 类的实例通过延迟加载方法从 Northwind 数据库获取所有数据。

提供模拟数据的类如下所示

    public class MockedDataClient : IData
    {
        Northwind data = null;
        private Int32 numberOfRecords = 0;


        public MockedDataClient(Int32 numberOfRecords)
        {
            this.numberOfRecords = numberOfRecords;
        }

        public Northwind Data
        {
            get
            {
                if (data == null)
                    data = GetNorthwindData();

                return data;
            }
            set
            {
                data = value;
            }
        }

        private Northwind GetNorthwindData()
        {
            Northwind northwindData = new Northwind();

            northwindData.CategoryData = new Lazy<List<Category>> (() => MockData.GetCategories(numberOfRecords));
            northwindData.CustomerData = new Lazy<List<Customer>> (() => MockData.GetCustomers(numberOfRecords));
            northwindData.CustomerDemographicData = new Lazy<List<CustomerDemographic>>(() => MockData.GetCustomerDemographics(numberOfRecords));
            northwindData.EmployeeData = new Lazy<List<Employee>>(() => MockData.GetEmployees(numberOfRecords));
            northwindData.OrderData = new Lazy<List<Order>> (() => MockData.GetOrders(numberOfRecords));
            northwindData.OrderDetailData = new Lazy<List<Order_Detail>> (() => MockData.GetOrderDetails(numberOfRecords));
            northwindData.ProductData = new Lazy<List<Product>> (() => MockData.GetProducts(numberOfRecords));
            northwindData.RegionData = new Lazy<List<Region>> (() => MockData.GetRegions(numberOfRecords));
            northwindData.ShipperData = new Lazy<List<Shipper>> (() => MockData.GetShippers(numberOfRecords));
            northwindData.SupplierData = new Lazy<List<Supplier>> (() => MockData.GetSuppliers(numberOfRecords));
            northwindData.TerritoryData = new Lazy<List<Territory>> (() => MockData.GetTerritories(numberOfRecords));

            return northwindData;
        }
    }

可以看到,类结构是相同的,但数据来自稍后将描述的 MockData 类中的 static 方法。

DataClient 类库中还有另外两个类,Northwind 类如下所示

    public class Northwind
    {
        public Lazy<List<Category>> CategoryData { get; set; }
        public Lazy<List<Customer>> CustomerData { get; set; }
        public Lazy<List<CustomerDemographic>> CustomerDemographicData { get; set; }
        public Lazy<List<Employee>> EmployeeData { get; set; }
        public Lazy<List<Order>> OrderData { get; set; }
        public Lazy<List<Order_Detail>> OrderDetailData { get; set; }
        public Lazy<List<Product>> ProductData { get; set; }
        public Lazy<List<Region>> RegionData { get; set; }
        public Lazy<List<Shipper>> ShipperData { get; set; }
        public Lazy<List<Supplier>> SupplierData { get; set; }
        public Lazy<List<Territory>> TerritoryData { get; set; }
    }

这被用作存储延迟属性数据的类。这个类库中的最后一个类是 OrdersData 类,如下所示

    public class OrdersData
    {
        public DateTime? OrderDate { get; set; }
        public String ProductName { get; set; }
        public Int32? Quantity { get; set; }
        public Decimal Price { get; set; }
        public Decimal TotalCost { get; set; }
    }

这个类在宿主应用程序中用于在 DataGrid 中显示从数据库派生出来的数据。

DataMocking 类库只有一个类,如下所示

    public class MockData
    {
        public static List<Category> GetCategories(int size)
        {
            List<Category> categories = Builder<Category>.CreateListOfSize(size).Build().ToList();
            return categories;
        }

        public static List<Customer> GetCustomers(int size)
        {
            List<Customer> customers = Builder<Customer>.CreateListOfSize(size).Build().ToList();
            return customers;
        }

        public static List<CustomerDemographic> GetCustomerDemographics(int size)
        {
            List<CustomerDemographic> customerDemographics = Builder<CustomerDemographic>.CreateListOfSize(size).Build().ToList();
            return customerDemographics;
        }

        public static List<Employee> GetEmployees(int size)
        {
            List<Employee> employess = Builder<Employee>.CreateListOfSize(size).Build().ToList();
            return employess;
        }

        public static List<Order> GetOrders(int size)
        {
            List<Order> orders = Builder<Order>.CreateListOfSize(size).Build().ToList();
            return orders;
        }

        public static List<Order_Detail> GetOrderDetails(int size)
        {
            List<Order_Detail> orderDetails = Builder<Order_Detail>.CreateListOfSize(size).Build().ToList();
            return orderDetails;
        }

        public static List<Product> GetProducts(int size)
        {
            List<Product> products = Builder<Product>.CreateListOfSize(size).Build().ToList();
            return products;
        }

        public static List<Region> GetRegions(int size)
        {
            List<Region> regions = Builder<Region>.CreateListOfSize(size).Build().ToList();
            return regions;
        }

        public static List<Shipper> GetShippers(int size)
        {
            List<Shipper> shippers = Builder<Shipper>.CreateListOfSize(size).Build().ToList();
            return shippers;
        }

        public static List<Supplier> GetSuppliers(int size)
        {
            List<Supplier> suppliers = Builder<Supplier>.CreateListOfSize(size).Build().ToList();
            return suppliers;
        }

        public static List<Territory> GetTerritories(int size)
        {
            List<Territory> territories = Builder<Territory>.CreateListOfSize(size).Build().ToList();
            return territories;
        }
    }

可以看到,这个类使用了 NuGet 包管理器中的 NBuilder 扩展来生成模拟数据。

WPF 宿主应用程序

宿主应用程序的结构如下所示

Host

使用的编程模式是 MVVM 模式,这是通过使用 View Model 和 Command 类实现的。

View Model 由两个类组成,它们之间是继承关系。ViewModelBase 类如下所示

    class ViewModelBase : INotifyPropertyChanged
    {
        protected Northwind NorthwindData = null;
        protected IUnityContainer container;

        public ViewModelBase()
        {
            container = new UnityContainer();
            ContainerBootStrapper.RegisterTypes(container);

            NorthwindData = container.Resolve<IData>("MockedData").Data;
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Private Methods

        public void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    } 

此类包含更新视图 (MainWindow.xaml) 所需的 INotifyPropertyChanged 接口成员。可以看到,NorthwindData 成员通过解析 IOC 容器上名为的模拟数据注册来填充。

此外,构造函数中还调用了 ContainerBootStrapper.RegisterTypes,如下所示

    class ContainerBootStrapper
    {
        public static void RegisterTypes(IUnityContainer container)
        {
            container.RegisterType<IData, MockedDataClient>("MockedData",
                                            new TransientLifetimeManager(),
                                            new InjectionConstructor(10));

            container.RegisterType<IData, DatabaseDirectClient>("DbData",
                                            new TransientLifetimeManager(),
                                            new InjectionConstructor(false));
        }
    }

使用的容器类型是 Microsoft.Practices.Unity 容器。有两个命名配置:'DbData' 和 'MockedData'。每个配置都注册一个不同的继承自 IData 接口的类,如前所述。这两个注册都使用 TransientLifetimeManager,它在每次解析时都会提供一个不同的类实例。最后,注入了相应的 InjectionConstructor 参数。

ViewModelBase 中,NorthwindData 类在此处被实例化,通过解析命名配置 'MockedData' 的 IData 接口。这确保了在启动时选择了最可靠的数据源。

MainViewModel 类继承自 ViewModelBase 类,如下所示

    class MainViewModel : ViewModelBase
    {
        #region Construction

        public MainViewModel()
        {
            TabSelectionChangedCommand = new Command(GetTabSelectionChangedCommandExecute, GetTabSelectionChangedCommandCanExecute);
            ComboDatasourceChangedCommand = new Command(GetComboDatasourceChangedCommandExecute, GetComboDatasourceChangedCommandCanExecute);
            ComboCustomerChangedCommand = new Command(GetComboCustomerChangedCommandExecute, GetComboCustomerChangedCommandCanExecute);

            CustomerData = NorthwindData.CustomerData.Value.ToObservableCollection();
            EmployeeData = NorthwindData.EmployeeData.Value.ToObservableCollection();

            if (!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
            {
                Customers = DataOperations.GetCustomers(NorthwindData);
                CustomerOrdersData = DataOperations.GetCustomerOrders(NorthwindData, NorthwindData.CustomerData.Value.Select(x => x.CustomerID).FirstOrDefault()).ToObservableCollection();
            }
        }

        #endregion

        #region Commands

        public Command TabSelectionChangedCommand { get; set; }
        public Command ComboDatasourceChangedCommand { get; set; }
        public Command ComboCustomerChangedCommand { get; set; }

        private Boolean GetComboCustomerChangedCommandCanExecute(Object parameter)
        {
            return true;
        }

        private void GetComboCustomerChangedCommandExecute(Object parameter)
        {
            if (parameter == null) return;

            Tuple<String, String> customerData = (Tuple<String, String>)parameter;

            String customerID = customerData.Item1;

            InvokeMethodThreaded(new Action(() =>
            {
                CustomerOrdersData = DataOperations.GetCustomerOrders(NorthwindData, customerID);
            }));
        }

        private Boolean GetComboDatasourceChangedCommandCanExecute(Object parameter)
        {
            return true;
        }

        private void GetComboDatasourceChangedCommandExecute(Object parameter)
        {
            ComboBoxItem comboBox = (ComboBoxItem)parameter;

            String selection = comboBox.Content.ToString();

            if (selection == "Mocked Data")
                NorthwindData = container.Resolve<IData>("MockedData").Data;

            if (selection == "Live Data")
            {
                InvokeMethodThreaded(new Action(() =>
                {
                    NorthwindData = container.Resolve<IData>("DbData").Data;
                }));
            }

            TabSelectedIndex = -1;
        }

        private Boolean GetTabSelectionChangedCommandCanExecute(Object parameter)
        {
            return true;
        }

        private void GetTabSelectionChangedCommandExecute(Object parameter)
        {
            if (parameter == null) return;

            TabItem tabItem = (TabItem)parameter;

            if (tabItem.Header.ToString() == "Customers")
            {
                InvokeMethodThreaded(new Action(() =>
                {
                    CustomerData = NorthwindData.CustomerData.Value.ToObservableCollection();
                }));
            }
            if (tabItem.Header.ToString() == "Customer Orders")
            {
                InvokeMethodThreaded(new Action(() =>
                {
                    Customers = DataOperations.GetCustomers(NorthwindData).ToObservableCollection();
                    CustomerOrdersData = new ObservableCollection<OrdersData>();
                }));
            }
            if (tabItem.Header.ToString() == "Employees")
            {
                InvokeMethodThreaded(new Action(() =>
                {
                    EmployeeData = NorthwindData.EmployeeData.Value.ToObservableCollection();
                }));
            }
        }

        #endregion

        #region Properties

        private Int32 _tabSelectedIndex = 0;
        public Int32 TabSelectedIndex
        {
            set
            {
                _tabSelectedIndex = value;
                RaisePropertyChanged("TabSelectedIndex");
            }
        }

        private ObservableCollection<OrdersData> _customerOrdersData = new ObservableCollection<OrdersData>();
        public ObservableCollection<OrdersData> CustomerOrdersData
        {
            get
            {
                return _customerOrdersData;
            }
            set
            {
                _customerOrdersData = value;
                RaisePropertyChanged("CustomerOrdersData");
            }
        }


        private ObservableCollection<Tuple<string, string>> _customers = new ObservableCollection<Tuple<string, string>>();
        public ObservableCollection<Tuple<string, string>> Customers
        {
            get
            {
                return _customers;
            }
            set
            {
                _customers = value;
                RaisePropertyChanged("Customers");
            }
        }

        private ObservableCollection<Customer> _customerData = new ObservableCollection<Customer>();
        public ObservableCollection<Customer> CustomerData
        {
            get
            {
                return _customerData;
            }
            set
            {
                _customerData = value;
                RaisePropertyChanged("CustomerData");
            }
        }

        private ObservableCollection<Employee> _employeeData = new ObservableCollection<Employee>();
        public ObservableCollection<Employee> EmployeeData
        {
            get
            {
                return _employeeData;
            }
            set
            {
                _employeeData = value;
                RaisePropertyChanged("EmployeeData");
            }
        }

        #endregion

        #region Visibility

        private Visibility _busyVisibility = Visibility.Hidden;
        public Visibility BusyVisibility
        {
            get
            {
                return _busyVisibility;
            }
            set
            {
                _busyVisibility = value;
                RaisePropertyChanged("BusyVisibility");
            }
        }

        #endregion

        #region Private Methods

        private void UISetBusy()
        {
            BusyVisibility = Visibility.Visible;
        }

        private void UIClearBusy()
        {
            BusyVisibility = Visibility.Hidden;
        }

        private void InvokeMethodThreaded(Action actionToExecute)
        {
            Thread t = new Thread(delegate()
            {
                UISetBusy();
                actionToExecute();
                UIClearBusy();
            });
            t.Start();
        }

        #endregion
    }

有三个 Command 类的实例。它们用于处理来自视图的事件并执行相应的操作。Command 类的解释可以在我名为 WPF Secure Messenger 的文章中找到,该文章也使用了它。

应用程序启动后,将出现以下窗口


启动时,应用程序会加载模拟的 Employees 数据到第一个 DataGrid 控件中,如前所述。MainWindow.xaml 文件如下所示

 <Window x:Class="Dependency.Injection.WPFHost.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Dependency.Injection.WPFHost"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        Title="Dependency Injection Data Demo" Height="600" Width="1000" ResizeMode="NoResize"
        Icon="Injection.ico" >
    <Window.Background>
        <LinearGradientBrush>
            <GradientStop Color="LightSteelBlue" Offset="0.5" />
            <GradientStop Color="LightBlue" Offset="1" />
        </LinearGradientBrush>
    </Window.Background>
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="780*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="90" />
            <RowDefinition Height="460*" />
        </Grid.RowDefinitions>
        <ComboBox x:Name="comboDataSource" Grid.Column="0" Grid.Row="0" Width="100" Height="22" HorizontalAlignment="Left" VerticalAlignment="Top"
                  Margin="30,32" >
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="SelectionChanged" >
                    <i:InvokeCommandAction Command="{Binding ComboDatasourceChangedCommand}"
                                  CommandParameter="{Binding ElementName=comboDataSource, Path=SelectedItem}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
            <ComboBoxItem Content="Mocked Data" Selector.IsSelected="True" />
            <ComboBoxItem Content="Live Data" />
        </ComboBox>
        <GroupBox Grid.Column="0" Grid.Row="0" Width="150" Height="60" HorizontalAlignment="Left" VerticalAlignment="Top"
                  Margin="10" Header="Data Source"/>
        <TabControl x:Name="tabControl" Grid.Column="0" Grid.Row="1" Margin="10"
                    SelectedIndex="{Binding TabSelectedIndex, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="SelectionChanged">
                    <i:InvokeCommandAction Command="{Binding TabSelectionChangedCommand}"
                                  CommandParameter="{Binding ElementName=tabControl, Path=SelectedItem}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
            <TabItem Header="Employees">
                <DataGrid ItemsSource="{Binding Path=EmployeeData}" AutoGenerateColumns="False" AlternatingRowBackground="LightSteelBlue">
                    <DataGrid.Columns>
                        <DataGridTextColumn Width="*" Binding="{Binding Path=FirstName}" Header="First Name" IsReadOnly="True" />
                        <DataGridTextColumn Width="*" Binding="{Binding Path=LastName}" Header="Last Name" IsReadOnly="True" />
                        <DataGridTextColumn Width="*" Binding="{Binding Path=Title}" Header="Title" IsReadOnly="True" />
                        <DataGridTextColumn Width="*" Binding="{Binding Path=Extension}" Header="Extension" IsReadOnly="True" />
                    </DataGrid.Columns>
                </DataGrid>
            </TabItem>
            <TabItem Header="Customers">
                <DataGrid ItemsSource="{Binding Path=CustomerData}" AutoGenerateColumns="False" AlternatingRowBackground="LightSteelBlue">
                    <DataGrid.Columns>
                        <DataGridTextColumn Width="*" Binding="{Binding Path=CompanyName}" Header="First Name" IsReadOnly="True" />
                        <DataGridTextColumn Width="*" Binding="{Binding Path=ContactName}" Header="Last Name" IsReadOnly="True" />
                        <DataGridTextColumn Width="*" Binding="{Binding Path=ContactTitle}" Header="Title" IsReadOnly="True" />
                        <DataGridTextColumn Width="*" Binding="{Binding Path=Address}" Header="Address" IsReadOnly="True" />
                    </DataGrid.Columns>
                </DataGrid>
            </TabItem>
            <TabItem Header="Customer Orders">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="35" />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>
                    <Label Grid.Row="0" HorizontalAlignment="Left" Content="Customer" Margin="10,3" />
                    <ComboBox x:Name="comboCustomer" Grid.Row="0" Height="22" Width="150" HorizontalAlignment="Left" Margin="75,0"
                              ItemsSource="{Binding Customers}" DisplayMemberPath="Item2" SelectedValuePath="Item1" SelectedIndex="0">
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="SelectionChanged" >
                                <i:InvokeCommandAction Command="{Binding ComboCustomerChangedCommand}"
                                  CommandParameter="{Binding ElementName=comboCustomer, Path=SelectedItem}"/>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </ComboBox>
                    <DataGrid Grid.Row="1" ItemsSource="{Binding Path=CustomerOrdersData}" AutoGenerateColumns="False"
                               AlternatingRowBackground="LightSteelBlue">
                        <DataGrid.Columns>
                            <DataGridTextColumn Width="*" Binding="{Binding Path=OrderDate, StringFormat={}{0:dd-MM-yyyy}}" Header="Order Date" IsReadOnly="True" />
                            <DataGridTextColumn Width="*" Binding="{Binding Path=ProductName}" Header="Product Name" IsReadOnly="True" />
                            <DataGridTextColumn Width="*" Binding="{Binding Path=Quantity}" Header="Quatity" IsReadOnly="True" />
                            <DataGridTextColumn Width="*" Binding="{Binding Path=Price, StringFormat={}{0:C}}" Header="Price" IsReadOnly="True" />
                            <DataGridTextColumn Width="*" Binding="{Binding Path=TotalCost, StringFormat={}{0:C}}" Header="Total Cost" IsReadOnly="True" />
                        </DataGrid.Columns>
                    </DataGrid>
                </Grid>
            </TabItem>
        </TabControl>
        <Rectangle x:Name="rectBusy" Grid.Column="0" Grid.Row="1" Grid.RowSpan="10" Grid.ColumnSpan="3" Margin="10,33,10,10"
                   Visibility="{Binding BusyVisibility, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, FallbackValue=Visible}">
            <Rectangle.Fill>
                <VisualBrush>
                    <VisualBrush.Visual>
                        <StackPanel>
                            <StackPanel.Background>
                                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1" Opacity="0.75">
                                    <GradientStop Color="LightGray" Offset="0.0" />
                                    <GradientStop Color="Gray" Offset="1.0" />
                                </LinearGradientBrush>
                            </StackPanel.Background>
                            <TextBlock FontSize="25"/>
                            <TextBlock FontSize="25" FontWeight="Medium" FontFamily="Verdana"  Foreground="DarkBlue" Text="  Lazy Loading  "/>
                            <TextBlock FontSize="25"/>
                        </StackPanel>
                    </VisualBrush.Visual>
                </VisualBrush>
            </Rectangle.Fill>

        </Rectangle>

    </Grid>
</Window>

视图和 ViewModel 之间的数据绑定是通过在 XAML 文件中进行以下声明实现的

    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>

局部 namespace 声明如下

 xmlns:local="clr-namespace:Dependency.Injection.WPFHost 

这种机制确保 XAML 文件中的绑定引用 MainViewModel 类中的属性。典型的绑定声明如下所示

 Visibility="{Binding BusyVisibility, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, FallbackValue=Visible}" 

Visibility 属性绑定到 MainViewModel 中名为 BusyVisibility 的属性,模式是单向的,当 BusyVisibility 属性更改并调用 RaisePropertyChanged 方法时,视图会更新。

如你所知,WPF 标准不支持将控件事件连接到命令,但可以通过使用 System.Windows.Interactivity 命名空间来实现,该命名空间可以作为引用添加。必须在 XAML 文件中声明命名空间,如下所示

        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

标签 'i' 现在可以如下引用,以将事件连接到命令

        <ComboBox x:Name="comboDataSource" Grid.Column="0" Grid.Row="0" Width="100" Height="22" HorizontalAlignment="Left" VerticalAlignment="Top"
                  Margin="30,32" >
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="SelectionChanged" >
                    <i:InvokeCommandAction Command="{Binding ComboDatasourceChangedCommand}"
                                  CommandParameter="{Binding ElementName=comboDataSource, Path=SelectedItem}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
            <ComboBoxItem Content="Mocked Data" Selector.IsSelected="True" />
            <ComboBoxItem Content="Live Data" />
        </ComboBox>

ComboBox 上方的 SelectionChanged 事件连接到一个名为 ComboDatasourceChangedCommand 的命令,该命令同样位于 MainViewModel 类中。

应用程序通过按需加载数据到视图 (MainWindow.xaml) 中运行,这是通过订阅 TabControl 的 selection changed 事件实现的,如下所示

        private void GetTabSelectionChangedCommandExecute(Object parameter)
        {
            if (parameter == null) return;

            TabItem tabItem = (TabItem)parameter;

            if (tabItem.Header.ToString() == "Customers")
            {
                InvokeMethodThreaded(new Action(() =>
                {
                    CustomerData = NorthwindData.CustomerData.Value.ToObservableCollection();
                }));
            }
            if (tabItem.Header.ToString() == "Customer Orders")
            {
                InvokeMethodThreaded(new Action(() =>
                {
                    Customers = DataOperations.GetCustomers(NorthwindData).ToObservableCollection();
                    CustomerOrdersData = new ObservableCollection<OrdersData>();
                }));
            }
            if (tabItem.Header.ToString() == "Employees")
            {
                InvokeMethodThreaded(new Action(() =>
                {
                    EmployeeData = NorthwindData.EmployeeData.Value.ToObservableCollection();
                }));
            }
        }

当选择“Customers”和“Employees”选项卡时,CustomerDataEmployeeData 属性会从 NorthwindData 对象中用相应的数据填充。需要注意的是,这里使用了 .Value 属性,这是延迟加载启动并从数据库或模拟数据源获取数据的地方。“Customer Orders”选项卡如下所示

tab

对于“Customer Orders”选项卡,数据是通过 DataOperations 类以不同的方式派生的,如下所示

        public static ObservableCollection<OrdersData> GetCustomerOrders(Northwind northWindData, String customerID)
        {
            try
            {
                List<Order> customerOrders = (from cd in northWindData.CustomerData.Value
                                              where cd.CustomerID == customerID
                                              select cd).FirstOrDefault().Orders.ToList();

                List<OrdersData> ordersData = new List<OrdersData>();

                customerOrders.ForEach(x => ordersData.Add(
                                                    new OrdersData()
                                                    {
                                                        OrderDate = x.OrderDate,
                                                        ProductName = x.Order_Details.First().Product.ProductName,
                                                        Price = x.Order_Details.First().UnitPrice,
                                                        Quantity = x.Order_Details.First().Quantity,
                                                        TotalCost = x.Order_Details.First().UnitPrice *
                                                                    x.Order_Details.First().Quantity
                                                    }));

                return ordersData.ToObservableCollection();
            }
            catch (Exception ex)
            {
                return null;
            }
        }

从该选项卡上的 ComboBox 中选择的客户中,枚举出订单数据集合。

注意: 只有在选择“实时数据”模式时,数据才会出现在 DataGrid 中。这是因为订单数据并未填充到模拟数据结构中。

每次延迟加载数据处理时,一个矩形会覆盖在表单选项卡控件上,以防止用户进行任何进一步的操作,如下所示

lazy loading

每次更改“数据源” ComboBox 的选择时,都会执行以下代码块

        private void GetComboDatasourceChangedCommandExecute(Object parameter)
        {
            ComboBoxItem comboBox = (ComboBoxItem)parameter;

            String selection = comboBox.Content.ToString();

            if (selection == "Mocked Data")
                NorthwindData = container.Resolve<IData>("MockedData").Data;

            if (selection == "Live Data")
            {
                InvokeMethodThreaded(new Action(() =>
                {
                    NorthwindData = container.Resolve<IData>("DbData").Data;
                }));
            }

            TabSelectedIndex = -1;
        }

可以看到,通过解析容器上的相应命名配置,将 NorthwindData 类设置为正确的数据源。

InvokeMethodThreaded 方法用于将数据操作发送到单独的线程,这可以防止 UI 线程被阻塞导致应用程序冻结。InvokeMethodThreaded 方法如下所示

        private void UISetBusy()
        {
            BusyVisibility = Visibility.Visible;
        }

        private void UIClearBusy()
        {
            BusyVisibility = Visibility.Hidden;
        }

        private void InvokeMethodThreaded(Action actionToExecute)
        {
            Thread t = new Thread(delegate()
            {
                UISetBusy();
                actionToExecute();
                UIClearBusy();
            });
            t.Start();
        }

BusyVisibility 属性启用了前面描述的“延迟加载”矩形。

创建数据库的说明

为了在本地 SQL Server 实例上创建 Northwind 数据库,Visual Studio 必须以“管理员”模式启动,如下所示

admin

加载项目后,右键单击数据库项目,然后选择“Publish...”。此时将出现以下对话框

dialog

接下来,按“Load Profile”并选择数据库项目内的 XML 文件。这将加载配置,如下所示

接下来,选择“Edit”来编辑目标数据库连接,将出现以下对话框

edit

接下来,将服务器名称更改为适合你的数据库服务器的名称,然后按 OK。

注意: 请勿按“Test Connection”,因为它会失败,因为数据库尚不存在。

最后,按“Publish”,数据库应该会被创建。如果过程成功,则“Data Tools Operations”视图应如下所示

success

最后要做的就是更改 WPF 宿主应用程序的 App.config 中的连接字符串。需要将数据源更改为你 SQL Server 实例的名称,如下所示

config

现在应用程序可以运行在“实时模式”和“模拟数据”模式下。

关注点

此项目可能看起来是一种不寻常的编程模式,使用了延迟加载,但由于 Entity Framework 默认启用了延迟加载,因此这似乎是一个有趣的研究。我发现的一个优点是,数据操作功能(数据库查询)可以从数据库项目中移出,并移到宿主应用程序或其他单独的类库中。

类库的数量相当多,有些代码量很少。这是为了提供一个可扩展的项目结构,可以用于更大型的项目类型。

你可能已经注意到,有一个名为“Data Demo Trace.xml”的文件。这是从 SQL Server Profiler 中提取的。这是我使用该应用程序时进行的一项研究,用于查看数据何时被请求自数据库。跟踪显示,数据仅在加载到表单时才从数据库中请求,这正是我的预期。

历史

  • 版本 1.0
© . All rights reserved.