本篇文章主要分享本人对MVVM,三层架构与ORM框架的一些心得,如果各路大神有其他看法可以在评论区评论,欢迎各位提出指导意见。废话不多说直接上图上代码。
这个软件是以学生信息管理系统为例子,UI 层提供对学生信息的增加,删除,修改,查询功能,对应到底层对数据库实现增删改查的操作.下面我就以三层架构的思维从上往下一一描述。
1.UI 层
这里采用了MVVM 的设计架构,目的是让视图代码和数据模型降低耦合,实现属性,方法的绑定。在控件上我稍微使用了控件模板和样式。
(1)View 代码:
<Window x:Class="MVVMTest.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:MVVMTest.View"
xmlns:prism="http://prismlibrary.com/"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<Style x:Key="buttonStyle" TargetType="Button">
<Setter Property="Margin" Value="30,5,30,5" />
<Setter Property="FontSize" Value="20"/>
<Setter Property="Background" Value="White"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="10" BorderBrush="Blue" BorderThickness="1" Background="{TemplateBinding Background}">
<!--继承父控件的Content样式-->
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<!--触发器,可以根据需求给控件增加不同的触发方法-->
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="Aqua"></Setter>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" Value="ForestGreen"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="0.3*"/>
</Grid.ColumnDefinitions>
<DataGrid Name="dataGrid" Grid.Column="0" CanUserAddRows="False" AutoGenerateColumns="False" ItemsSource="{Binding studentModel.StudentList, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" CanUserSortColumns="False" IsReadOnly="True" >
<DataGrid.Columns>
<DataGridTextColumn Header="名字" Width="*" Binding="{Binding Name}"></DataGridTextColumn>
<DataGridTextColumn Header="年龄" Width="*" Binding="{Binding Year}"></DataGridTextColumn>
<DataGridTextColumn Header="地址" Width="*" Binding="{Binding Address}"></DataGridTextColumn>
<DataGridTextColumn Header="电话号码" Width="*" Binding="{Binding PhoneNumber}"></DataGridTextColumn>
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding selectItemChangedCommand}" CommandParameter="{Binding ElementName=dataGrid, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="学生信息">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal">
<Label Content="姓名:"/>
<TextBox Name="txbName" Margin="10,0,10,0" Width="150" Text="{Binding studentModel.Name, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, FallbackValue=Error}"/>
</StackPanel>
<StackPanel Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal">
<Label Content="年龄:"/>
<TextBox Name="txbYear" Margin="10,0,10,0" Width="150" Text="{Binding studentModel.Year, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, FallbackValue=Error}" />
</StackPanel>
<StackPanel Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal">
<Label Content="地址:"/>
<TextBox Name="txbAddress" Margin="10,0,10,0" Width="150" Text="{Binding studentModel.Address, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, FallbackValue=Error}" />
</StackPanel>
<StackPanel Grid.Row="3" HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal">
<Label Content="电话:"/>
<TextBox Name="txbPhone" Margin="10,0,10,0" Width="150" Text="{Binding studentModel.PhoneNumber, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, FallbackValue=Error}" />
</StackPanel>
</Grid>
</GroupBox>
<GroupBox Grid.Row="1" Header="数据操作">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Style="{StaticResource buttonStyle}" Command="{Binding buttonQueryCommand}" Content="查询"/>
<Button Grid.Row="1" Style="{StaticResource buttonStyle}" Command="{Binding buttonInsertCommand}" Content="新增"/>
<Button Grid.Row="2" Style="{StaticResource buttonStyle}" Command="{Binding buttonUpdataCommand}" Content="修改"/>
<Button Grid.Row="3" Style="{StaticResource buttonStyle}" Command="{Binding buttonDeleteCommand}" Content="删除"/>
</Grid>
</GroupBox>
</Grid>
</Grid>
</Window>
PS:在界面底下构造需要把Viewmodel 的数据绑定到前端中,可以组合引用
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;
using MVVMTest.ViewModel;
using MVVMTest.View;
namespace MVVMTest.View
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = MainViewModel.Instance;
}
}
}
(2)ViewModel 代码(这里跟事件的绑定我使用了Prism 框架,因为不想自己造轮子,其他的框架也是可以的,这块业务我使用了单例去设计)
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using MVVMTest.Model;
using Prism.Commands;
namespace MVVMTest.ViewModel
{
public class MainViewModel
{
static MainViewModel()
{
}
private MainViewModel()
{
IniViewModel();
BusinessLogic.BusinessLogic.Instance.SetRequest(studentModel.GetType());
}
public static MainViewModel Instance { get; } = new MainViewModel(); //这里使用单例去做业务处理
//控件命令列表
// Prism 框架下的事件绑定,可带参和不带参
private DelegateCommand _buttonQueryCommand;
public DelegateCommand buttonQueryCommand =>
_buttonQueryCommand ?? (_buttonQueryCommand = new DelegateCommand(ViewQuery));
private DelegateCommand _buttonInsertCommand;
public DelegateCommand buttonInsertCommand =>
_buttonInsertCommand ?? (_buttonInsertCommand = new DelegateCommand(ViewInsert));
private DelegateCommand _buttonUpdataCommand;
public DelegateCommand buttonUpdataCommand =>
_buttonUpdataCommand ?? (_buttonUpdataCommand = new DelegateCommand(ViewUpdata));
private DelegateCommand _buttonDeleteCommand;
public DelegateCommand buttonDeleteCommand =>
_buttonDeleteCommand ?? (_buttonDeleteCommand = new DelegateCommand(ViewDelete));
private DelegateCommand<object> _selectItemChangedCommand;
public DelegateCommand<object> selectItemChangedCommand =>
_selectItemChangedCommand ?? (_selectItemChangedCommand = new DelegateCommand<object>(GetModelObject));
public StudentModel studentModel { get; set; }
private StudentModel selectStudentModel = new StudentModel();
private void IniViewModel()
{
studentModel = new StudentModel();
}
private void ViewQuery()
{
studentModel.StudentList = BusinessLogic.BusinessLogic.Instance.QueryData();
}
private void ViewInsert()
{
BusinessLogic.BusinessLogic.Instance.InsertData(studentModel);
}
private void ViewUpdata()
{
selectStudentModel.Name = studentModel.Name;
selectStudentModel.Year = studentModel.Year;
selectStudentModel.Address = studentModel.Address;
selectStudentModel.PhoneNumber = studentModel.PhoneNumber;
BusinessLogic.BusinessLogic.Instance.UpdataData(selectStudentModel);
}
private void ViewDelete()
{
BusinessLogic.BusinessLogic.Instance.DeleteData(selectStudentModel);
}
private void GetModelObject(object obj)
{
if(obj!=null)
{
selectStudentModel = obj as StudentModel;
}
}
}
}
Model 层(Model 层主要用来创建数据模型 和 对数据库的映射,我这里使用了SqlSuger Code First 去实现数据库映射,同是界面对数据模型双向绑定 INotifyPropertyChanged 这个接口主要实现通知客户端数据刷新,System自带接口)
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SqlSugar;
namespace MVVMTest.Model
{
[SugarTable("StudentData")] //映射表名
public class StudentModel : INotifyPropertyChanged
{
private string _Name { get; set; }
[SugarColumn(IsNullable =true)]
public string Name
{
get { return _Name; }
set
{
_Name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
private int _Id { get; set; }
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)] //主键
public int Id { get { return _Id; } set { _Id = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Id")); } }
private string _Year { get; set; }
[SugarColumn(IsNullable = true)]
public string Year { get { return _Year; } set { _Year = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Year")); } }
private string _Address { get; set; }
[SugarColumn(IsNullable = true)]
public string Address { get { return _Address; } set { _Address = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Address")); } }
private string _PhoneNumber { get; set; }
[SugarColumn(IsNullable = true)]
public string PhoneNumber { get { return _PhoneNumber; } set { _PhoneNumber = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PhoneNumber")); } }
[SugarColumn(IsIgnore = true)]
public List<StudentModel> StudentList { get { return studentData; } set { studentData = value;PropertyChanged?.Invoke(this,new PropertyChangedEventArgs("StudentList")); } }
private List<StudentModel> studentData = new List<StudentModel>() { };
public event PropertyChangedEventHandler PropertyChanged;
}
}
2.业务逻辑层(这里我也用了单例,并且保证线程安全)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using MVVMTest.DataAccess.DataBase;
using MVVMTest.Common;
using MVVMTest.Model;
using System.Data;
using System.Collections;
using System.Reflection;
namespace MVVMTest.BusinessLogic
{
class BusinessLogic
{
private BusinessLogic()
{
}
private static readonly object obj=new object();
private volatile static BusinessLogic instance = null;
SqlSugerHelper sqlSugerHelper = new SqlSugerHelper();
public static BusinessLogic Instance
{
get
{
if(instance==null)
{
lock(obj)
{
if(instance == null)
instance = new BusinessLogic();
}
}
return instance;
}
}
public void SetRequest(Type dataModel)
{
sqlSugerHelper.IniDB(GlobelEnum.DatabaseType.SqlServer, dataModel);
}
public void InsertData(StudentModel studentModel)
{
sqlSugerHelper.Insert(studentModel);
}
public List<StudentModel> QueryData()
{
List<StudentModel> dataTable = new List<StudentModel>();
dataTable = sqlSugerHelper.Query();
return dataTable;
}
public void DeleteData(StudentModel studentModel)
{
sqlSugerHelper.Delete(studentModel);
}
public void UpdataData(StudentModel studentModel)
{
sqlSugerHelper.Updata(studentModel);
}
private DataTable ListToDt<T>(IEnumerable<T> collection)
{
var props = typeof(T).GetProperties();
var dt = new DataTable();
dt.Columns.AddRange(props.Select(p => new
DataColumn(p.Name, p.PropertyType)).ToArray());
if (collection.Count() > 0)
{
for (int i = 0; i < collection.Count(); i++)
{
ArrayList tempList = new ArrayList();
foreach (PropertyInfo pi in props)
{
object obj = pi.GetValue(collection.ElementAt(i), null);
tempList.Add(obj);
}
object[] array = tempList.ToArray();
dt.LoadDataRow(array, true);
}
}
return dt;
}
}
}
3.数据访问层(这里针对不同的数据库我使用了工厂模式去实现,由用户使用的时候再去选择实例哪一个数据库,这里的代码可能处理的不太好,欢迎大家发表不同意见)
(1)数据库基类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using SqlSugar;
namespace MVVMTest.DataAccess.DataBase
{
public abstract class SqlBase
{
public abstract SqlSugarClient IniDb(Type DataModel, string DataBaseName, string ServerName) ;
}
}
(2)数据库子类(SqlSever)(这里设计SqlSuger Code First 的代码)
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMTest.DataAccess.DataBase
{
public class SqlSever : SqlBase
{
public override SqlSugarClient IniDb(Type DataModel, string DataBaseName, string ServerName )
{
SqlSugarClient db = new SqlSugarClient(new ConnectionConfig()
{
ConnectionString =string.Format("server={0};database={1};uid=数据库账户;pwd=密码, ServerName, DataBaseName),
DbType = SqlSugar.DbType.SqlServer,//设置数据库类型
IsAutoCloseConnection = true,//自动释放数据务,如果存在事务,在事务结束后释放
InitKeyType = InitKeyType.Attribute //从实体特性中读取主键自增列信息
});
db.Aop.OnLogExecuting = (sql, pars) =>
{
Console.WriteLine(sql + "\r\n" +
db.Utilities.SerializeObject(pars.ToString())); //打印数据库日志
Console.WriteLine();
};
//创建数据库 如果该库不存在,则进行创建。(这里创建的是名字为 Student 数据库)
db.DbMaintenance.CreateDatabase();
//初始化数据表,如果没有则创建
db.CodeFirst.InitTables(DataModel);
return db;
}
}
}
(3)数据库子类(Sqlite)
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMTest.DataAccess.DataBase
{
public class Sqlite : SqlBase
{
public override SqlSugarClient IniDb(Type DataModel, string DataBaseName, string ServerName)
{
SqlSugarClient db = new SqlSugarClient(new ConnectionConfig()
{
ConnectionString = string.Format("server={0};database={1};uid=数据库账户;pwd=密码", ServerName, DataBaseName),
DbType = SqlSugar.DbType.Sqlite,//设置数据库类型
IsAutoCloseConnection = true,//自动释放数据务,如果存在事务,在事务结束后释放
InitKeyType = InitKeyType.Attribute //从实体特性中读取主键自增列信息
});
db.Aop.OnLogExecuting = (sql, pars) =>
{
Console.WriteLine(sql + "\r\n" +
db.Utilities.SerializeObject(pars.ToString()));
Console.WriteLine();
};
//创建数据库 如果该库不存在,则进行创建。(这里创建的是名字为 Student 数据库)
db.DbMaintenance.CreateDatabase();
//初始化数据表,如果没有则创建
db.CodeFirst.InitTables(DataModel);
return db;
}
}
}
(4) 数据库工厂
using MVVMTest.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMTest.DataAccess.DataBase
{
public class DataBaseFactory
{
private DataBaseFactory()
{
}
static DataBaseFactory() { }
public static DataBaseFactory Instance { get; } = new DataBaseFactory();
public SqlBase Create(GlobelEnum.DatabaseType databaseType)
{
SqlBase dataBaseFactory = null;
switch(databaseType)
{
case GlobelEnum.DatabaseType.MySql:
break;
case GlobelEnum.DatabaseType.SqlServer:
dataBaseFactory = new SqlSever();
break;
}
return dataBaseFactory;
}
}
}
(5)数据库访问接口
using MVVMTest.Common;
using MVVMTest.Model;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MVVMTest.Model;
using System.Data;
namespace MVVMTest.DataAccess.DataBase
{
public class SqlSugerHelper
{
SqlBase sqlBase;
SqlSugarClient sqlSugarClient;
public void IniDB(GlobelEnum.DatabaseType databaseType, Type dataModel)
{
sqlBase = DataBaseFactory.Instance.Create(databaseType);
sqlSugarClient = sqlBase.IniDb(dataModel, "Student", "服务器名称");
}
public void Insert(StudentModel studentModel)
{
sqlSugarClient.Insertable(studentModel).ExecuteCommand();
//var a = sqlSugarClient.Ado.SqlQuery<StudentModel>("select * from StudentData"); // 数据库语句
}
public List<StudentModel> Query()
{
List<StudentModel> studentModels = new List<StudentModel>();
studentModels=sqlSugarClient.Queryable<StudentModel>().ToList();
sqlSugarClient.Close();
return studentModels;
}
public void Delete(StudentModel studentModel)
{
sqlSugarClient.Deleteable(studentModel).ExecuteCommand();
}
public void Updata(StudentModel studentModel)
{
sqlSugarClient.Updateable(studentModel).ExecuteCommand();
}
}
}
公共类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMTest.Common
{
public static class GlobelEnum
{
public enum SqlCommand
{
Invalid=-1,
IniDB=0,
Query=1,
Insert=2,
Updata=3,
Delete=4,
}
public enum DatabaseType
{
Invalid=0,
Sqlite=1,
SqlServer=2,
MySql=3,
}
public enum ORMType
{
Invalid = 0,
CodeFirst=1,
DBFirst=2,
ModelFirst=3,
}
}
}
公共类中有一些是不需要的枚举,可以不用完全借鉴
欢迎各位大神指导评论