一篇文章,帶你玩轉MVVM,Dapper,AutoMapper


一、背景

由於現在做的項目都是采用WPF來進行UI設計,開發過程中都是基於MVVM來進行開發,但是項目中的MVVM並不是真正的把實體和視圖進行解耦,而是將實體和視圖完全融合起來,ViewModel只是用來實現View和Model的數據同步,違背了MVVM設計的數據雙向綁定的初衷,完全沒有發揮出MVVM的優勢。

二、MVVM基本概念

1.M:表示Model,也就是一個實體對象。

2.V:表示VIew,也就是UI界面展示,即人機交互界面。

3.ViewModel:可以理解為搭建View和Model的一個業務邏輯橋梁。

三、Demo來說明

首先建立解決方案,方案框架如下:

在Models中創建一個Model實體對象Contacts

 

public class Contacts
    {
        public int ID { get; set; }

        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public string Company { get; set; }
        public string Title { get; set; }
    }

接下來我們在ViewModel中實現V和M的完全解耦,在ViewModels中添加ContractsViewModels

 

 public class ContractsViewModels : BaseViewModels
    {
       public  Contacts contracts = null;
        public ContractsViewModels(Contacts contracts)
        {
            this.contracts = contracts;
        }
        public ContractsViewModels()
        {
            contracts = new Contacts();
        }

        public int ID { get { return contracts.ID; } set { contracts.ID = value; OnPropertyChanged(this, nameof(ID)); } }
       
        public string FirstName { get { return contracts.FirstName; } set { contracts.FirstName = value; OnPropertyChanged(this, nameof(FirstName)); } }
       
        public string LastName { get { return contracts.LastName; } set { contracts.LastName = value; OnPropertyChanged(this, nameof(LastName)); } }
      

        public string Email { get { return contracts.Email; } set { contracts.Email = value; OnPropertyChanged(this, nameof(Email)); } }
      
        public string Company { get { return contracts.Company; } set { contracts.Company = value; OnPropertyChanged(this, nameof(Company)); } }

      
        public string Title { get { return contracts.Title; } set { contracts.Title = value; OnPropertyChanged(this, nameof(Title)); } }


    }

當數據雙向綁定時,視圖知道自己綁定了那個實體,為了讓實體的屬性改變中,能夠通知綁定的View,我們需要實現INotifyPropertyChanged這個接口,具體實現如下

 public class BaseViewModels : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(object sender, string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(sender ?? this, new PropertyChangedEventArgs(name));
            }
        }
    }

以上實現,在ViewModel中改變屬性,便可以輕松的通知到綁定時View控件,真正意義上實現了雙向數據綁定。

