一、背景

由于现在做的项目都是采用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

  1. public class Contacts
  2. {
  3. public int ID { get; set; }
  4.  
  5. public string FirstName { get; set; }
  6. public string LastName { get; set; }
  7. public string Email { get; set; }
  8. public string Company { get; set; }
  9. public string Title { get; set; }
  10. }

接下来我们在ViewModel中实现V和M的完全解耦,在ViewModels中添加ContractsViewModels

  1. public class ContractsViewModels : BaseViewModels
  2. {
  3. public Contacts contracts = null;
  4. public ContractsViewModels(Contacts contracts)
  5. {
  6. this.contracts = contracts;
  7. }
  8. public ContractsViewModels()
  9. {
  10. contracts = new Contacts();
  11. }
  12.  
  13. public int ID { get { return contracts.ID; } set { contracts.ID = value; OnPropertyChanged(this, nameof(ID)); } }
  14.  
  15. public string FirstName { get { return contracts.FirstName; } set { contracts.FirstName = value; OnPropertyChanged(this, nameof(FirstName)); } }
  16.  
  17. public string LastName { get { return contracts.LastName; } set { contracts.LastName = value; OnPropertyChanged(this, nameof(LastName)); } }
  18.  
  19. public string Email { get { return contracts.Email; } set { contracts.Email = value; OnPropertyChanged(this, nameof(Email)); } }
  20.  
  21. public string Company { get { return contracts.Company; } set { contracts.Company = value; OnPropertyChanged(this, nameof(Company)); } }
  22.  
  23. public string Title { get { return contracts.Title; } set { contracts.Title = value; OnPropertyChanged(this, nameof(Title)); } }
  24.  
  25. }

当数据双向绑定时,视图知道自己绑定了那个实体,为了让实体的属性改变中,能够通知绑定的View,我们需要实现INotifyPropertyChanged这个接口,具体实现如下

  1. public class BaseViewModels : INotifyPropertyChanged
  2. {
  3. public event PropertyChangedEventHandler PropertyChanged;
  4. protected void OnPropertyChanged(object sender, string name)
  5. {
  6. if (PropertyChanged != null)
  7. {
  8. PropertyChanged(sender ?? this, new PropertyChangedEventArgs(name));
  9. }
  10. }
  11. }

以上实现,在ViewModel中改变属性,便可以轻松的通知到绑定时View控件,真正意义上实现了双向数据绑定。

接下来在ViewModel中添加ContractsVMServices来方便View的数据操作

