Skip to content

Commit 6a270df

Browse files
committed
episode 42
1 parent 2785d62 commit 6a270df

File tree

2 files changed

+355
-1
lines changed

2 files changed

+355
-1
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
### 2023
1818

19-
**九月份**[041](docs/episode-041.md) :high_brightness:
19+
**九月份**[042](docs/episode-042.md) :high_brightness: | [第 041 期](docs/episode-041.md)
2020

2121
**八月份**[第 040 期](docs/episode-040.md)
2222

docs/episode-042.md

+354
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
# .NET 每周分享第 42 期
2+
3+
## 卷首语
4+
5+
为什么 Startup 不选择 .NET 作为后端语言
6+
7+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/cfd81057-5150-4148-afcf-ec293902da89)
8+
9+
`Reddit` 上的一个[讨论](https://www.reddit.com/r/dotnet/comments/16fu7o0/why_isnt_dotnet_core_popular_among_startups/?share_id=m43b3lhRz0cwGG0GLrSew&utm_content=1&utm_medium=ios_app&utm_name=ioscss&utm_source=share&utm_term=1&rdt=61720)
10+
11+
> 为什么创业公司不选择 .NET
12+
13+
高赞回答列出了下面几个原因
14+
15+
1. 创业公司吸引年轻人,而年轻人更加喜欢 Javascript
16+
2. 创业公司通常是全栈,Javascript 适合全栈开发
17+
3. 对于 `C#``.NET` 有着传统的看法
18+
4. 过去一段事件 `.NET` 社区一些错误决定
19+
5. `.NET` 这种基于约束的方法很难让人喜欢
20+
6. 人力资源方面的挑战
21+
7. 不喜欢多线程
22+
8. 面向对象编程的恐惧
23+
9. 创业公司喜欢 Mac 电脑
24+
10. 认为 `.NET``Java` 太主流了
25+
26+
27+
## 行业资讯
28+
29+
1、[C# 学习证书](https://devblogs.microsoft.com/dotnet/csharp-certification-training-series/)
30+
31+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/8c6a7176-9ec6-4fc2-9e75-f62f2f539b6e)
32+
33+
微软联合 `freecCodeCamp` 推出了 `C#` 培训课程,在其中可以获得相关的证书。
34+
35+
2、[Visual Studio 团队茶话会](https://devblogs.microsoft.com/visualstudio/visual-studio-tea-technology-miniseries/)
36+
37+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/d5baebd4-5ff2-4850-be1a-a47288476075)
38+
39+
10 月 4 号,`Visual Studio` 团队会组织一个茶话会,邀请团队人来分享关于 `Visual Studio` 的其他不为人知的方面,比如如何将 idea 转换为 feature,如何 debug,如何用 Visual Studio 创建移动应用程序。
40+
41+
42+
## 文章推荐
43+
44+
1、[EF Core 中的乐观锁](https://www.milanjovanovic.tech/blog/solving-race-conditions-with-ef-core-optimistic-locking)
45+
46+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/c38c012c-e5f1-4b72-9570-90f6ce10580e)
47+
48+
49+
在数据库应用程序中,我们通常需要考虑并发问题。比如一个酒店订阅系统,我们不能在同一个时间段将一个房间分发给两个订单。应用程序的逻辑一般是这样的
50+
51+
1. 查询特定房间在特定时间段的订阅情况
52+
2. 如果被预定,返回订阅失败
53+
3. 否则,调用付款等其他服务
54+
4. 更新房间信息并且保存
55+
56+
这些步骤不是在同一个 `Transaction` 完成的,这就带来一个问题,如果在第 1 步之后,房间被其他用户约定了,那么在第 4 步的时候,就会覆盖前面一个订阅的结果。
57+
那么 `SQL Server` 中的乐观锁就能解决这个问题,对于表中的都会内置一个 `rowversion` 列,当这一行被更新后,就会自动更改,那么过程就变成这样
58+
1. 查询特定房间在特定时间段的订阅情况
59+
2. 如果被预定,返回订阅失败
60+
3. 否则或者该行数据和 `rowversion`,调用付款等其他服务
61+
4. 在更新的时候只有 `rowversion` 和之前上一步获取的相同的时候,才更新成功,否则失败。
62+
63+
体现在代码中是这样的
64+
65+
```csharp
66+
public class Apartment
67+
{
68+
public Guid Id { get; set; }
69+
[Timestamp]
70+
public byte[] Version { get; set; }
71+
}
72+
```
73+
74+
或者在 `Fluent API`
75+
76+
```csharp
77+
protected override void OnModelCreating(ModelBuilder modelBuilder)
78+
{
79+
modelBuilder.Entity<Apartment>()
80+
.Property<byte[]>("Version")
81+
.IsRowVersion();
82+
}
83+
```
84+
85+
这样在调用 `SaveSchanges` 方法的时候,会抛出 `DbUpdateConcurrencyException` 异常
86+
87+
2、[于 Moq 中的 SponsorLink 作者的观点](https://codecodeship.com/blog/2023-09-07-daniel-cazzulino)
88+
89+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/062a5f1e-5134-49b9-8281-d64685e4e457)
90+
91+
前一阵子 `Moq` 库的 `SponserLink` 的事情在 `.NET` 社区掀起了巨大的讨论。在事件平息之后,作者采访了 `Moq` 的作者聊了一下这件事的来龙去脉,包含了下面的话题
92+
- 作者对开源认识
93+
- 使用 `SponerLink` 的效果
94+
- 事件发生后 `Moq` 的现状
95+
- 对后续的规划和展望
96+
97+
98+
3、[Weak Reference](https://steven-giesel.com/blogPost/675b75fc-2c1b-43da-9ff8-42962ca8159b)
99+
100+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/165fa6d2-4bd6-4bb2-86f4-5781b2d724fc)
101+
102+
103+
C# 作为一个托管的编程语言,也就是说我们并不需要关心内存的问题。但是有很多情况下会导致内存泄漏,比如事件的的委托。
104+
105+
```csharp
106+
public class Publisher
107+
{
108+
public event Action<string> OnChange = delegate { };
109+
public void Raise(){
110+
OnChange("hello world");
111+
}
112+
public string Label => "Hi";
113+
}
114+
115+
public class Subscriper : IDisposable
116+
{
117+
private bool disposed = false;
118+
public Subscriper() {
119+
Console.WriteLine("ctor");
120+
}
121+
~Subscriper() {
122+
Console.WriteLine("descrt");
123+
Dispose(false);
124+
}
125+
public void Dispose(){
126+
Console.WriteLine("dispose");
127+
Dispose(true);
128+
GC.SuppressFinalize(this);
129+
}
130+
131+
protected virtual void Dispose(bool disposing)
132+
{
133+
if (!disposed) {
134+
disposed = true;
135+
}
136+
}
137+
public void WriteLine(string s){
138+
Console.WriteLine(s);
139+
}
140+
}
141+
```
142+
比如这里定义了一个 `Puber` 和一个 `Suber` 类,那么在应用程序中
143+
144+
```csharp
145+
Subscriper sub;
146+
Publisher publisher = new Publisher();
147+
148+
for (int i = 0; i < 20; i ++)
149+
{
150+
sub = new Subscriper();
151+
// publisher.OnChange += sub.WriteLine;
152+
}
153+
Console.ReadKey();
154+
GC.Collect();
155+
GC.WaitForPendingFinalizers();
156+
Console.WriteLine();
157+
Console.WriteLine(publisher.Label);
158+
```
159+
160+
当调用 `GC.Collect` 之后,`~Subscriper` 方法就会被多次执行,因为被 GC 给回收。但是如果我们将 `publisher.OnChange += sub.WriteLine;` 恢复,所有的 `~Subscriper` 都不会执行,因为从 `GC` 角度来看,它注册了一个 `Puber` 的事件。这样导致了内存泄露。
161+
162+
`C#` 中有一个叫做 `WeakReference` 类型,它能够跳过在 `GC` 对标记阶段。该类型有两个重要属性 `IsAlive` 表明引用的对象是否活着,`Target` 能够获得该对象。
163+
164+
```csharp
165+
public class WeakEvent
166+
{
167+
private readonly List<WeakReference> _listeners = new List<WeakReference>();
168+
169+
public void AddListener(Action<string> handler)
170+
{
171+
_listeners.Add(new WeakReference(handler));
172+
}
173+
174+
public void Raise()
175+
{
176+
for(int i = _listeners.Count - 1; i >= 0; i--)
177+
{
178+
WeakReference wr = _listeners[i];
179+
if (wr != null)
180+
{
181+
if (wr.IsAlive)
182+
{
183+
((Action<string>)wr.Target)("hell world");
184+
}
185+
else
186+
{
187+
_listeners.RemoveAt(i);
188+
}
189+
}
190+
}
191+
}
192+
}
193+
194+
public class Publisher
195+
{
196+
private readonly WeakEvent _event = new WeakEvent();
197+
public void Raise(){
198+
_event.Raise();
199+
}
200+
201+
public void Register(Action<string> handler){
202+
_event.AddListener(handler);
203+
}
204+
public string Label => "Hi";
205+
}
206+
207+
208+
public class Subscriper : IDisposable
209+
{
210+
private bool disposed = false;
211+
public Subscriper(){
212+
Console.WriteLine("ctor");
213+
}
214+
215+
~Subscriper(){
216+
Console.WriteLine("descrt");
217+
Dispose(false);
218+
}
219+
220+
public void Dispose(){
221+
Console.WriteLine("dispose");
222+
Dispose(true);
223+
GC.SuppressFinalize(this);
224+
}
225+
226+
protected virtual void Dispose(bool disposing)
227+
{
228+
if (!disposed)
229+
{
230+
if (disposing)
231+
{
232+
// Dispose managed resources here
233+
}
234+
// Dispose unmanaged resources here
235+
disposed = true;
236+
}
237+
}
238+
239+
public void WriteLine(string s)
240+
{
241+
Console.WriteLine(s);
242+
}
243+
}
244+
```
245+
246+
这时候,我们在调用下面这段代码的时候,由于 `sub``WriteLine` 注册对象是 `WeakReference`, 那么 GC 就会将他们全部回收。
247+
248+
```csharp
249+
Subscriper sub;
250+
Publisher publisher = new Publisher();
251+
252+
for (int i = 0; i < 20; i ++)
253+
{
254+
sub = new Subscriper();
255+
publisher.OnChange += sub.WriteLine;
256+
}
257+
Console.ReadKey();
258+
GC.Collect();
259+
GC.WaitForPendingFinalizers();
260+
Console.WriteLine();
261+
Console.WriteLine(publisher.Label);
262+
```
263+
264+
4、[.NET 8 Performance 提高](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/)
265+
266+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/3d45fbe0-fdf0-417e-bceb-9f5ee6c3644c)
267+
268+
一年一度的 `.NET` 形成提升的文章又来了,今年轮到 `.NET 8`。这是一篇接近 20 页的文章,如果你有时间,可以快速浏览一下。
269+
270+
5、[VS for Mac 退役的思考](https://www.youtube.com/watch?v=M7V9ZzxPtVc&ab_channel=IAmTimCorey)
271+
272+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/9c106622-73c9-4902-a0f4-00c6983d1c1c)
273+
274+
前一阵子微软宣布明年将不再支持 `Visual Studio for Mac`, 当时引起了巨大的争论,现在作者分享了它对这件事情的看法
275+
- 为什么微软创建 `.NET`? 答案就是挣钱,不管是买授权,还是和其他产品绑定,或者做咨询甚至是获取声誉,都是为了挣钱。
276+
- `Visual Studio` 并不是一个天生跨平台的软件,有很多 windows 上的依赖。
277+
- `VS Code` 是在 Linux 和 Mac 上很好的选项
278+
- 这个决定看上去太着急了,尤其对于 MAUI 的发展
279+
- 微软需要更多的 `Why`
280+
281+
282+
6、[迁移 ASP.NET 到 ASP.NET Core](https://www.jimmybogard.com/tales-from-the-net-migration-trenches/)
283+
284+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/47a3c336-1ec5-495a-a6dc-82e48a0ef0b0)
285+
286+
这是一些列博客,作者介绍了如何将 `ASP.NET Framework` 的应用程序迁移到 `ASP.NET Core`, 作者采取了一种渐进式的迁移方式。通过一个空的 `ASP.NET Core` 的应用程序,然后将请求转发原本的应用程序,然后逐步迁移其他 Controller.
287+
288+
7、[EF Core 中不同的 load 方式](https://blog.jetbrains.com/dotnet/2023/09/21/eager-lazy-and-explicit-loading-with-entity-framework-core/)
289+
290+
在数据库中,我们通过外键的方式将不同的表链接起来,在 EF Core 中,我们可以通过这种方式来多表查询,主要有三种方式
291+
292+
1. Eager loading
293+
294+
这种方式是通过 `Include` 语句来包含子表
295+
296+
```csharp
297+
var invoices = db.Invoices
298+
.Include(invoice => invoice.InvoiceLines)
299+
.ToList();
300+
// All invoices are already loaded...
301+
foreach (var invoice in invoices)
302+
{
303+
// ...including all their Invoice lines
304+
foreach (var invoiceLine in invoice.InvoiceLines)
305+
{
306+
// ...
307+
}
308+
}
309+
```
310+
311+
312+
2. Lazy loading
313+
这种方式只有在进行对子表属性访问的时候,才会发送查询请求
314+
315+
```csharp
316+
var invoices = db.Invoices
317+
.ToList();
318+
319+
// All invoices are already loaded...
320+
foreach (var invoice in invoices)
321+
{
322+
// ...invoice lines are queried when accessed...
323+
foreach (var invoiceLine in invoice.InvoiceLines)
324+
{
325+
// ...the related product is also queried when accessed
326+
var product = invoiceLine.Product;
327+
328+
// ...
329+
}
330+
}
331+
```
332+
333+
334+
3. Explicit loading
335+
336+
337+
通过 `Load()` 方法显示的获取相应的子表
338+
339+
```csharp
340+
var invoices = db.Invoices
341+
.ToList();
342+
343+
// All invoices are already loaded...
344+
foreach (var invoice in invoices)
345+
{
346+
// ...but you'll have to explicitly load invoice lines when they are needed
347+
db.Entry(invoice).Collection(p => p.InvoiceLines).Load();
348+
349+
foreach (var invoiceLine in invoice.InvoiceLines)
350+
{
351+
// ...
352+
}
353+
}
354+
```

0 commit comments

Comments
 (0)