目录
0. 来点鸡汤
时不时看看自己以前写的博客,感触很多。已经快一年多没有认真写博客了,今天重新开张,希望以此为契机,重拾生活的信心。
去年研究生毕业,去了北京,年薪拿到了30万。但只在山巅呆了四个月,便草草结束,离开北京各中原由无从说起,家庭和事业那时候只能选一个。虽然选了家庭,但现在想起北京的那份工作,也觉得可惜。离开北京之后,“安安心心”拿5000的月薪又快一年了,岗位也从原来的算法工程师变成程序员,这就是上帝给你开了个窗户,你刚看到一丝希望,谁知上帝开窗就给你一个大逼兜,然后关上窗户,留下你在风中凌乱。刚去北京时,我以为命运的齿轮开始转动,我真正的人生正式开始,原来只是去看了一遭那水中花、镜中月,留存脑海中的北京梦,与现在的满地鸡毛,讽刺啊。
哈哈哈,生活还要继续呢!原来京东买东西,现在拼多多、咸鱼也能凑合,玩算法换成写代码就当给自己夯实基础,领导原来是博士、硕士,现在的领导清一色中专,这不工作上更容易忽悠领导了吗?这样想,好多了。。
听我叨逼叨那么多,哈哈,那我顺便说点关于C#的事情吧,也当做我成长的一个记录。这份教程不会每一步都去截图,如果你是小白,建议花个10分钟快速入门一下C#的基本概念,这个链接: C#菜鸟教程 带你快速入门。我写的虽然是WPF入门教程,但是C#遇到的问题我都会记录下来,直到把整个大楼给造好。
1. 概念
C# 怎么读? 读C井?读c星?哈哈
正确读音 C Sharp 音标: [ʃɑːrp]
1.1 C#能做什么
上位机软件、桌面显示软件、unity 3D游戏、网页开发等
1.2 为什么要选择C#,而不是QT或者其它?
(1)C# 简单易上手。qt 基本就C++的语法,用起来很复杂。
别扯什么运行速度,内存那些有的没的,那些东西全是扯犊子,对于新手或者绝大多数人,那些东西可能写一辈子代码也不用考虑,现在的计算机不缺算力和存储空间。主要精力应该是保证功能的实现和稳定运行。
(2)C# 是微软创造出来的,背靠宇宙第一强编辑器 visual studio,对于代码的调试,兼容,有着无可比拟的优势。
我举个例子,每台Windows电脑都有个事件查看器,它记录了电脑的各种异常事件。我们知道,写代码的时间是远远没有调试的时间长的,而用C#写的程序,通过Windows自带的事件查看器就能定位到异常代码是第几行,你就说这点,选不选C#。
(3)学会C# 会的是一类东西。
比如你是用C#写桌面应用程序(winform、WPF),你还可以用C#写网页 (asp.net),现在火热的Unity3D脚本也是通过C#来完成的,只要微软不跨,你说为啥不选一劳永逸的语言。
老子就不听傻逼博主的意见,我就要学qt。
宝啊,你看看我写的其他文章,也是鬼话连篇,但是我写博客没有糊弄各位宝,no copy,no paste.(是不是不知道啥意思,哈哈,快去百度翻译一下再和我犟)。如果你还是不选择C#,我只能画个圈诅咒你 —你写的代码如果有bug,永远也找不到。
1.3 winform 和 wpf有什么区别
C#有控制台应用程序Console,也有桌面应用程序(图形界面),现在主要就是用来展示数据的。
c#有两种方式写桌面应用程序:WPF、winform。我们来看看它们有什么不同。
- winform老,wpf新;
- winform窗体的控件属性是在C#里实现的,WPF则是在XAML里面实现的
- winform修改控件复杂,wpf简单
- winform入门简单,wpf入门难
宝啊,你读到此处想说什么?
什么傻逼博主,说了好像又啥都没说,都是些什么鬼啊?
我就想和你说,winform过时了,要学WPF。
1.4 .net Framework 和 .net Core联系
讲个小插曲,有一次其他部门的一个同事台上发言,原话:“我们这个程序是用 dot net core 开发的”,听到这句话时,我有点懵逼,什么人这是,你就说用C#开发的不就完了么,还tmd左一句dot net core,右一句dot net core,啥也不是。
看上面这张图片,我选的都是WPF,但是它们的架构不一样。
- .net Framework老, .net Core新
- .net Framework只针对windows平台,但包含了Windows平台的所有特性, .net Core 支持多个平台,但没有前者全面.如果要用xp系统,则使用framework是更好的选择
顺便说一嘴:咱可不是喜欢背后说人坏话,我批评甚至是鄙视我那个说dot net core 的同事,原因是他不写任何代码,也不会写,却总是装逼,咱讨厌这样的人。
1.5 WPF各个组成部分
分为XAML文件和cs文件,XAML文件用来处理界面,cs文件处理后台逻辑
App.xaml 指定系统启动界面,资源,引入的程序集
=======现在是 北京时间2023年9月27日 23:13,小傲娇的博主困了,不想写了
2. xaml
wpf有两部分构成,一部分是界面(前端设计),负责界面的设计和数据展示。另一部分是程序逻辑(后端),负责业务流程和数据处理。两部分相互独立,装逼的说法叫前后端分离。前端和后端会存在数据交互,可以通过事件或者绑定等方法来实现。
2.1 xaml中的对象和属性
=======现在是 北京时间2023年10月1日 20:41,小傲娇的博主今天没事,接着写。
xaml是xml的扩展,很多用法和想xml是一致的,如果你学过html,那应该会很快入门xaml,这东西只要入门了,写桌面应用程序布局时很爽,你用了这个,基本不会再用回winform了。
-
- 对象元素
这里的对象和面向对象编程的概念基本一致。面向对象编程的对象是对一类具有相同属性的物体进行抽象,比如有个Dog类、CAT类,这里的对象是逻辑层面上的。
xaml 中也有对象,比如按钮utton、文本框TextBox等都是对象,这里的对象是布局上的概念,更多是文法层面,不是逻辑上的概念。
在WPF中,xmal代码也称作前端代码(控件),而以.cs结尾的代码叫做 “C#代码”(也叫后端代码或者代码隐藏)。
<!--第一种写法--> <Button>按钮1</Button> <!--第二种写法--> <Button Content="按钮2"/>
- 对象元素
-
- 属性
在C#中(其他编程语言也一样),一个类中往往有各种属性,比如学生类中有姓名属性、年纪属性等等。在xaml中,这个概念有点类似,比如有个按钮控件(按钮类),那它也有自己的属性,比如行高、行宽、背景颜色等等。下面这个示例,Grid.Row、Grid.Column都是属性。
<Grid Grid.Row="2" Grid.Column="0" ShowGridLines="True">
- 属性
-
- 属性元素
属性元素是对象中的属性的属性,比如Button是一个对象,background是一个属性,对属性再设置就叫属性元素: SolidColorBrush
<Button> <Button.Background > <SolidColorBrush Color="red"></SolidColorBrush> </Button.Background> </Button>
- 属性元素
2.2 xaml页面布局
2.2.1 层级概念
- xaml设计是按照行和列的概念设计的;
- xaml是树形结构,在使用前先对Grid进行设计(几行几列);
- 在每个层级下面对界面进行设计
可能看了上面的图片也觉得一头雾水,我们现在从网页上随便截图一张,结合WPF看看到底什么是层级结构
我们把这个界面先整体分成三个区域, 即3行1列
第1行第1列 所有的图标依次用stackPanel放置控件就可以了
在第2行第1列 里面进一步切割,可在分成 2行1列
在第3行第1列 里面进一步切割,可在分成 1行3列
这样一个界面就被切割好了, 理解上面这个例子,应该就知道WPF层级的概念了
2.2.2 使用 Grid 定义行和列
根据上面层级的概念,将界面划分区域,接下来就可以用< Grid >来实现了. 下面的代码定义2行2列的布局
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
我们还可以在第2行第2列里再布置2行2列,代码实现
如下
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
</Grid>
2.2.3 设置行和列
-
绝对值
-
按比例
-
auto
<Grid ShowGridLines="True"> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="80"/> <RowDefinition Height="2*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Button Grid.Row="0" Grid.Column="0" Width="40" Height="40"/> <Button Grid.Row="1" Grid.Column="2"/> </Grid>
2.3 xaml样式
2.3.1 方法一:不给样式命名
直接在wpf控件前面写某类控件的样式,该方法不给样式起名字,后面使用该类控件时不需要引用,默认会使用该类样式。如下面代码,当我创建一个按钮是就会使用 <Window.Resources>里面的样式。
<Window x:Class="Wpf_test.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:Wpf_test"
mc:Ignorable="d"
Title="MainWindow" Height="500" Width="700">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Background" Value="#FF20B9E6"/>
<Setter Property="Height" Value="50"/>
<Setter Property="Width" Value="100"/>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Content="按钮"/>
</Grid>
</Window>
=======现在是 北京时间2023年10月1日 22:38,小傲娇的博主累了,不想写了
2.3.2 方法二:给样式命名
<Window.Resources>
<Style x:Key="login" TargetType="Button">
<Setter Property="Background" Value="#FF06C5F0"/>
<Setter Property="Height" Value="50"/>
<Setter Property="Width" Value="100"/>
</Style>
<Style x:Key="quit" TargetType="Button">
<Setter Property="Background" Value="#FFF1F1F1"/>
<Setter Property="Height" Value="50"/>
<Setter Property="Width" Value="100"/>
</Style>
</Window.Resources>
2.3.3 给样式命名同时继承基础样式
<Window.Resources>
<!--基础样式-->
<Style TargetType="Button">
<Setter Property="FontSize" Value="20"/>
</Style>
<Style x:Key="login" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="#FF06C5F0"/>
</Style>
<!--继承基础样式 添加引用-->
<Style x:Key="quit" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="#FFF1F1F1"/>
</Style>
</Window.Resources>
2.4 在资源字典定义样式
在项目管理文件中添加一个资源字典Dictionary,对不同的控件进行样式设计. 在App.xaml中添加一个全局的资源字典, 将Dictionary的文件路径添加进去
2.4.1 添加资源字典
//在项目管理文件中添加一个资源字典Dictionary
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1">
<!--新建一个全局资源字典-->
<!--基础样式-->
<Style TargetType="Button">
<Setter Property="FontSize" Value="20"/>
</Style>
<!--继承基础样式 添加引用-->
<Style x:Key="login" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="#FF06C5F0"/>
</Style>
<!--继承基础样式 添加引用-->
<Style x:Key="quit" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="#FFF1F1F1"/>
</Style>
</ResourceDictionary>
2.4.2 全局引用资源字典
<Application x:Class="WpfApp1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
StartupUri="Window1.xaml">
<Application.Resources>
<!--添加一个全局的资源资源字典-->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/BaseButtonStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
2.5 控件模板重写
当你创建一个控件时,这个控件有自己的背景、颜色,这都是系统自定义好的,但是这并不是我们需要的,我们不可能每一次创建时都去修改,所以需要对控件模版改造,这个过程就就叫做控件模版重写。
<Grid>
<Button Content="自定义按钮" Background="#FF00805D" Width="120" Height="100" BorderBrush="Black" BorderThickness="4">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border Name="bbb" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="15">
<TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bbb" Property="Background" Value="red"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="bbb" Property="Background" Value="#FF10F3CA"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
3. C# 代码语法规则
3.1 变量 、属性、字段分别是什么?
这个概念特别重要,搞不懂不行
类内部的私有变量即为字段,如代码中的变量 a;
属性向外暴露接口(公有部分),使外部能够通过属性访问内部字段, 属性本身不保存数据, 对属性操作实际是对属性对应字段操作; 如代码中对属性b操作,其实是对a操作;
/// <summary>
/// 定义一个变量(字段) a ,同时初始化
/// </summary>
private int a = 1;
/// <summary>
/// 定义属性 b
/// </summary>
public int b
{
get {
return a; }
set {
a = value; }
}
3.2 属性、变量、字段
在.NET中,属性(Properties)、字段(Fields)和变量(Variables)是编程中经常使用的术语,它们各自有不同的含义和用途。以下是这些术语的基本定义和区别:
变量(Variables):
- 定义:变量是用来存储数据的标识符,它可以根据程序的需要存储不同类型的数据。
- 访问:通过变量名直接访问。
- 作用域:根据声明的位置(如方法内、类内、命名空间内等),变量的作用域会有所不同。
- 示例:
int number = 10;
,这里number
是一个整型变量。
字段(Fields):
- 定义:字段是类的成员变量,它隶属于类而不是方法。字段通常是私有的,并且可以通过类的属性、方法或其他成员来访问。
- 访问:通常通过类的实例访问,除非它们是静态的,则可以通过类名直接访问。
- 作用域:字段的作用域通常是整个类。
- 示例:
public class MyClass { private int myField; // 这是一个私有字段 public int MyProperty // 这是一个属性,下面会详细介绍 { get { return myField; } set { myField = value; } } }
属性(Properties):
- 定义:属性是类的成员,它提供了一种灵活的方式来读取、写入或计算私有字段的值。属性通常包含get和set访问器。
- 访问:通过类的实例访问,就像访问字段一样,但实际上是在调用方法。
- 作用域:属性的作用域通常是整个类,但可以通过get和set访问器进行更精细的控制。
- 示例:继续上面的
MyClass
例子,MyProperty
是一个属性,它有get和set访问器,允许外部代码读取和修改myField
的值。public int MyProperty // 属性定义 { get { return myField; } // get访问器,用于读取属性值 set { myField = value; } // set访问器,用于设置属性值 }
总结区别:
- 变量是最基本的存储单元,而字段是类的变量成员。
- 属性提供了一种更安全、更灵活的方式来访问类的字段。它们允许在读取或写入字段值之前执行额外的逻辑(例如验证)。
- 通常建议将字段标记为private,并通过公共属性来访问它们,以实现封装和数据隐藏。这样可以确保对象状态的完整性和安全性。
-
易错点
在C#中, 只能在类内定义变量和属性,同时允许对改变量或者属性进行初始化, 但不允许一个变量直接引用另外一个变量,如下所示,这是新人经常犯的一个错误。
3.3 set{}、get{}用法
在类Test中设置一个私有变量name,同时设置一个公有变量,通过公有变量name对私有变量NAME操作
public class Test
{
/// <summary>
/// 字段:一般私有,不对外开放, 首字母一般小写
/// </summary>
private string name;
/// <summary>
/// 属性:一般为共有,作为外部访问对应字段的一个接口, 首字母一般大写
/// </summary>
public string NAME
{
get {
return name; } //通过NAME返回name的值
set
{
if (value == "keson")
{
Console.WriteLine("hello,keson!");
}
else
{
name = value; //通过NAME设置name的值
Console.WriteLine("that is not keson,is "+ name);
}
}
}
}
class Program
{
static void Main()
{
Test test = new Test();
test.NAME = "孙悟空";
}
}
3.4 App.config用法
App.config文件是系统默认的配置文件, 使用时需要添加引用 System.Configuration.dll, 该配置文件用于修改数据库连接字符串/窗口日志的信息.
App.config代码:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<appSettings>
<add key="keyName" value="valueName"/>
<add key="keyName2" value="valueName2"/>
<add key="keyName3" value="valueName3"/>
</appSettings>
</configuration>
-
C# 代码
拿到配置文件里的内容
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); string settingValue = System.Configuration.ConfigurationManager.AppSettings["keyName"]; } }
3.5 将一个类拆开写在多处
calss1.cs和class2.cs同属于一个类,c#允许一个类拆开写,这样防止写在一处,不方便阅读。
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
public partial class Window1 : Window
{
public Window1()
{
fun();
}
}
4. 数据绑定
4.1 原理
通常是指将目标源(具有依赖属性的对象)绑定到 目标数据上(通常是控件)
数据绑定分为4种:
4.2 xaml 实现数据绑定
案例一
绑定源:代码隐藏 绑定目标: TextBox
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//设置窗体上下文对象 这里设置的是MyClass
DataContext = new MyClass();
}
}
public class MyClass
{
public int hight {
get; set; } = 100;
public int width {
get; set; } = 101;
}
<Grid>
<StackPanel>
<WrapPanel>
<TextBlock Text="窗口的标题为:"/>
<TextBox Text="{Binding Path=Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</WrapPanel>
<WrapPanel>
<TextBlock Text="窗口的高度为:"/>
<!--上面MyClass里的hight-->
<TextBox Text="{Binding hight}" Width="50" />
</WrapPanel>
<WrapPanel>
<TextBlock Text="窗口的宽度为:"/>
<TextBox Text="{Binding width}" Width="50" />
</WrapPanel>
</StackPanel>
</Grid>
案例二
绑定源:TextBox 绑定目标: TextBox
<TextBox Grid.Row="1" Name="textBox_receiveData" Text={BindingPath=Text,ElementName=tbx2 }">
</TextBox>
4.3 C#代码实现数据绑定
public MainWindow()
{
InitializeComponent();
BindingData();
}
private void BindingData()
{
//1 创建绑定对象
var binding = new Binding("Text");
//2 设置绑定源
binding.Source = this.keson;
//3 设置绑定目标
keson_copy.SetBinding(TextBlock.TextProperty, binding);
}
<StackPanel>
<WrapPanel>
<TextBox Name="keson" Text="永远滴神"/>
</WrapPanel>
<WrapPanel>
<TextBlock Name="keson_copy" />
</WrapPanel>
</StackPanel>
namespace WpfApp1
{
/// <summary>
/// Window1.xaml 的交互逻辑
/// </summary>
public partial class Window1 : Window
{
LoginModel loginModel = new LoginModel();
public Window1()
{
InitializeComponent();
//数据绑定
this.DataContext = loginModel;
loginModel.user_name = "keson";
}
private void Button_Click(object sender, RoutedEventArgs e)
{
图书馆主界面 lib = new 图书馆主界面();
if(loginModel.user_name == "张三") //这里的textbox_userName就是xaml里的x:Name
{
lib.Show();
}
else
{
MessageBox.Show("账号错误");
loginModel.user_name = ""; //账号不是"张三"时,textbox里的内容会清空
}
}
}
public class LoginModel: INotifyPropertyChanged
{
private string _user_name;
public string user_name
{
get {
return _user_name; }
set
{
_user_name = value;
RaisePropertyChanged("user_name");
}
}
public event PropertyChangedEventHandler PropertyChanged; //1 申明一个事件
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));//3 事件绑定一个方法
}
}
}
<Grid Grid.Row="1" Grid.Column="0">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="账号:" FontSize="40" Foreground="#FF000605" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Text="{Binding user_name}" x:Name="textbox_userName" Width="300" Background="White" Margin="10,10" FontSize="40" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
</Grid>
4.4 PropertyChanged实现数据绑定
(1)原理
(2) 界面
(3) xaml
<Window x:Class="WpfApp2.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:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" MinHeight="40">
<StackPanel >
<TextBox Name="tbx" Height="50" Margin="5" Text="TextBox1"/>
<TextBox Name="tbx2" Height="50" Margin="5" Text="TextBox2" TextChanged="Tbx2_TextChanged"/>
</StackPanel>
</Window>
(4) c#
using System;
using System.Collections.Generic;
using System.ComponentModel;
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 WpfApp2
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
Student stu;
public MainWindow()
{
InitializeComponent();
stu = new Student();
// 准备Binding
Binding binding = new Binding();
binding.Source = stu;
binding.Path = new PropertyPath("Name");
//使用Binding连接数据源于Binding目标
BindingOperations.SetBinding(tbx, TextBox.TextProperty, binding);
}
private void Tbx2_TextChanged(object sender, TextChangedEventArgs e)
{
stu.Name = tbx2.Text;
}
}
class Student : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name
{
get {
return name; }
set
{
name = value;
if(PropertyChanged != null)
{
PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Name"));
}
}
}
}
}
5 c#中的委托
5.1 如何理解委托?
这个名词困惑了了我好久,应该对比C++中的函数指针(一个指向函数的指针),理解了函数指针就理解委托了.
函数指针的就是一个指针变量指向函数,例子如下,定义了一个*pf的函数指针,入参是两个int,返回值也是int,将pf的地址指向max函数,这样pf也能使用max函数.
int max(int a,int b){
return a>b?a;b;
}
int (*pf)(int, int);
pf = max;
不同的是委托可以挂载多个方法。
委托是一个容器,这个容器可以挂载不同的方法。先简单说如何使用:定义一个委托,并给改委托挂载方法,执行委托。
具体步骤:
- 声明委托;
- 创建委托实例;
- 委托绑定方法;
- 调用委托
public delegate int Calculate(int a, int b); //委托相当于函数指针
/// <summary>
/// 为委托创建一个加法计算方法
/// </summary>
/// <param name="name"></param>
public static int addMethod(int a, int b)
{
return a + b;
}
/// <summary>
/// 为委托创建一个乘法计算方法
/// </summary>
/// <param name="name"></param>
public static int MutilMethod(int a, int b)
{
return a * b;
}
static void Main(string[] args)
{
/// <summary>
/// 将加法计算方法赋值给委托
/// </summary>
Calculate cal = addMethod;
cal += MutilMethod;
//调用委托(依次调用)
int a = cal(22, 2);
int b = cal(2, 9);
int c = cal(22, 2);
int d = cal(2, 9);
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
Console.WriteLine(d);
运行结果:
44
18
44
18
请按任意键继续. . .
5.2 系统自带的两种委托 Action<> 和Func<>
Action<> 用于挂载无返回值的方法
Func<> 用于挂载有返回值的方法
static void Main(string[] args)
{
delegateFun DF = F1;
DF();
//内置的委托
//1 指向无返回值方法
Action a = F1;
a();
//2 指向有返回值方法
Func<int, int, int> func = Paremeters_fun;
Console.WriteLine("2 指向有返回值方法,返回值为:" + func(2, 6));
//3 指向无返回值无入参 匿名方法
Action a2 = delegate ()
{
Console.WriteLine("3 指向无返回值无入参 匿名方法");
};
a2();
//4 指向无返回值有入参 匿名方法
Action<int,string> a3 = delegate (int i,string s)
{
Console.WriteLine($"4 指向无返回值有入参 匿名方法: i = {
i},s = {
s}");
};
a3(1,"keson");
//5 指向有返回值方法 匿名方法
Func<int, int, int> func2 = delegate (int i, int j)
{
return i + j;
};
Console.WriteLine("5 指向有返回值方法 匿名方法: " + func2(50, 6));
//6 指向有返回值lamda表达式
Func<int, int, int> func3 =(int i, int j)=>
{
return i + j;
};
Console.WriteLine("6 指向有返回值lamda表达式:" + func3(50, 6));
//7 指向有返回值lamda表达式(省略入参类型)
Func<int, int, int> func4 = (i,j) =>
{
return i + j;
};
Console.WriteLine("7 指向有返回值lamda表达式(省略入参类型):" + func3(50, 6));
}
5.3 简写委托的形式
委托经常伴随着Lambda表达式, Lambda表达式变化很多,有时候看到别人用的时候经常觉得莫名奇妙,我们从基本的开始,看看他们是一步步被简化的.
static void Main(string[] args)
{
int[] nums = {
11, 52, 69, 33, 54, 2, 9, 23, 66, 45 };
//IEnumerable<int> result = nums.Where(a=>a > 10);
//演变顺序
// 1 先写一个匿名方法
Action<int> action = delegate (int i) {
Console.WriteLine(i); };
// Action<int> action2 = delegate (i) { Console.WriteLine(i); }; //匿名方法数据类型不能省略
// Action<int> action3 = delegate (int i) Console.WriteLine(i); ; //匿名方法大括号不能省略
// 2 换成lamada表达式
Action<int> action4 = (int i) => {
Console.WriteLine(i); }; //完整写法
Action<int> action5 = (i) => {
Console.WriteLine(i); }; //数据类型省略
Action<int> action6 = (i) => Console.WriteLine(i); ; //大括号省略
Action<int> action7 = (i) => Console.WriteLine(i); //分号省略
Action<int> action8 = i => Console.WriteLine(i); //入参只有一个时,入参的小括号省略
//3 如果是有返回值
Func<int, bool> func1 = delegate (int i) //先写个匿名方法
{
if (i > 0)
{
return true; }
else
{
return false;
}
};
Func<int, bool> func2 = (int i) => //换成lamada表达式
{
if (i > 0)
{
return true; }
else
{
return false;
}
};
Func<int, bool> func3 = (i) => //数据类型省略
{
if (i > 0)
{
return true; }
else
{
return false;
}
};
Func<int, bool> func4 = (i) => //优化返回代码
{
return i > 0;
};
Func<int, bool> func5 = (i) => //返回语句只有一条语句,省略大括号和return关键字
i > 0;
Func<int, bool> func6 = i => //入参只有一个,省略入参的括号
i > 0;
}
C#中的事件
- 在类内声明委托
- 在类内声明事件
namespace ConsoleApp1
{
/// <summary>
/// 事件发布者
/// </summary>
public class GreetingManager
{
/// <summary>
/// 声明一个委托变量
/// </summary>
/// <param name="name"></param>
public delegate void display(string name);
/// <summary>
/// 声明一个事件
/// </summary>
public event display display_event; //相当于对委托类型的变量进行封装
/// <summary>
/// 创建一个处理方法
/// </summary>
/// <param name="name"></param>
public void show(string name)
{
if (display_event != null)
{
display_event(name);
}
}
}
/// <summary>
/// 订阅者
/// </summary>
public class GreetWays
{
public void EnglishGreeting(string name)
{
Console.WriteLine("good moring, " + name);
}
public void ChineseGreeting(string name)
{
Console.WriteLine("早上好, " + name);
}
}
class Program
{
static void Main()
{
GreetingManager greetingManager = new GreetingManager();
GreetWays greetWays = new GreetWays();
greetingManager.display_event += greetWays.EnglishGreeting; //事件绑定方法
greetingManager.display_event += greetWays.ChineseGreeting;
greetingManager.show("keson"); //调用了show就触发了display_event事件
Console.ReadKey();
}
}
}
static void Main(string[] args)
{
int[] nums = {
11, 52, 69, 33, 54, 2, 9, 23, 66, 45 };
//IEnumerable<int> result = nums.Where(a=>a > 10);
//演变顺序
// 1 先写一个匿名方法
Action<int> action = delegate (int i) {
Console.WriteLine(i); };
// Action<int> action2 = delegate (i) { Console.WriteLine(i); }; //匿名方法数据类型不能省略
// Action<int> action3 = delegate (int i) Console.WriteLine(i); ; //匿名方法大括号不能省略
// 2 换成lamada表达式
Action<int> action4 = (int i) => {
Console.WriteLine(i); }; //完整写法
Action<int> action5 = (i) => {
Console.WriteLine(i); }; //数据类型省略
Action<int> action6 = (i) => Console.WriteLine(i); ; //大括号省略
Action<int> action7 = (i) => Console.WriteLine(i); //分号省略
Action<int> action8 = i => Console.WriteLine(i); //入参只有一个时,入参的小括号省略
//3 如果是有返回值
Func<int, bool> func1 = delegate (int i) //先写个匿名方法
{
if (i > 0)
{
return true; }
else
{
return false;
}
};
Func<int, bool> func2 = (int i) => //换成lamada表达式
{
if (i > 0)
{
return true; }
else
{
return false;
}
};
Func<int, bool> func3 = (i) => //数据类型省略
{
if (i > 0)
{
return true; }
else
{
return false;
}
};
Func<int, bool> func4 = (i) => //优化返回代码
{
return i > 0;
};
Func<int, bool> func5 = (i) => //返回语句只有一条语句,省略大括号和return关键字
i > 0;
Func<int, bool> func6 = i => //入参只有一个,省略入参的括号
i > 0;
}
```
# C#不同界面之间互相操作控件
- 主界面可以操作子界面,子界面不可以操作主界面
```csharp
//主界面窗口
public partial class Form1 : Form
{
public delegate void Del_main();
static public Form1 form1 = new Form1();
public Form1()
{
form1 = this;
InitializeComponent();
}
private void button_ok_Click(object sender, EventArgs e)
{
Form2.form2.Show();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Form2.form2.Close();
Environment.Exit(0);
}
}
public partial class Form2 : Form
{
static public Form2 form2 = new Form2();
public Form2()
{
form2 = this;
InitializeComponent();
}
private void form2_button_Click(object sender, EventArgs e)
{
Update_textBox_rcvCanData("测试");
}
private void Update_textBox_rcvCanData(string text)
{
if (Form1.form1.textBox.InvokeRequired)
{
//确保是在UI线程调用控件
Form1.form1.textBox.Invoke(new Action<string>(Update_textBox_rcvCanData), text);
return;
}
Form1.form1.textBox.AppendText(text);
}
}
Lambda表达式
在.NET Core中,Lambda表达式是一种简洁的代码块表示形式,通常用于创建匿名方法。Lambda表达式可以包含表达式和语句,并且可用于创建委托或表达式树类型。Lambda表达式在LINQ查询、集合操作、排序、事件处理等方面非常有用。
Lambda表达式的基本语法如下:
(parameter list) => expression
其中,(parameter list)
是参数列表,expression
是Lambda主体表达式。
以下是一些在.NET Core中使用Lambda表达式的常见用法示例:
List<int> numbers = new List<int> {
1, 2, 3, 4, 5 }; //数据源
- 作为参数传递给方法:
List<int> numbers = new List<int> {
1, 2, 3, 4, 5 };
int evenCount = numbers.Count(n => n % 2 == 0); // 使用Lambda表达式作为Count方法的参数来计算偶数数量
- 用于LINQ查询:
var filteredNumbers = numbers.Where(n => n > 3); // 使用Lambda表达式过滤大于3的数字
- 用于排序集合:
var sortedNumbers = numbers.OrderBy(n => n); // 使用Lambda表达式按升序对数字进行排序
- 作为事件处理程序:
button.Click += (sender, e) => {
/* 处理点击事件的代码 */ }; // 使用Lambda表达式作为事件处理程序订阅按钮的Click事件
- 创建委托实例:
Func<int, int> square = x => x * x; // 使用Lambda表达式创建委托实例,该委托接受一个int参数并返回其平方
int result = square(4); // 调用委托实例计算4的平方并获取结果
- 在并行编程中使用:
Parallel.For(0, numbers.Count, i => {
/* 并行处理的代码 */ }); // 使用Lambda表达式定义并行循环中的操作逻辑
这些是.NET Core中Lambda表达式的常见用法示例。通过使用Lambda表达式,你可以以简洁、可读性强且功能强大的方式表示代码块,并在各种场景中应用它们。
MVVM
-
M model 数据模型
-
V View 界面
-
VM ViewModel 整合业务
WPF 定时器
(System.Threading.Timer)的使用
using System;
using System.Collections.Generic;
using System.ComponentModel;
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 System.Windows.Threading;
//using System.Timers;
using System.Threading;
namespace WpfApp2
{
private Timer timer;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
timer = new Timer(new TimerCallback(timerCall), null, 0, 5000);
}
private void timerCall(object state)
{
this.Dispatcher.BeginInvoke(new Action(() =>
{
tbx3.AppendText("定时器时间到 ");
}));
}
}
}
配置文件读取
public partial class MainWindow : Window
{
public string path;
public MainWindow()
{
InitializeComponent();
path = System.AppDomain.CurrentDomain.BaseDirectory + "Config.ini";
CreateFile(path);
ReadConfigFile(path);
}
Dictionary<string, string> DictInitData = new Dictionary<string, string>();
public void ReadConfigFile(string path)
{
//读取配置文件并保存到字典中
StreamReader sr = new StreamReader(path);
string line;
while ((line = sr.ReadLine()) != null)
{
line = line.Trim();
if (line.StartsWith("#"))
{
continue;
}
if (!string.IsNullOrEmpty(line) && line.Contains("="))
{
int equalsIndex = line.IndexOf('=');
if (equalsIndex > 0)
{
string key = line.Substring(0, equalsIndex).Trim();
string value = line.Substring(equalsIndex + 1).Trim();
DictInitData.Add(key, value);
}
}
}
sr.Close();
if (DictInitData.Count != 3)
{
File.Delete(path);
CreateFile(path);
}
}
private void CreateFile(string path)
{
//判断配置文件是否存在,不存在创建一个
if (!File.Exists(path))
{
StreamWriter sw = File.CreateText(path);
sw.WriteLine("ip=192.168.0.1");
sw.WriteLine("port=80");
sw.Flush();
sw.Close();
}
}
}
UI线程
private void Update_textBox_rcvGuideData(string text)
{
if (this.IsHandleCreated)
textBox_rcvGuideData.BeginInvoke(new Action(() =>
{
textBox_rcvGuideData.AppendText(text + "\r\n");
if (textBox_rcvGuideData.Lines.Length > 500)
{
textBox_rcvGuideData.Clear();
}
}));
}
辨识attribute和property
- property
property针对类和对象来说(C#代码),下面这个类中的name,fun2都叫property
class Person()
{
string name;
int age;
void fun1(){
//吃饭
}
void fun2(){
//睡觉
}
}
- attribute
attribute是编程语言文法层面的东西(针对xaml来说),比如button按钮的宽度/高度
链接: link
单例:加深理解静态成员和方法
宝啊,你有没有想过什么是单例?类似单身狗吗?茕茕孑立,形影相吊,这好像不太正确,还有影子陪着单身汪的呀!
一个类只能创建一个实例(有啥用?鬼知道哩,反正我没用过)我写这个主要是帮着我理解C#的关键字 ”static“。
internal class Singleton
{
private static Singleton uniqueInstance;
private static readonly object lockObject = new object();
private string? name;
private int age;
private Singleton(string name,int age)
{
this.name = name;
this.age = age;
}
public static Singleton GetInstance()
{
if (uniqueInstance == null)
{
lock (lockObject)
{
if(uniqueInstance == null)
{
uniqueInstance = new Singleton(name, age);
}
}
}
return uniqueInstance;
}
}
为啥上面的代码无论你实例化多少个对象,实际上只有一个对象,这是为什么?原因就是“static”,无论成员还是方法,加上这个关键字,在实例化的时候,都不属于实例本身,他们都属于类本身,换句话说,类中的静态成员和方法被所有类共享。
上面的uniqueInstance是一个静态变量,无论创建多少个实例,最后这些实例都是共享一个静态成员变量。
LINQ的用法
这部分内容借鉴了杨中科老师的很多东西,大家可以去b站找他的视频学习真的值得一看. 链接: 杨中科老师视频链接
LINQ中where用法和原理
static void Main(string[] args)
{
int[] nums = {
11, 52, 69, 33, 54, 2, 9, 23, 66, 45 };
// 1. 调用系统的方法
IEnumerable<int> result = nums.Where<int>(a => a > 20 && a < 40); //where后面的是lamada表达式的简写形式
foreach(var i in result)
{
Console.WriteLine(i);
}
Console.WriteLine(" ");
// 2. 调用自己写的方法1
IEnumerable<int> result2 = SelectNums(nums, a => a > 20);
foreach (var i in result2)
{
Console.WriteLine(i);
}
Console.WriteLine(" ");
// 3. 调用自己写的方法2
IEnumerable<int> result3 = SelectNums2(nums, a => a > 30);
foreach (var i in result3) //foreach (var i in SelectNums2(nums, a => a > 20))
{
Console.WriteLine(i);
}
}
static IEnumerable<int> SelectNums(IEnumerable<int> items, Func<int, bool> func)
{
List<int> list = new List<int>();
foreach(int item in items)
{
if(func(item) == true)
{
list.Add(item);
}
}
return list;
}
static IEnumerable<int> SelectNums2(IEnumerable<int> items, Func<int, bool> func)
{
foreach (int item in items)
{
if (func(item) == true)
{
yield return item;
}
}
}
LINQ常用扩展方法
static void Main(string[] args)
{
List<Employee> list = new List<Employee>();
list.Add(new Employee {
Id = 1, Name = "张三", Age = 50, Gender = true, Salary = 50000 });
list.Add(new Employee {
Id = 2, Name = "李四", Age = 40, Gender = false, Salary = 90000 });
list.Add(new Employee {
Id = 3, Name = "赵六", Age = 30, Gender = false, Salary = 30000 });
IEnumerable<Employee> employee = list.Where((e) => {
return e.Age > 30; }); // employees.Where(e => return e.Age > 30 );
foreach(var i in employee)
{
Console.WriteLine(i);
}
Console.WriteLine(list.Count(e => e.Salary > 50000));
Console.WriteLine(list.Any(e => e.Gender == true));
// 防御性编程
Console.WriteLine(list.Single(e => e.Name == "张三"));
// Console.WriteLine(list.Single(e => e.Name == "孙悟空"));//没有该条信息,会报异常
Console.WriteLine(list.SingleOrDefault(e => e.Name == "孙悟空"));
}
class Employee
{
public long Id {
get; set; }
public string Name {
get; set; }
public int Age {
get; set; }
public bool Gender {
get; set; }
public int Salary{
get; set; }
/// <summary>
/// C#所有的class和struct都会继承object,而每一个object都会有一个ToString的方法,这里重写该方法
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"Id={
Id},Name={
Name},Age={
Age},Gender={
Gender},Salary={
Salary}";
}
}
class Dog
{
public string nickName {
get; set; }
public int age {
get; set; }
public override string ToString()
{
return $"nickName = {
nickName},age={
age}";
}
}
static void Main(string[] args)
{
List<Employee> list = new List<Employee>();
list.Add(new Employee {
Id = 1, Name = "张三", Age = 50, Gender = true, Salary = 50000 });
list.Add(new Employee {
Id = 2, Name = "李四", Age = 40, Gender = false, Salary = 90000 });
list.Add(new Employee {
Id = 3, Name = "赵六", Age = 30, Gender = false, Salary = 30000 });
list.Add(new Employee {
Id = 4, Name = "gati", Age = 34, Gender = true, Salary = 6000 });
list.Add(new Employee {
Id = 5, Name = "jim", Age = 40, Gender = false, Salary = 7000 });
list.Add(new Employee {
Id = 6, Name = "ancle", Age = 24, Gender = false, Salary = 2000 });
Console.WriteLine("where:");
IEnumerable<Employee> employee = list.Where((e) => {
return e.Age > 30; }); // employees.Where(e => e.Age > 30 );
foreach(var i in employee)
{
Console.WriteLine(i);
}
Console.WriteLine("count:");
Console.WriteLine(list.Count(e => e.Salary > 50000));
Console.WriteLine(list.Any(e => e.Gender == true));
// 防御性编程
Console.WriteLine("single:");
Console.WriteLine(list.Single(e => e.Name == "张三"));
// Console.WriteLine(list.Single(e => e.Name == "孙悟空"));//没有该条信息,会报异常
Console.WriteLine(list.SingleOrDefault(e => e.Name == "孙悟空"));
//排序
Console.WriteLine("order:");
foreach (var i in list.OrderBy(e => e.Salary))
{
Console.WriteLine(i);
}
//跳过和取
Console.WriteLine("skip and take:");
foreach (var i in list.Skip(2).Take(30))
{
Console.WriteLine(i);
}
//分组
Console.WriteLine("\r\n groupby:");
var groupItem = list.GroupBy(e => e.Age);
foreach(var i in groupItem)
{
Console.WriteLine(i.Key);
foreach(var j in i)
{
Console.WriteLine(j);
}
Console.WriteLine("");
}
//投影 类似数据库的select
IEnumerable<int> age_select = list.Select(e => e.Age); //把所有的年龄取出来
IEnumerable<string> name_select = list.Select(e => e.Name);
Console.WriteLine("\r\n 投影:");
foreach (var i in age_select)
{
Console.WriteLine(i);
}
IEnumerable<Dog> dogs = list.Select(e => new Dog {
nickName = e.Name, age = e.Age });
Console.WriteLine("\r\n 投影dog类:");
foreach (var i in dogs)
{
Console.WriteLine(i);
}
Console.WriteLine("\r\n 匿名类型:");
var items = list.Select(e => new{
姓名 = e.Name,性别 = e.Gender?"男":"女"});
foreach (var i in items)
{
Console.WriteLine(i.姓名+" "+i.性别);
}
//综合语法
Console.WriteLine("\r\n 综合语法:");
var items2 = list.GroupBy(e => e.Age).Select(g => new {
年龄 = g.Key, MaxSalary = g.Max(e => e.Salary), minSalary = g.Min(e => e.Salary),人数 = g.Count()});
foreach (var i in items2)
{
Console.WriteLine(i. 年龄 + " " + i.MaxSalary+ " " + i.minSalary + " " + i.人数);
}
}
//类型转化
IEnumerable<Employee> items = list.Where(e => e.Salary > 6000);
List<Employee> L1 = items.ToList();
Employee[] arry = items.ToArray();
//链式语法
Console.WriteLine("\r\n 链式语法:");
var list2 = list.Where(e => e.Id > 2).GroupBy(e => e.Age).OrderBy(g => g.Key).Take(3).
Select(g => new {
年龄 = g.Key, 平均工资 = g.Average(e => e.Salary) });
foreach(var i in list2)
{
Console.WriteLine(i.年龄 + " " + i.平均工资);
}
split方法可能会掉入的陷阱
split会按照特定字符进行分割,同时返回分割后的字符数组.分割后一定要注意字符前后端是否存在空格,不然会掉入大坑.
通过Linq读取配置文件
//配置文件接口类
public interface IConfigService
{
string GetValue(string name);
}
//配置文件实现类
public class IniFileConfig : IConfigService
{
public string FilePath {
get; set; }
public string GetValue(string name)
{
var kv = File.ReadAllLines(FilePath).Select(s => s.Split('=')).Select(strs => new {
Name = strs[0].Trim(), value = strs[1].Trim() }).SingleOrDefault(keyValue => keyValue.Name == name);
//var kv2 = kv.Select(s => s.Split('='));
if (kv != null)
{
return kv.value;
}
else
{
return null;
}
}
}
//调用配置文件
IniFileConfig config = new IniFileConfig();
string SmtpServer = config.GetValue("SmtpServer");
string UserName = config.GetValue("UserName");
string Password = config.GetValue("Password");
linq常见问题
//linq解决面试问题
int i = 4;
int j = 5;
int k = 6;
int[] nums = new int[] {
i,j,k };
//linq求解
int max = nums.Max();
//math处理
int max2 = Math.Max(i, Math.Max(j, k));
//三元运算法
int max3 = (i = i > j ? i : j) > k ? i : k;
int max4 = i > j ? i > k ? i : k : j > k ? j : k;
Console.WriteLine(max4);
//求解平均值
string s = "1,2,3,4,5,6,7,8,9";
string[] str = s.Split(',');
IEnumerable<int> arry = str.Select(e => Convert.ToInt32(e));
Console.WriteLine(arry.Average());
var avg = s.Split(',').Select(p => Convert.ToInt32(p));
//统计字符串字母出现的频率
Console.WriteLine("\r\n 统计字符串字母出现的频率:");
string s1 = "hello world,keson,hgongnoring";
var items4 = s1.Where(c => char.IsLetter(c)).Select(c => char.ToLower(c)).GroupBy(c => c).Select(g => new {
g.Key, count = g.Count() })
.OrderByDescending(g=>g.count).Where(g=>g.count>2);
foreach(var item in items4)
{
Console.WriteLine(item);
}
依赖注入
将一个类放到一个容器中,使用这个类时不需要实例化,直接从从容器中取出来
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleApp1
{
class Program
{
delegate void delegateFun();
static void Main(string[] args)
{
/*
* 1 ServiceCollection是内置的反转控制容器;services.BuildServiceProvider是使用容器中服务
* 2 使用步骤:创建控制容器;向容器中注册方法;使用容器的BuildServiceProvider向容器中获取服务
*/
//注册服务
ServiceCollection services = new ServiceCollection();
//services.AddTransient<TestServiceImpl>();
//services.AddSingleton<TestServiceImpl>();
services.AddScoped<TestServiceImpl>(); //同一个scope里拿到的对象是一致的
using (ServiceProvider sp = services.BuildServiceProvider())
{
TestServiceImpl testService = sp.GetRequiredService<TestServiceImpl>();
testService.Name = "keson";
testService.SayHello();
TestServiceImpl testService2 = sp.GetRequiredService<TestServiceImpl>();
//Console.WriteLine("testService, testService2:" + object.ReferenceEquals(testService, testService2));
using (IServiceScope scope1 = sp.CreateScope())
{
TestServiceImpl t = scope1.ServiceProvider.GetService<TestServiceImpl>();
t.Name = "张三";
t.SayHello();
TestServiceImpl t1 = scope1.ServiceProvider.GetService<TestServiceImpl>();
//Console.WriteLine("t, t1:" + object.ReferenceEquals(t, t1));
//Console.WriteLine("t, testService:" + object.ReferenceEquals(t, testService));
}
using (IServiceScope scope2 = sp.CreateScope())
{
TestServiceImpl t = scope2.ServiceProvider.GetService<TestServiceImpl>();
t.Name = "李四";
t.SayHello();
}
}
}
}
public interface ITestServic
{
string Name {
get; set; }
void SayHello();
}
public class TestServiceImpl : ITestServic,IDisposable
{
public string Name {
get; set; }
public void Dispose()
{
Console.WriteLine("disposable..........");
}
public void SayHello()
{
Console.WriteLine($"hi,my {
Name}");
}
}
public class TestServiceImpl2 : ITestServic
{
public string Name {
get; set; }
public void SayHello()
{
Console.WriteLine($"你好,我是{
Name}");
}
}
}
依赖注入 : 接口 + 实现
直接从类中注入
在一个A类中使用另外一个B类,不需要new 一个新的B类对象,只需要在被注入对象A实例化时传入B类的接口即可。
//Program.cs
static void Main(string[] args)
{
NotificationService notificationService = new NotificationService(new EmailSender());
notificationService.UseSendMessage();
}
//--------------------------------------------
public interface IMessageSender
{
void SendMessage(string message);
}
//--------------------------------------------
public class EmailSender : IMessageSender
{
public void SendMessage(string message)
{
Console.WriteLine(message);
}
}
//--------------------------------------------
public class NotificationService
{
private readonly IMessageSender messageSender;
public NotificationService(IMessageSender messageSender)
{
this.messageSender = messageSender;
}
public void UseSendMessage()
{
messageSender.SendMessage("hello,keson");
}
}
通过服务容器依赖注入
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleApp1
{
class Program
{
delegate void delegateFun();
static void Main(string[] args)
{
/*
* 1 ServiceCollection是内置的反转控制容器;services.BuildServiceProvider是使用容器中服务
* 2 使用步骤:创建控制容器;向容器中注册方法;使用容器的BuildServiceProvider向容器中获取服务
*/
//注册服务
ServiceCollection services = new ServiceCollection();
services.AddScoped<ITestService, TestServiceImpl>(); // 接口 + 实现
services.AddScoped<ITestService, TestServiceImpl2>();
using (ServiceProvider sp = services.BuildServiceProvider()) //当然,你也可以用services.BuildServiceProvider(),这里用变量接一下方便用
{
//services.BuildServiceProvider().GetService<ITestServic>();
ITestService ITest = sp.GetService<ITestService>();
ITest.Name = "孙悟空";
ITest.SayHello();
//Console.WriteLine(ITest.GetType());
ITestService ITest1 = sp.GetRequiredService<ITestService>(); //确定一定有这个服务
IEnumerable<ITestService> ITest2 = sp.GetServices<ITestService>(); //获取多个服务
foreach(var i in ITest2)
{
Console.WriteLine(ITest2.GetType());
}
}
}
}
public interface ITestService
{
string Name {
get; set; }
void SayHello();
}
public class TestServiceImpl : ITestService,IDisposable
{
public string Name {
get; set; }
public void Dispose()
{
Console.WriteLine("disposable..........");
}
public void SayHello()
{
Console.WriteLine($"hi,I am {
Name}");
}
}
public class TestServiceImpl2 : ITestService
{
public string Name {
get; set; }
public void SayHello()
{
Console.WriteLine($"你好,我是{
Name}");
}
}
}
依赖注入:通过构造函数
这里有一个小细节,所有实现的接口的实现必须是公有的
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleApp1
{
class Program
{
delegate void delegateFun();
static void Main(string[] args)
{
ServiceCollection services = new ServiceCollection();
services.AddScoped<Controller>();
services.AddScoped<ILog, LogImpl>();
services.AddScoped<IStorage, StorageImpl>();
services.AddScoped<IConfig, ConfigImpl>();
services.AddScoped<ILog, LogImpl>();
using(var sp = services.BuildServiceProvider())
{
var c = sp.GetRequiredService<Controller>();
c.Test();
}
Console.ReadKey();
}
}
class Controller
{
private readonly ILog log;
private readonly IStorage storage;
public Controller(ILog log,IStorage storage)
{
this.log = log;
this.storage = storage;
}
public void Test()
{
log.Log("开始上传");
storage.Save(" 名字 ", " 上传的内容 ");
log.Log("上传完毕");
}
}
interface ILog
{
void Log(string msg);
}
class LogImpl : ILog
{
public void Log(string msg)
{
Console.WriteLine($"日志: {
msg}");
}
}
interface IConfig
{
string GetValue(string name);
}
class ConfigImpl:IConfig
{
public string GetValue(string name)
{
return "config file";
}
}
interface IStorage
{
void Save(string name,string content);
}
class StorageImpl:IStorage
{
private readonly IConfig config;
public StorageImpl(IConfig config)
{
this.config = config;
}
public void Save(string name,string content)
{
string server = config.GetValue("server");
Console.WriteLine($"向服务器为: {
server}的文件名为: {
name}上传:{
content}");
}
}
}
配置读取
配置容器
可能年纪大了,对于那种不讲人话的人,特别反感,包括但不限于:领导、同同事、同学、长辈、博主、水军、我自己。他们就一个特点,很简单的事情他们包装的面目全非,听得新人云里雾里,以彰显在别人眼里不值一提的优越感。
什么是配置容器?他就是一种管理配置文件的东西。什么是配置文件,举个例子,你想连接数据库,你得有账户名、密码、端口号等等,这些东西不可能每次登录都自己手动输入,也不可能都会提供输入这些信息的界面,那就得从文件中读取,我们把它叫做配置文件。
*本章的核心就是Configrution这个命名空间,*搞懂这个就ok了,主要包括两个点:怎么创建配置容器?如何通过配置容器读取配置文件。
步骤
-
构建配置容器;
-
向配置容器中添加配置文件(json,xml,txt);
-
通过Build()读取配置文件;
ConfigurationBuilder builder = new();
builder.AddJsonFile("config.json",optional:true,reloadOnChange:true);
IConfigurationRoot configRoot = builder.Build();
json文件作为绑定源, 配置类作为绑定目标. 将配置类绑定到配置文件. 配置文件肯定得拿出来才能用, 如果一致放在json文件中,无法用,所以用一个和json配置文件相同的类来接受数据
//选项类被绑定到 ICconfigurationRoot 接口
Config config = new Config();
configurationRoot.Bind(config);
//选项类被绑定到 ICconfigurationRoot 接口 子类
Service service = new Service();
configurationRoot.GetSection("Service").Bind(service);
实例:数据源绑定单个类
该方式将类单个单个的配置,配置起来简单,但不利于集中管理
//json file
{
"key1": "IamString",
"key2": 10,
"key3": true
}
//------------------------------------------------------------------------
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;
class Program
{
static void Main(string[] args)
{
IConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddJsonFile("appsetting.json", optional: true, reloadOnChange: true);
var configurationRoot = builder.Build();
//选项类被绑定到 ICconfigurationRoot 接口
Config config = new Config();
configurationRoot.Bind(config);
Console.WriteLine($"key1:{
config.Key1}");
Console.WriteLine($"key2:{
config.Key2}");
Console.WriteLine($"key3:{
config.Key3}");
//选项类被绑定到 ICconfigurationRoot 接口 子类
Service service = new Service();
configurationRoot.GetSection("Service").Bind(service);
Console.WriteLine($"Service.Host {
service.Host}");
Console.WriteLine($"Service.Host {
service.Port}");
//
Console.ReadKey();
}
}
class Config
{
public string Key1 {
get; set; }
public int Key2 {
get; set; }
public bool Key3 {
get; set; }
}
class Service
{
public string Host {
get; set; }
public string Port {
get; set; }
//不能注入私有属性
//public string Port { get; private set; } = "999";
}
注册服务
步骤
-
创建一个服务容器;
-
容器对配置类进行注册服务;
services.Configurede<Ttype>{lamda表达式};
-
获取服务
serviceProvider.GetRequiredService<IOptions<MyOption>>();
//或者
serviceProvider.GetRequiredService<IOptionsSnapshot<Config>>();
实例:直接通过lambda表达式对类配置
using Microsoft.Extensions.Configuration;
//using Microsoft.Extensions.Configuration.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
var services = new ServiceCollection();
// 通过.Configure 对选项类进行配置(注册服务)
services.Configure<MyOption>(n =>
{
n.A = "keson";
n.B = "";
});
var serviceProvider = services.BuildServiceProvider();
//通过<IOptions<MyOption>>()获取服务
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
MyOption myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);
public class MyOption
{
public string A {
get; set; }
public string B {
get; set; }
}
实例:通过addOption配置
这种方法配置起来也很简单,且可以链式编程(集中配置,再一个地方对多个类绑定数据源)
addOption和IOptionsSnapshot区别:前者是刚运行时就保存在缓存中,后者是每次处理请求时都会重新从IOptionsSnapshot中读取,以便能够获取到最新的配置信息。
//--------------------------json文件-------------------------------
{
"name": "keson",
"age": "28",
"proxy": {
"address": "192.168.1.0","port": "80"}
}
//--------------------------main()-------------------------------
namespace 配置文件
{
internal class Program
{
static void Main(string[] args)
{
ServiceCollection services = new();
services.AddScoped<Controller>();
ConfigurationBuilder builder = new();
/*
//通过json读取配置文件
builder.AddJsonFile("config.json",optional:true,reloadOnChange:true);
*/
//通过命令行读取配置文件 可以在调试中设置 不同参数用空格分开,不能有多余的空格
builder.AddCommandLine(args);
IConfigurationRoot configRoot = builder.Build();
services.AddOptions().Configure<Config>(e => configRoot.Bind(e));
using (var sp= services.BuildServiceProvider())
{
//通过一个Controller类来操控Config
var c2 = sp.GetRequiredService<Controller>();
c2.Test();
//直接操控
var c = sp.GetRequiredService<IOptionsSnapshot<Config>>();
c.Value.name = "孙悟空";
c.Value.age = "500";
Console.WriteLine(c.Value.name);
Console.WriteLine("--------------");
Console.WriteLine(c.Value.age);
}
}
}
class Config
{
public string name {
get; set; }
public string age {
get; set; }
public Proxy proxy {
get; set; }
}
class Proxy
{
public string address {
get; set; }
public int Port {
get; set; }
}
}
//--------------------------Controller-------------------------------
internal class Controller
{
//用于访问请求生存期的 TOptions 的值
private readonly IOptionsSnapshot<Config> optconfig;
public Controller(IOptionsSnapshot<Config> optconfig)
{
this.optconfig = optconfig;
}
public void Test()
{
Console.WriteLine(optconfig.Value.name);
Console.WriteLine("--------------");
Console.WriteLine(optconfig.Value.age);
}
}
扁平化配置
name=如来 age=10000 proxy:address=1.1.1.1 proxy:port=9999 proxy:IDs:0=69 proxy:IDs:1=69
日志系统
日志级别
Trace -> Debug -> Informatio -> Warning -> Error -> Critical
日志记录到控制台
核心代码:
-
引用扩展包
-
将日志记录到控制台
-
设置记录到控制台代码Log Level
using Microsoft.Extensions.Logging; //记录日志用的扩展包
loggingBuilder.AddConsole(); //将日志记录到控制台
loggingBuilder.SetMinimumLevel(LogLevel.Trace); //设置最低输出级别信息
完整代码
//----------------Test()类-------------------------
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 日志系统
{
internal class TestLogging
{
private readonly ILogger<TestLogging> logger;
public TestLogging(ILogger<TestLogging> logger)
{
this.logger = logger;
}
public void Test()
{
logger.LogDebug("开始执行Logging");
logger.LogWarning("程序警告");
logger.LogError("程序失败");
try
{
File.ReadAllText("A://");
logger.LogDebug("读取文件成功");
}
catch (Exception e)
{
logger.LogError(e, "读取文件失败"); //将捕获的异常也打印出来
}
}
}
}
//----------------Main()-------------------------
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using 日志系统;
ServiceCollection services = new ServiceCollection();
services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddConsole(); //将日志记录到控制台
//loggingBuilder.AddEventLog(); //将日志记录到Windows日志事件中
loggingBuilder.SetMinimumLevel(LogLevel.Trace); //设置最低输出级别信息
});
services.AddScoped<TestLogging>();
using(var sp = services.BuildServiceProvider())
{
TestLogging testLogging = sp.GetRequiredService<TestLogging>();
testLogging.Test();
}
日志记录到文本: NLog
官网查看例程
很多时候我们不仅需要从控制台查看日志, 还需要从日志文件上查看, Microsoft.Extensions.Logging不满足需求, 需要引用 NLog.Extensions.Logging . 下面的例图是访问NLog.Extensions.Logging扩展包的方法
复制config代码,注意名字一定要是nlog.config , 存储位置也要修改,直接改为程序的根目录.
Nlog核心代码
NLog是通过Xml实现配置的, 主要有两部分组成: target 和 rules, 先定义目标,通过规则匹配
target:
- type: 输出到文件还是控制台
- name: target的名字,就像你声明一个变量 int a = 5, 引用它的时候,就可以通过name引用.
- filename: 输出文件名字
rules:
- name: 匹配的名字,允许使用正则表达式,例如匹配命名空间的名字
- minlevel: 需要记录的最低日志权限
- wirteTo: 需要输出到的匹配的 target name
<targets>
<target xsi:type="File" name="allfile" fileName="Test.log"/>
<target xsi:type="Console" name="lifetimeConsole" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="allfile" />
<logger name="SystemServices.*" minlevel="Warn" writeTo="lifetimeConsole" />
</rules>
实例
- Main()
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
//using Microsoft.Extensions.Logging.Console;
using NLog.Extensions.Logging;
using SystemServices;
using 日志系统;
ServiceCollection services = new ServiceCollection();
services.AddLogging(loggingBuilder =>
{
//loggingBuilder.AddConsole();
//loggingBuilder.AddEventLog(); //将日志记录到Windows日志事件中
loggingBuilder.AddNLog(); //日志记录到文本文件
//loggingBuilder.SetMinimumLevel(LogLevel.Trace); //设置最低输出级别信息
});
services.AddScoped<Test1>();
services.AddScoped<Test2>();
using(var sp = services.BuildServiceProvider())
{
Test1 test1 = sp.GetRequiredService<Test1>();
test1.Test();
Test2 test2 = sp.GetRequiredService<Test2>();
test2.Test();
}
- nlog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Info"
internalLogFile="internal-nlog-AspNetCore.txt">
<!-- enable asp.net core layout renderers -->
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<!-- the targets to write to -->
<targets>
<!-- File Target for all log messages with basic details -->
<target xsi:type="File" archiveAboveSize="100" maxArchiveFiles="10" name="allfile" fileName="logs/nlog-AspNetCore-all-${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|
${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}" />
<!-- File Target for own log messages with extra web details using some ASP.NET core renderers -->
<target xsi:type="File" name="ownFile-web" archiveAboveSize="1000" fileName="logs/nlog-AspNetCore-own-${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|
${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />
<!--Console Target for hosting lifetime messages to improve Docker / Visual Studio startup detection -->
<target xsi:type="Console" name="lifetimeConsole" layout="${MicrosoftConsoleLayout}" />
</targets>
<!-- rules to map from logger name to target -->
<rules>
<!--All logs, including from Microsoft-->
<logger name="*" minlevel="Trace" writeTo="allfile" />
<logger name="日志系统.*" minlevel="Debug" writeTo="lifetimeConsole" />
<logger name="SystemServices.*" minlevel="Warn" maxlevel="Warn" writeTo="lifetimeConsole" />
<!--Output hosting lifetime messages to console target for faster startup detection -->
<logger name="Microsoft.Hosting.Lifetime" minlevel="Info" writeTo="lifetimeConsole, ownFile-web" final="true" />
<!--Skip non-critical Microsoft logs and so log only own logs (BlackHole) -->
<logger name="Microsoft.*" maxlevel="Info" final="true" />
<logger name="System.Net.Http.*" maxlevel="Info" final="true" />
<logger name="*" minlevel="Trace" writeTo="ownFile-web" />
</rules>
</nlog>
-
两个Test类
//------------------Test1------------------ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 日志系统 { internal class Test1 { private readonly ILogger<Test1> logger; public Test1(ILogger<Test1> logger) { this.logger = logger; } public void Test() { logger.LogDebug("Test1开始执行"); logger.LogWarning("Test1程序警告"); logger.LogError("Test1程序失败"); } } } //------------------Test2------------------ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using 日志系统; namespace SystemServices { internal class Test2 { private readonly ILogger<Test2> logger; public Test2(ILogger<Test2> logger) { this.logger = logger; } public void Test() { logger.LogDebug("Test2开始执行"); logger.LogWarning("Test2开始执行程序警告"); logger.LogError("Test2开始执行程序失败"); } } }
SeriLog: 结构化日志
以键值对的形式传保存日志
主要代码和Nlog基本一致,只要把 SeriLog添加到容器中就好
using Serilog;
using Serilog.Formatting.Json;
ServiceCollection services = new ServiceCollection();
services.AddLogging(loggingBuilder =>
{
//loggingBuilder.AddConsole();
//loggingBuilder.AddEventLog(); //将日志记录到Windows日志事件中
//loggingBuilder.AddNLog(); //日志记录到文本文件
//loggingBuilder.SetMinimumLevel(LogLevel.Trace); //设置最低输出级别信息
//使用SeriLog
Serilog.Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console(new JsonFormatter())
.WriteTo.File(new JsonFormatter(), "logs/SeriLog.log")
.CreateLogger();
loggingBuilder.AddSerilog();
});
Entity Framework Core
通过C#代码创建表
假设我们要设计这样一张表,我们是不是得一句一句的去写sql语句
操作步骤:
-
先建实体类, 再建实体配置类, 再建数据库配置
-
控制台迁移数据: Add-Migration Init
-
将数据写入数据库: update-database
// 1.建立实体类 internal class Person { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public DateTime Birthday { get; set;} public string BirthPlace { get; set; } } //2.配置实体类 internal class PersonEntityConfig:IEntityTypeConfiguration<Person> { public void Configure(EntityTypeBuilder<Person> builder) //Configure继承自IEntityTypeConfiguration { builder.ToTable("T_Persons"); } } //3.也可以只定义实体类,但不配置,系统会默认配置 internal class Dog { public int Id { get; set; } public string Name { get; set; } } //4.写个数据库配置类 将要写入数据库的类用DbSet<Type>在这里声明,同时该类继承自DbContext.类里重写两个方法,OnConfiguring用啦配置数据的连接 OnModelCreating组装数据模型 internal class MyDbContext:DbContext { public DbSet<Book> Books { get; set; } public DbSet<Person> Persons { get; set; } public DbSet<Dog> Dog { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { string connStr = "Server=.;Database=demo1;Trusted_Connection=True;Encrypt=false;"; optionsBuilder.UseSqlServer(connStr); optionsBuilder.LogTo(Console.WriteLine); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly); } } //5. 控制台迁移数据: Add-Migration Init; 将数据写入数据库: update-database
通过C#对表进行怎删改查
上面的内容是对数据库的表进行设计, 怎么实现对设计好的表进行增删改查
插入数据/查询数据
-------------------------main()-----------------------------
using EF_Core;
using (MyDbContext ctx = new MyDbContext())
{
//ctx相当于逻辑上的数据库
Dog d = new Dog();
d.Name = "旺财";
ctx.Dog.Add(d); //把d对象加入Dog这个逻辑上的表里
//ctx.SaveChanges();
await ctx.SaveChangesAsync();
Book b1 = new Book {
Name = "三体", AuthorName = "刘慈欣", Price = 15 };
Book b2 = new Book {
Name = "西游记", AuthorName = "罗贯中", Price = 20 };
Book b3 = new Book {
Name = "水浒传", AuthorName = "施耐庵", Price = 18 };
Book b4 = new Book {
Name = "红楼梦", AuthorName = "曹雪芹", Price = 19 };
ctx.Books.Add(b1);
ctx.Books.Add(b2);
ctx.Books.Add(b3);
ctx.Books.Add(b4);
var books = ctx.Books.Select(b => b.Price ==15).ToList(); //查询数据
IQueryable<Book> books = ctx.Books.Where(b => b.Price > 18);
foreach (Book book in books)
{
Console.WriteLine(book.Name);
Console.WriteLine("**********");
}
}
删除数据/更新数据
//删除内容: 1.先查出实体 2.从内存中删除数据
var b = ctx.Books.Where(b=>b.Name == "西游记");
foreach(var i in b)
{
//ctx.Books.Remove(i); //删除数据
i.AuthorName = "Guanzhong Luo"; //跟新数据
}
FluenAPI和DataAnnotation区别
FluenAPI复杂不耦合, DataAnnotation简单但是存在耦合
# FluenAPI: 单独写个配置类
internal class PersonEntityConfig:IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
builder.ToTable("T_Persons");
}
}
# DataAnnotation: 直接通过注解的方式配置
[Table("T_Cat")]
internal class Cat
{
public int Id {
get; set; }
[Required]
[MaxLength(69)]
public string Name {
get; set; }
public int CatId1 {
get; set; }
[NotMapped] //不将该属性映射到数据库,尽量不要用
public int CatId2 {
get; set; }
}
Guid用法
---------------实体类---------------------
internal class Rabbit
{
public Guid Id {
get; set; }
public string Name {
get; set; }
}
---------------main()---------------------
Rabbit r1 = new Rabbit();
r1.Name = "红兔";
ctx.Rabbits.Add(r1);
await ctx.SaveChangesAsync();
Hi/Lo算法
Migration的常用命令
-
把数据库升级或者回滚到某个状态: update-database migrationState
-
删除最后一次的迁移脚本: remov-migration
-
生成SQL迁移脚本: script-migration
-
add-migration 取一个名字
-
updat-database
反向工程
Scaffold-DbContext "Server=.;Database=Student;Trusted_Connection=True;MultipleActiveResultSets=true;Encrypt=false;" Microsoft.EntityFrameworkCore.SqlServer
EF core查询执行sql语句
- 标准日志: optionsBuilder.UseLoggerFactory(loggerFactory)
- 简单日志: optionsBuilder.LogTo(Console.WriteLine)
- ToQueryString()方法: var books = ctx.Books.Where(b => b.Price ==15); string sql = books.ToQueryString();
怎么记录C#代码对数据库的操作步骤,可以有标准日志和简单日志的方法
--------------BOOk类----------------
internal class Book
{
public long Id {
get; set; }
public string Name {
get; set; }
public string Title {
get; set; }
public string AuthorName {
get; set; }
public float Price {
get; set; }
}
--------------BOOk配置类------------------
using EF_Core;
using static System.Reflection.Metadata.BlobBuilder;
using (MyDbContext ctx = new MyDbContext())
{
Book b1 = new Book {
Name = "三体", AuthorName = "刘慈欣", Price = 15 };
ctx.Books.Add(b1);
await ctx.SaveChangesAsync();
var books = ctx.Books.Where(b => b.Price ==15);
string sql = books.ToQueryString();
Console.WriteLine(sql);
}
-------------Main()------------------
internal class MyDbContext:DbContext
{
private static ILoggerFactory loggerFactory =
LoggerFactory.Create(b => b.AddConsole());
public DbSet<Book> Books {
get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connStr = "Server=.;Database=Student;Trusted_Connection=True;Encrypt=false;";
optionsBuilder.UseSqlServer(connStr);
optionsBuilder.UseLoggerFactory(loggerFactory); //标准日志
optionsBuilder.LogTo(Console.WriteLine); //简单日志
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
}
EF Core映射Mysql
所有代码都和上面的SQLServer一样,只是在DbContext上修改, 在OnConfiguring上进行配置
string MySqlConneStr = "server=localhost;user=root;password=123456;database=demo1";
//特别注意:此处使用的是:Pomelo.EntityFramework.MySql包.
var serverVersion = new MySqlServerVersion(new Version(8, 0, 34));
optionsBuilder.UseMySql(MySqlConneStr, serverVersion);
对应关系: 关系配置在任何一方
一个Artcle对应多个Comment
四种关系: 弄清楚谁是主语,谁是宾语
one | many | |
---|---|---|
Has | HasOne | HasMany |
With | WithOne | WithMany |
一对多: 关系配置在多端
Comment类和配置
internal class Comment
{
public long Id {
get; set; }
public string? Message {
get; set; }
public Artcle? Artcle1 {
get; set; }
}
internal class CommentConfig : IEntityTypeConfiguration<Comment>
{
public void Configure(EntityTypeBuilder<Comment> builder)
{
builder.ToTable("T_Commnet");
builder.HasOne(c => c.Artcle1).WithMany(a=>a.Comments);
}
}
Artcle类和配置类
internal class Artcle
{
public long Id {
get; set; }
public string? Title {
get; set; }
public string? Message {
get; set; }
public List<Comment> comments {
get; set; } = new List<Comment>();
}
internal class ArtcleConfig : IEntityTypeConfiguration<Artcle>
{
public void Configure(EntityTypeBuilder<Artcle> builder)
{
builder.ToTable("T_Artcle");
}
}
MyDbContext
internal class MyDbContext:DbContext
{
public DbSet<Artcle> Artcles {
get; set; }
public DbSet<Comment> Comments {
get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connStr = "Server=.;Database=demo1;Trusted_Connection=True;Encrypt=false;";
optionsBuilder.UseSqlServer(connStr);
//optionsBuilder.LogTo(Console.WriteLine);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
}
main()
using EF_Core;
using Microsoft.EntityFrameworkCore;
using 一对多;
using (MyDbContext ctx = new MyDbContext())
{
/*
Artcle artcle = new();
artcle.Title = "keson is the best";
artcle.Message = "据说,他是当代扫地僧";
//Comment c1 = new() { Message = "太牛了" };
Comment c2 = new() { Message = "也太搞笑了" };
//artcle.Comments.Add(c1);
artcle.Comments.Add(c2);
ctx.Artcles.Add(artcle);
ctx.SaveChanges();
*/
/*
//IQueryable<Artcle> artcles = ctx.Artcles.Where(a => a.Id == 1); //不查询关联表
IQueryable<Artcle> artcles = ctx.Artcles.Include(a=>a.Comments). Where(a => a.Id == 1); //关联表也查询出来
foreach (var artcle in artcles)
{
Console.WriteLine(artcle.Id);
Console.WriteLine(artcle.Title);
foreach(var c in artcle.Comments)
{
}
}
*/
/*
var comments = ctx.Comments.Include(c=>c.Artcle1).Where(c => c.Id == 1);
foreach(var c in comments)
{
Console.WriteLine(c.Id);
Console.WriteLine(c.Artcle1.Id);
Console.WriteLine(c.Artcle1.Message);
}
*/
//上面的取法会取出所有的字段,通过匿名方法可以只获取需要的字段,提高数据库性能
var a1 = ctx.Artcles.Select(a => new {
a.Id, a.Title }).First();
Console.WriteLine(a1.Id + a1.Title);
}
一对多:关系配置在一端
所有的代码都一样,只是配置在一端
internal class ArtcleConfig : IEntityTypeConfiguration<Artcle>
{
public void Configure(EntityTypeBuilder<Artcle> builder)
{
builder.ToTable("T_Artcle");
builder.HasMany(a => a.Comments).WithOne(c => c.Artcle1);
}
}
EFCore性能
EF Core绝大部分的性能超过绝大多数程序员
单向导航
-
withmany()不带参数
-
有主从表的用一对多
-
只是基础处关系的用单向导航
internal class User
{
public long Id {
get; set; }
public string Name {
get; set; }
}
internal class UserConfig : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("T_User");
}
}
*******************************
internal class Leave
{
public long Id {
get; set; }
public User Requester {
get; set; }
public User? Approver {
get; set; }
public string? remake {
get; set; }
}
internal class LeaveConfig : IEntityTypeConfiguration<Leave>
{
public void Configure(EntityTypeBuilder<Leave> builder)
{
builder.ToTable("T_Leaves");
builder.HasOne<User>(l => l.Requester).WithMany();
builder.HasOne<User>(l => l.Approver).WithMany();
}
}
*******************************
internal class MyDbContext:DbContext
{
public DbSet<User> Users {
get; set; }
public DbSet<Leave> Leaves {
get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connStr = "Server=.;Database=demo1;Trusted_Connection=True;Encrypt=false;";
optionsBuilder.UseSqlServer(connStr);
optionsBuilder.LogTo(Console.WriteLine);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
}
--------------------main()----------------------
using EF_Core;
using 单向导航;
Console.WriteLine("Hello, World!");
using (MyDbContext ctx = new MyDbContext())
{
/*
var l = ctx.Leaves.FirstOrDefault();
if (l != null)
{
Console.WriteLine(l);
}
*/
User u1 = new User {
Name = "孙悟空" };
Leave leave = new Leave {
Requester = u1, remake = "回家结婚" };
ctx.Leaves.Add(leave);
ctx.SaveChanges();
}