代码如下:

  1. /// <summary>
  2. /// ContractsViewModels服务类
  3. /// </summary>
  4. public class ContractsVMServices
  5. {
  6. /// <summary>
  7. /// 加载所有数据的事件
  8. /// </summary>
  9. public event EventHandler OnLoadAllContracts;
  10. /// <summary>
  11. /// 数据集合对象
  12. /// </summary>
  13. public ObservableCollection<ContractsViewModels> ContractsViewModels { get; private set; }
  14. /// <summary>
  15. /// 单个实体的事件
  16. /// </summary>
  17. public event EventHandler OnLoadContracts;
  18. /// <summary>
  19. /// 单个实体
  20. /// </summary>
  21. public ContractsViewModels ContractsViewModel { get; private set; }
  22.  
  23. private ContractsVMServices() { }
  24.  
  25. public static ContractsVMServices Instances = new ContractsVMServices();
  26. /// <summary>
  27. /// 初始化
  28. /// </summary>
  29. public void Init()
  30. {
  31. ContractsViewModels = new ObservableCollection<ContractsViewModels>();
  32. ContractsViewModel = new ContractsViewModels(new Contacts());
  33. OnLoadAllContracts?.Invoke(this, EventArgs.Empty);
  34. OnLoadContracts?.Invoke(this, EventArgs.Empty);
  35. AutoMapperWrapper.Start();
  36. LoadAllContracts();
  37. }
  38. /// <summary>
  39. /// 定时模拟数据改变
  40. /// </summary>
  41. /// <param name="afterSecond"></param>
  42. /// <param name="freSecond"></param>
  43. public void ChangeByTime(int afterSecond = , int freSecond = )
  44. {
  45. Task.Run(async () =>
  46. {
  47. await Task.Delay(afterSecond * );
  48. while (true)
  49. {
  50. try
  51. {
  52. foreach (var item in ContractsViewModels)
  53. {
  54. item.Title = "Change" + DateTime.Now.ToString();
  55. string update = "Update contacts set Title=@Title where ID=@ID";
  56. List<KeyValuePair<string, object>> ls = new List<KeyValuePair<string, object>>();
  57. ls.Add(new KeyValuePair<string, object>(nameof(item.Title), item.Title));
  58. ls.Add(new KeyValuePair<string, object>(nameof(item.ID), item.ID));
  59.  
  60. DapperHelper.Update(update, ls);
  61.  
  62. }
  63. //string insert = @"Update into contacts (Id,FirstName,LastName,Email,Company,Title) values(@Id,@FirstName,@LastName,@Email,@Company,@Title)";
  64.  
  65. }
  66. catch (Exception ex)
  67. {
  68. }
  69. finally
  70. {
  71. await Task.Delay(freSecond * );
  72. }
  73. }
  74.  
  75. });
  76. }
  77. /// <summary>
  78. /// 从数据库中加载所有数据
  79. /// </summary>
  80. private void LoadAllContracts()
  81. {
  82. try
  83. {
  84. //Task.Run(() =>
  85. //{
  86. ContractsViewModels.Clear();
  87. List<Contacts> contracts = DBDapper.DapperHelper.Query<Contacts>("Contacts");
  88.  
  89. // ContractsViewModels = AutoMapperWrapper.Map<List<Contacts>, ObservableCollection<ContractsViewModels>>(contracts);
  90. foreach (var item in contracts)
  91. {
  92. //ContractsViewModels models= AutoMapperWrapper.Map<Contacts, ContractsViewModels>(item);
  93.  
  94. ContractsViewModels.Add(new ViewModels.ContractsViewModels(item));
  95. }
  96. //});
  97. }
  98. catch (Exception ex)
  99. {
  100. Console.WriteLine( ex.ToString());
  101.  
  102. }
  103. }
  104. /// <summary>
  105. /// 根据ID来加载指定数据
  106. /// </summary>
  107. /// <param name="id"></param>
  108. public void LoadOnContracts(int id)
  109. {
  110. ContractsViewModel = ContractsViewModels.Where(obj => obj.ID == id).FirstOrDefault();
  111. }
  112. /// <summary>
  113. /// 创建一个新的对象
  114. /// </summary>
  115. public void Add()
  116. {
  117. try
  118. {
  119. int id = ContractsViewModels.Count > ? ContractsViewModels[ContractsViewModels.Count - ].ID + : ;
  120. string insert = @"insert into contacts (Id,FirstName,LastName,Email,Company,Title) values(@Id,@FirstName,@LastName,@Email,@Company,@Title)";
  121. int res = DapperHelper.Insert<Contacts>(insert, new List<Contacts>() { new Contacts() { ID =id, FirstName = "", LastName = "", Email = "", Company = "", Title = "" } });
  122. if (res > )
  123. {
  124. LoadAllContracts();
  125. }
  126. }
  127. catch (Exception ex)
  128. {
  129.  
  130. Console.WriteLine(ex.ToString());
  131. }
  132. }
  133. /// <summary>
  134. /// 删除对象
  135. /// 为了方便演示我只删除数据集合中的第一个对象
  136. /// </summary>
  137. public void Delete()
  138. {
  139. try
  140. {
  141. if (ContractsViewModels.Count > )
  142. {
  143. string delete = "delete from Contacts where ID= @ID";
  144. int res = DapperHelper.Delete(delete, new { ID = ContractsViewModels[].ID });
  145. if (res > )
  146. {
  147. LoadAllContracts();
  148. }
  149. }
  150. }
  151. catch (Exception ex)
  152. {
  153.  
  154. Console.WriteLine(ex.ToString());
  155.  
  156. }
  157. }
  158.  
  159. }

实现数据库的CRUD操作,我这里主要用到半自动化的ORM----Dapper。

老生常谈,首先需要从Nuget中安装Dapper,安装到DBDapper项目中。

或者在控制台中输入: Install -Package Dapper 完成Dapper下载安装。