接下來在ViewModel中添加ContractsVMServices來方便View的數據操作

 

 

 代碼如下:

  /// <summary>
    /// ContractsViewModels服務類
    /// </summary>
    public class ContractsVMServices
    {
        /// <summary>
        /// 加載所有數據的事件
        /// </summary>
        public event EventHandler OnLoadAllContracts;
        /// <summary>
        /// 數據集合對象
        /// </summary>
        public ObservableCollection<ContractsViewModels> ContractsViewModels { get; private set; }
        /// <summary>
        /// 單個實體的事件
        /// </summary>
        public event EventHandler OnLoadContracts;
        /// <summary>
        /// 單個實體
        /// </summary>
        public ContractsViewModels ContractsViewModel { get; private set; }
        
        private ContractsVMServices() { }

        public static ContractsVMServices Instances = new ContractsVMServices();
        /// <summary>
        /// 初始化
        /// </summary>
        public void Init()
        {
            ContractsViewModels = new ObservableCollection<ContractsViewModels>();
            ContractsViewModel = new ContractsViewModels(new Contacts());
            OnLoadAllContracts?.Invoke(this, EventArgs.Empty);
            OnLoadContracts?.Invoke(this, EventArgs.Empty);
            AutoMapperWrapper.Start();
            LoadAllContracts();
        }
        /// <summary>
        /// 定時模擬數據改變
        /// </summary>
        /// <param name="afterSecond"></param>
        /// <param name="freSecond"></param>
        public void ChangeByTime(int afterSecond = 5, int freSecond = 2)
        {
            Task.Run(async () =>
            {
                await Task.Delay(afterSecond * 1000);
                while (true)
                {
                    try
                    {
                        foreach (var item in ContractsViewModels)
                        {
                            item.Title = "Change" + DateTime.Now.ToString();
                            string update = "Update contacts set Title=@Title where ID=@ID";
                            List<KeyValuePair<string, object>> ls = new List<KeyValuePair<string, object>>();
                            ls.Add(new KeyValuePair<string, object>(nameof(item.Title), item.Title));
                            ls.Add(new KeyValuePair<string, object>(nameof(item.ID), item.ID));

                            DapperHelper.Update(update, ls);

                        }
                        //string insert = @"Update into contacts (Id,FirstName,LastName,Email,Company,Title) values(@Id,@FirstName,@LastName,@Email,@Company,@Title)";

                    }
                    catch (Exception ex)
                    {
                    }
                    finally
                    {
                        await Task.Delay(freSecond * 1000);
                    }
                }

            });
        }
        /// <summary>
        /// 從數據庫中加載所有數據
        /// </summary>
        private void LoadAllContracts()
        {
            try
            {
                //Task.Run(() =>
                //{
                ContractsViewModels.Clear();
                List<Contacts> contracts = DBDapper.DapperHelper.Query<Contacts>("Contacts");


                // ContractsViewModels = AutoMapperWrapper.Map<List<Contacts>, ObservableCollection<ContractsViewModels>>(contracts);
                foreach (var item in contracts)
                {
                    //ContractsViewModels models= AutoMapperWrapper.Map<Contacts, ContractsViewModels>(item);

                    ContractsViewModels.Add(new ViewModels.ContractsViewModels(item));
                }
                //});
            }
            catch (Exception ex)
            {
                Console.WriteLine(  ex.ToString());
             
            }
        }
        /// <summary>
        /// 根據ID來加載指定數據
        /// </summary>
        /// <param name="id"></param>
        public void LoadOnContracts(int id)
        {
            ContractsViewModel = ContractsViewModels.Where(obj => obj.ID == id).FirstOrDefault();
        }
        /// <summary>
        /// 創建一個新的對象
        /// </summary>
        public void Add()
        {
            try
            {
                int id = ContractsViewModels.Count > 0 ? ContractsViewModels[ContractsViewModels.Count - 1].ID + 1 : 0;
                string insert = @"insert into contacts (Id,FirstName,LastName,Email,Company,Title) values(@Id,@FirstName,@LastName,@Email,@Company,@Title)";
                int res = DapperHelper.Insert<Contacts>(insert, new List<Contacts>() { new Contacts() { ID =id, FirstName = "123", LastName = "456", Email = "123", Company = "1324", Title = "444" } });
                if (res > 0)
                {
                    LoadAllContracts();
                }
            }
            catch (Exception ex)
            {

                Console.WriteLine(ex.ToString());
            }
        }
        /// <summary>
        /// 刪除對象
        /// 為了方便演示我只刪除數據集合中的第一個對象
        /// </summary>
        public void Delete()
        {
            try
            {
                if (ContractsViewModels.Count > 0)
                {
                    string delete = "delete from Contacts where ID= @ID";
                    int res = DapperHelper.Delete(delete, new { ID = ContractsViewModels[0].ID });
                    if (res > 0)
                    {
                        LoadAllContracts();
                    }
                }
            }
            catch (Exception ex)
            {

                Console.WriteLine(ex.ToString());

            }
        }


        
    }

實現數據庫的CRUD操作,我這里主要用到半自動化的ORM----Dapper。

老生常談,首先需要從Nuget中安裝Dapper,安裝到DBDapper項目中。

 

 

或者在控制台中輸入: Install -Package Dapper 完成Dapper下載安裝。

創建一個DapperHelper幫助類

  public class DapperHelper
    {
        //三部曲
        //第一步:使用連接字符串創建一個IDBConnection對象;
        static IDbConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["SqlServerConnString"].ToString());
        //第二步:編寫一個查詢並將其存儲在一個普通的字符串變量中;
        public static  void test()
        {
            string query = "SELECT * from contacts;";
            List<Contacts> contracts=(List<Contacts>)conn.Query<Contacts>(query);
            Console.WriteLine( contracts.Count);
        }

        public static  List<T> Query<T>(string typeofName)
        {
            string query = $"SELECT * from {typeofName};";
            List<T> contracts = (List<T>)conn.Query<T>(query);
            return contracts;
        }

        public static  int Insert<T>(string sql,IEnumerable<T> ls)
        {
            try
            {
              return   conn.Execute(sql, ls);
            }
            catch (Exception ex)
            {

                throw new Exception(ex.ToString());
            }
               
        }
        public static int Update(string sql, IEnumerable<KeyValuePair<string,object>> ls)
        {
            try
            {
                return conn.Execute(sql, ls);
            }
            catch (Exception ex)
            {

                throw new Exception(ex.ToString());
            }

        }


        public static int Delete(string sql, object obj)
        {
            try
            {
                return conn.Execute(sql, obj);
            }
            catch (Exception ex)
            {

                throw new Exception(ex.ToString());
            }
        }
        //public static int Update<T>(string sql, List<T> ls)
        //{

        //}
        //第三步:調用db.execute()並傳遞查詢,完成。
    }

看上面的三步曲,和ADO.net大同小異,不過我們再也不用將數據庫表的數據轉換成實體了,采用Dapper便傻瓜式的轉換了。

