目录
0. 来点鸡汤1. 概念1.1 C#能做什么1.2 为什么要选择C#,而不是QT或者其它?1.3 winform 和 wpf有什么区别1.4 .net Framework 和 .net Core联系1.5 WPF各个组成部分 2. xaml2.1 xaml中的对象和属性2.2 xaml页面布局2.2.1 层级概念2.2.2 使用 Grid 定义行和列2.2.3 设置行和列 2.3 xaml样式2.3.1 方法一:不给样式命名2.3.2 方法二:给样式命名2.3.3 给样式命名同时继承基础样式 2.4 在资源字典定义样式2.4.1 添加资源字典2.4.2 全局引用资源字典 2.5 控件模板重写 3. C# 代码语法规则3.1 变量 、属性、字段分别是什么?3.2 属性、变量、字段变量(Variables):字段(Fields):属性(Properties):总结区别:易错点 3.3 set{}、get{}用法3.4 App.config用法3.5 将一个类拆开写在多处 4. 数据绑定4.1 原理4.2 xaml 实现数据绑定案例一案例二 4.3 C#代码实现数据绑定4.4 PropertyChanged实现数据绑定(1)原理(2) 界面(3) xaml(4) c# 5 c#中的委托5.1 如何理解委托?5.2 系统自带的两种委托 Action<> 和Func<>5.3 简写委托的形式 C#中的事件Lambda表达式MVVMWPF 定时器配置文件读取UI线程辨识attribute和property 单例:加深理解静态成员和方法LINQ的用法LINQ中where用法和原理LINQ常用扩展方法split方法可能会掉入的陷阱通过Linq读取配置文件linq常见问题 依赖注入依赖注入 : 接口 + 实现直接从类中注入通过服务容器依赖注入 依赖注入:通过构造函数 配置读取配置容器步骤实例:数据源绑定单个类 注册服务步骤实例:直接通过lambda表达式对类配置实例:通过addOption配置 扁平化配置 日志系统日志级别日志记录到控制台核心代码:完整代码 日志记录到文本: NLog官网查看例程Nlog核心代码实例 SeriLog: 结构化日志 Entity Framework Core通过C#代码创建表通过C#对表进行怎删改查插入数据/查询数据删除数据/更新数据 FluenAPI和DataAnnotation区别Guid用法Hi/Lo算法Migration的常用命令反向工程EF core查询执行sql语句EF Core映射Mysql对应关系: 关系配置在任何一方一对多: 关系配置在多端一对多:关系配置在一端 EFCore性能单向导航
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。
1.4 .net Framework 和 .net Core联系
讲个小插曲,有一次其他部门的一个同事台上发言,原话:“我们这个程序是用 dot net core 开发的”,听到这句话时,我有点懵逼,什么人这是,你就说用C#开发的不就完了么,还tmd左一句dot net core,右一句dot net core,啥也不是。
看上面这张图片,我选的都是WPF,但是它们的架构不一样。
顺便说一嘴:咱可不是喜欢背后说人坏话,我批评甚至是鄙视我那个说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进行设计(几行几列);在每个层级下面对界面进行设计![在这里插入图片描述](http://zhangshiyu.com/zb_users/upload/2024/04/20240403123620171211898026441.png)
可能看了上面的图片也觉得一头雾水,我们现在从网页上随便截图一张,结合WPF看看到底什么是层级结构
![在这里插入图片描述](http://zhangshiyu.com/zb_users/upload/2024/04/20240403123620171211898082312.png)
我们把这个界面先整体分成三个区域, 即3行1列
第1行第1列 所有的图标依次用stackPanel放置控件就可以了
在第2行第1列 里面进一步切割,可在分成 2行1列
![在这里插入图片描述](http://zhangshiyu.com/zb_users/upload/2024/04/20240403123620171211898095233.png)
在第3行第1列 里面进一步切割,可在分成 1行3列
![在这里插入图片描述](http://zhangshiyu.com/zb_users/upload/2024/04/20240403123620171211898054129.png)
这样一个界面就被切割好了, 理解上面这个例子,应该就知道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);
运行结果:
44184418请按任意键继续. . .
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
propertyproperty针对类和对象来说(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.csstatic 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();}