创建一个DapperHelper帮助类

  1. public class DapperHelper
  2. {
  3. //三部曲
  4. //第一步:使用连接字符串创建一个IDBConnection对象;
  5. static IDbConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["SqlServerConnString"].ToString());
  6. //第二步:编写一个查询并将其存储在一个普通的字符串变量中;
  7. public static void test()
  8. {
  9. string query = "SELECT * from contacts;";
  10. List<Contacts> contracts=(List<Contacts>)conn.Query<Contacts>(query);
  11. Console.WriteLine( contracts.Count);
  12. }
  13.  
  14. public static List<T> Query<T>(string typeofName)
  15. {
  16. string query = $"SELECT * from {typeofName};";
  17. List<T> contracts = (List<T>)conn.Query<T>(query);
  18. return contracts;
  19. }
  20.  
  21. public static int Insert<T>(string sql,IEnumerable<T> ls)
  22. {
  23. try
  24. {
  25. return conn.Execute(sql, ls);
  26. }
  27. catch (Exception ex)
  28. {
  29.  
  30. throw new Exception(ex.ToString());
  31. }
  32.  
  33. }
  34. public static int Update(string sql, IEnumerable<KeyValuePair<string,object>> ls)
  35. {
  36. try
  37. {
  38. return conn.Execute(sql, ls);
  39. }
  40. catch (Exception ex)
  41. {
  42.  
  43. throw new Exception(ex.ToString());
  44. }
  45.  
  46. }
  47.  
  48. public static int Delete(string sql, object obj)
  49. {
  50. try
  51. {
  52. return conn.Execute(sql, obj);
  53. }
  54. catch (Exception ex)
  55. {
  56.  
  57. throw new Exception(ex.ToString());
  58. }
  59. }
  60. //public static int Update<T>(string sql, List<T> ls)
  61. //{
  62.  
  63. //}
  64. //第三步:调用db.execute()并传递查询,完成。
  65. }

看上面的三步曲,和ADO.net大同小异,不过我们再也不用将数据库表的数据转换成实体了,采用Dapper便傻瓜式的转换了。

看我们的代码,我们会发现,Model跟View完全独立,不过这个时候,ViewModel会多出跟Model一样的一个实体出来,为了实现Model到ViewModel的一个完美映射,我这里采用了AutoMapper来实现映射。

老套路,我们从Nuget中下载AutoMapper,安装到ViewModels。

或者在控制台中输入: Install -Package AutoMapper完成AutoMapper下载安装。

在ViewModels中建立AutoMapper的一个包装类AutoMapperWrapper,AutoMapper需要初始化应映射规则,因此我们需要先创建一个映射

  1. public class SourceProfile : MapperConfigurationExpression
  2. {
  3. public SourceProfile()
  4. {
  5. base.CreateMap<ContractsViewModels, Contacts>();
  6. base.CreateMap<Contacts, ContractsViewModels>();
  7. //base.CreateMap<ContractsViewModels,Models.Contracts>();
  8. }
  9. }

AutoMapperWrapper如下:

  1. public class AutoMapperWrapper
  2. {
  3. //protected DTOObject Result { get; set; }
  4.  
  5. //protected IEnumerable<DTOObject> Results { get; set; }
  6. static Mapper mapper = null;
  7. public static void Start()
  8. {
  9. MapperConfiguration configuration = new MapperConfiguration(new SourceProfile());
  10. mapper = new Mapper(configuration);
  11. // mapper.Map<,>
  12. // Mapper
  13. //new SourceProfile();
  14. }
  15. public static T Map<S, T>(S soure)
  16. {
  17. T to = mapper.Map<S, T>(soure);
  18.  
  19. return to;
  20. }
  21.  
  22. }

以上我们便实现了一个ViewModel,DB的后台管理。接下来我们来看一个View怎么实现数据的绑定

先看一下代码

  1.  
  1. <Window x:Class="View.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:local="clr-namespace:View"
  7. mc:Ignorable="d"
  8. Title="MainWindow" Height="" Width="" Loaded="Window_Loaded">
  9. <Grid>
  10. <DataGrid x:Name="datagrid" ItemsSource="{Binding ContractsViewModels}" AutoGenerateColumns="False" HorizontalAlignment="Left" Height="" Margin="72,70,0,0" VerticalAlignment="Top" Width="">
  11. <DataGrid.Columns>
  12. <DataGridTextColumn Header="ID" Binding="{Binding ID}"></DataGridTextColumn>
  13. <DataGridTextColumn Header="FirstName" Binding="{Binding FirstName}"></DataGridTextColumn>
  14. <DataGridTextColumn Header="LastName" Binding="{Binding LastName}"></DataGridTextColumn>
  15. <DataGridTextColumn Header="Email" Binding="{Binding Email}"></DataGridTextColumn>
  16. <DataGridTextColumn Header="Company" Binding="{Binding Company}"></DataGridTextColumn>
  17. <DataGridTextColumn Header="Title" Binding="{Binding Title}"></DataGridTextColumn>
  18.  
  19. </DataGrid.Columns>
  20. </DataGrid>
  21. <Button x:Name="btn_LoadAll" Content="加载所有Contract数据" HorizontalAlignment="Left" Margin="97,372,0,0" VerticalAlignment="Top" Click="btn_LoadAll_Click"/>
  22. <Button x:Name="btn_ChangeByTime" Content="定时改变" HorizontalAlignment="Left" VerticalAlignment="Top" Width="" Margin="284,372,0,0" Click="btn_ChangeByTime_Click"/>
  23. <Button x:Name="btn_Delete" Content="删除" HorizontalAlignment="Left" Margin="417,372,0,0" VerticalAlignment="Top" Width="" Click="btn_Delete_Click"/>
  24. <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="" Margin="591,144,0,0" TextWrapping="Wrap" Text="{Binding ContractsViewModel.FirstName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width=""/>
  25. <Label Content="FirstName" HorizontalAlignment="Left" Margin="506,141,0,0" VerticalAlignment="Top"/>
  26.  
  27. </Grid>
  28. </Window>
  1.  
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Windows;
  7. using System.Windows.Controls;
  8. using System.Windows.Data;
  9. using System.Windows.Documents;
  10. using System.Windows.Input;
  11. using System.Windows.Media;
  12. using System.Windows.Media.Imaging;
  13. using System.Windows.Navigation;
  14. using System.Windows.Shapes;
  15.  
  16. namespace View
  17. {
  18. /// <summary>
  19. /// MainWindow.xaml 的交互逻辑
  20. /// </summary>
  21. public partial class MainWindow : Window
  22. {
  23. public MainWindow()
  24. {
  25. InitializeComponent();
  26. }
  27.  
  28. private void Window_Loaded(object sender, RoutedEventArgs e)
  29. {
  30. try
  31. {
  32. ViewModels.ContractsVMServices.Instances.OnLoadAllContracts += (s, es) => {
  33. this.Dispatcher.InvokeAsync(() => datagrid.DataContext = s);
  34. };
  35. ViewModels.ContractsVMServices.Instances.OnLoadContracts += (s, es) => {
  36. this.Dispatcher.InvokeAsync(() => textBox.DataContext = s);
  37. };
  38. ViewModels.ContractsVMServices.Instances.Init();
  39. ViewModels.ContractsVMServices.Instances.LoadOnContracts();
  40. }
  41. catch (Exception ex)
  42. {
  43.  
  44. }
  45. }
  46.  
  47. private void btn_LoadAll_Click(object sender, RoutedEventArgs e)
  48. {
  49. ViewModels.ContractsVMServices.Instances.Add();
  50. }
  51.  
  52. private void btn_ChangeByTime_Click(object sender, RoutedEventArgs e)
  53. {
  54. ViewModels.ContractsVMServices.Instances.ChangeByTime();
  55. }
  56.  
  57. private void btn_Delete_Click(object sender, RoutedEventArgs e)
  58. {
  59. ViewModels.ContractsVMServices.Instances.Delete();
  60.  
  61. }
  62. }
  63. }

以上我们看到DataGrid的ItemsSource必须要先绑定一个源,这个源的名称跟我们ViewModel中的数据集合是一致的。修改TextBox中的数据的时候我们发现DataGrid的数据也跟着改变。以上就是实现V与M的一个解耦。