看我們的代碼,我們會發現,Model跟View完全獨立,不過這個時候,ViewModel會多出跟Model一樣的一個實體出來,為了實現Model到ViewModel的一個完美映射,我這里采用了AutoMapper來實現映射。

老套路,我們從Nuget中下載AutoMapper,安裝到ViewModels。

 

 

 或者在控制台中輸入: Install -Package AutoMapper完成AutoMapper下載安裝。

在ViewModels中建立AutoMapper的一個包裝類AutoMapperWrapper,AutoMapper需要初始化應映射規則,因此我們需要先創建一個映射

public class SourceProfile : MapperConfigurationExpression
    {
        public SourceProfile()
        {
            base.CreateMap<ContractsViewModels, Contacts>();
            base.CreateMap<Contacts, ContractsViewModels>();
            //base.CreateMap<ContractsViewModels,Models.Contracts>();
        }
    }

AutoMapperWrapper如下:

public class AutoMapperWrapper
    {
        //protected DTOObject Result { get; set; }

        //protected IEnumerable<DTOObject> Results { get; set; }
        static   Mapper mapper = null;
        public static void Start()
        {
            MapperConfiguration configuration = new MapperConfiguration(new SourceProfile());
            mapper =  new Mapper(configuration);
           // mapper.Map<,>
          //  Mapper
            //new SourceProfile();
        }
        public static T Map<S, T>(S soure)
        {
            T to = mapper.Map<S, T>(soure);

            return to;
        }

    }

以上我們便實現了一個ViewModel,DB的后台管理。接下來我們來看一個View怎么實現數據的綁定

先看一下代碼



<Window x:Class="View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:View"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded">
    <Grid>
        <DataGrid x:Name="datagrid" ItemsSource="{Binding ContractsViewModels}" AutoGenerateColumns="False" HorizontalAlignment="Left" Height="273" Margin="72,70,0,0" VerticalAlignment="Top" Width="415">
            <DataGrid.Columns>
                <DataGridTextColumn Header="ID" Binding="{Binding ID}"></DataGridTextColumn>
                <DataGridTextColumn Header="FirstName" Binding="{Binding FirstName}"></DataGridTextColumn>
                <DataGridTextColumn Header="LastName" Binding="{Binding LastName}"></DataGridTextColumn>
                <DataGridTextColumn Header="Email" Binding="{Binding Email}"></DataGridTextColumn>
                <DataGridTextColumn Header="Company" Binding="{Binding Company}"></DataGridTextColumn>
                <DataGridTextColumn Header="Title" Binding="{Binding Title}"></DataGridTextColumn>
                
            </DataGrid.Columns>
        </DataGrid>
        <Button x:Name="btn_LoadAll" Content="加載所有Contract數據" HorizontalAlignment="Left" Margin="97,372,0,0" VerticalAlignment="Top" Click="btn_LoadAll_Click"/>
        <Button x:Name="btn_ChangeByTime" Content="定時改變" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="284,372,0,0" Click="btn_ChangeByTime_Click"/>
        <Button x:Name="btn_Delete" Content="刪除" HorizontalAlignment="Left" Margin="417,372,0,0" VerticalAlignment="Top" Width="75" Click="btn_Delete_Click"/>
        <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="591,144,0,0" TextWrapping="Wrap" Text="{Binding ContractsViewModel.FirstName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
        <Label Content="FirstName" HorizontalAlignment="Left" Margin="506,141,0,0" VerticalAlignment="Top"/>

    </Grid>
</Window>

 

 



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace View
{
    /// <summary>
    /// MainWindow.xaml 的交互邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            try
            {
                ViewModels.ContractsVMServices.Instances.OnLoadAllContracts += (s, es) => {
                    this.Dispatcher.InvokeAsync(() => datagrid.DataContext = s);
                };
                ViewModels.ContractsVMServices.Instances.OnLoadContracts += (s, es) => {
                    this.Dispatcher.InvokeAsync(() => textBox.DataContext = s);
                };
                ViewModels.ContractsVMServices.Instances.Init();
                ViewModels.ContractsVMServices.Instances.LoadOnContracts(0);
            }
            catch (Exception ex)
            {

               
            }
        }

        private void btn_LoadAll_Click(object sender, RoutedEventArgs e)
        {
            ViewModels.ContractsVMServices.Instances.Add();
        }

        private void btn_ChangeByTime_Click(object sender, RoutedEventArgs e)
        {
            ViewModels.ContractsVMServices.Instances.ChangeByTime();
        }

        private void btn_Delete_Click(object sender, RoutedEventArgs e)
        {
            ViewModels.ContractsVMServices.Instances.Delete();

        }
    }
}

 

 

 

以上我們看到DataGrid的ItemsSource必須要先綁定一個源,這個源的名稱跟我們ViewModel中的數據集合是一致的。修改TextBox中的數據的時候我們發現DataGrid的數據也跟着改變。以上就是實現V與M的一個解耦。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM