• 一篇文章,带你玩转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的一个解耦。

  • 相关阅读:
    C#,asp.net,命名空间名,类名,方法名的获得
    asp.net引用用户控件
    SQL数据是否存在(是否有数据)判断,表,存储过程是否存在
    asp:Button 事件,点击事件 html Button runat="sever"
    CSS图片最大大小限制
    asp.net 路径
    Js实现网站的重定向,Js转向网址,Js跳转
    ViewState 页面状态保留
    vs 附加到进程
    sql XML处理,sp_xml_preparedocument,openxml
  • 原文地址:https://www.cnblogs.com/Koalin/p/11706420.html
Copyright © 2020-2023  润新知