一篇文章,带你玩转MVVM,Dapper,AutoMapper的更多相关文章

  1. 两篇文章带你走入.NET Core 世界:Kestrel+Nginx+Supervisor 部署上云服务器(二)

    背景: 上一篇:两篇文章带你走入.NET Core 世界:CentOS+Kestrel+Ngnix 虚拟机先走一遍(一) 已经交待了背景,这篇就省下背景了,这是第二篇文章了,看完就木有下篇了. 直接进 ...

  2. 两篇文章带你走入.NET Core 世界:CentOS+Kestrel+Ngnix 虚拟机先走一遍(一)

    背景: 上一篇:ASP.Net Core on Linux (CentOS7)共享第三方依赖库部署 已经交待了背景,这篇就省下背景了. 折腾的过程分两步: 第一步是:本机跑虚拟机部署试一下: 第二步是 ...

  3. 三篇文章带你极速入门php(三)之php原生实现登陆注册

    看下成果 ps:纯天然h5,绝不添加任何添加剂(css)以及化学成分(js)(<( ̄ ﹌  ̄)我就是喜欢纯天然,不接受任何反驳) 关于本文 用原生的php和html做了一个登陆注册,大概是可以窥 ...

  4. 难道你还不知道Spring之事务的回滚和提交的原理吗,这篇文章带你走进源码级别的解读。

    上一篇文章讲解了获取事务,并通过获取的connection设置只读,隔离级别等:这篇文章讲事务剩下的回滚和提交. 事务的回滚处理 之前已经完成了目标方法运行前的事务准备工作.而这些准备工作的最大目的无 ...

  5. 三篇文章带你极速入门php(一)之语法

    本文适合阅读用户 有其他语言基础的童鞋 看完w3cschool语法教程来回顾一下的童鞋(传送门,想全面看一下php语法推荐这里) 毫无基础然而天资聪慧颇有慧根(不要左顾右看说的就是你,老夫这里有一本& ...

  6. 这篇文章带你彻底理解synchronized

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  7. MySQL查询为什么没走索引?这篇文章带你全面解析

    工作中,经常遇到这样的问题,我明明在MySQL表上面加了索引,为什么执行SQL查询的时候却没有用到索引? 同一条SQL有时候查询用到了索引,有时候却没用到索引,这是咋回事? 原因可能是索引失效了,失效 ...

  8. 三篇文章带你极速入门php(二)之迅速搭建php环境

    前言 今天讲一下php在windows,mac,linux上的集成环境搭建,目标是简单快速,环境这个事得对号入座,windows用phpstudy,mac用mamp,linux用lnmp一键安装,直接 ...

  9. 带你玩转Visual Studio——带你了解VC++各种类型的工程

    原文地址:http://blog.csdn.net/luoweifu/article/details/48816605 上一篇文章带你玩转Visual Studio——带你新建一个工程一文中提到新建一 ...

随机推荐

  1. 微信网站登录doem

    直接上代码 namespace CloudPrj.WeiXin {     public partial class index : System.Web.UI.Page     {         ...

  2. SPA项目开发之首页导航+左侧菜单

    Mock.js: 前后端分离之后,前端迫切需要一种机制,不再需要依赖后端接口开发,而mockjs就可以做到这一点 Mock.js是一个模拟数据的生成器,用来帮助前端调试开发.进行前后端的原型分离以及用 ...

  3. leaflet-webpack 入门开发系列二加载不同在线地图切换显示(附源码下载)

    前言 leaflet-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载地址 w ...

  4. Android集成极光推送

    要说学习极光推送,个人感觉官方文档就非常好啦,但是没法,人太懒啦,为了下次能够快速的将极光推送集成到项目中,故结合之前开发的项目和官方文档记录下简单的Android集成极光推送,在这之前,先上一张简单 ...

  5. [20191011]拆分rowid 2.txt

    [20191011]拆分rowid 2.txt --//有了链接http://blog.itpub.net/267265/viewspace-2659612/=>[20191011]bash任意 ...

  6. bayaim_java_入门到精通_听课笔记bayaim_20181120

    ------------------java_入门到精通_听课笔记bayaim_20181120--------------------------------- Java的三种技术架构: JAVAE ...

  7. mssql sqlserver 使用sql脚本获取字符串存在多少个网址(url地址)的方法分享

    摘要:下文讲述获取一个字符串中存在多少个网址的方法,如下实验环境:sql server 2008 R2  实现思路: 1.新建一个自定义函数,可将单个字符串拆分为含单个网址的数据表 2.采用outer ...

  8. gdb x命令使用方法

    x命令是直接查看指定地址为开头的内存里的内容 既然是要看,就分你想怎么看,和看多少 怎么看: d 按十进制格式显示 x 按十六进制格式显示 a 按十六进制格式显示 u 按十六进制格式显示无符号整型 o ...

  9. win7个性化不能换界面:此页面上的一个或多个设置已被系统管理员禁用,关机里的切换用户和锁定为灰色

    win7个性化不能换界面:此页面上的一个或多个设置已被系统管理员禁用,关机里的切换用户和锁定为灰色 找到注册表 cmd-regedit HKEY_CURRENT_USER\Software\Micro ...

  10. Python高级特性之:List Comprehensions、Generator、Dictionary and set ...

    今天帅气的易哥和大家分享的是Pyton的高级特性,希望大家能和我一起学习这门语言的魅力. Python高级特性之:List Comprehensions.Generator.Dictionary an ...