Day 18 修改add()方法

假设今天有个状况是这样:有一笔日志,新增第二笔但还没送出前,想将第一笔删除,这时会发生什麽事呢?

竟然出错了!明明只是将要删除的PostId送到後端去,为什麽会有这样的错误讯息?
https://ithelp.ithome.com.tw/upload/images/20210918/20140893JdJcKcNZWZ.png

这就要说到 C# 的特性了,C# 是物件导向(Object-Oriented Programming, OOP)语言,也就是说任何东西包括资料、方法都能变成物件,BlogPost就是一个个物件,除了物件这种参考型别,也有单纯的intbool等实质型别。
(注:string分类上是参考型别,但语法上却是实质型别,这是为了避免无数的 string 塞爆记忆体。)

实质型别的意思是:两个实质型别之间的异动不会影响彼此。定义一个变数int a = 0;,再定义int b = a;b等於 0 这没问题,这时候如果再赋值b = 3;ab就不相等了,彼此间不会影响对方。下图用 LINQPad 示范,Dump()的意思是将该变数显示在下方Results区块,可以看到即便中间改动b的值,a也不受影响。
https://ithelp.ithome.com.tw/upload/images/20210918/20140893KPklBKGUNa.png

参考型别则是:B 物件如果来自 A 物件,不论哪个物件变动,另一个就会跟着变动。可以看到下图在12行将B物件的Title改为"BB",结果A物件的Title也跟着变了。
https://ithelp.ithome.com.tw/upload/images/20210918/2014089345Lz65nHTZ.png

那这些跟Blog有什麽关系呢?我们看後端BlogRepository.csGetBlog(),可以看到这边将blog回传,前端BlogBase.razor.cs这边接起来後,一旦触发add()就会在Blog.Posts新增一笔PostModel
https://ithelp.ithome.com.tw/upload/images/20210918/20140893bzq52KO27t.png
https://ithelp.ithome.com.tw/upload/images/20210918/20140893dneDfnL216.png

前端按下Delete按钮後,後端PostRepository.csDeletePost()这边会触发SaveChanges(),这时候的Blog.Posts会有一笔没有BlogTitleContentPostModel,这笔根本还没按过Submit按钮经由後端存到资料库,是只存在於前端的资料,但是触发SaveChanges()的时候却试图将这笔资料存进资料库,TitleContent是不能为null的,自然就出错了。
https://ithelp.ithome.com.tw/upload/images/20210918/20140893CvJ9C9aj61.png
https://ithelp.ithome.com.tw/upload/images/20210918/20140893x7tgJZV3xf.png

另外如果单纯将资料库的Posts捞出来,是看不到那一笔资料的,因为那是跟着BlogPostModel
https://ithelp.ithome.com.tw/upload/images/20210918/20140893RjKJlgLIXk.png

要解决这问题有几种方法,第一种是将BlogPost完全拆开,两者各有自己的前端画面,不过如果现实情况的专案遇到这种坑 (没错,这是笔者给自己挖的坑…),往往不会有时间做这种重构。

第二种方法是当後端PostRepository.cs收到没有TitlePostModel时,回传提示讯息。
https://ithelp.ithome.com.tw/upload/images/20210918/20140893jPw1FSYYie.png

前端PostBase.razor.cs改以deleted.IsSuccess判断,删除成功则将PostId传给Blog将该笔Post从画面删除,失败的话提示失败的原因。
https://ithelp.ithome.com.tw/upload/images/20210918/2014089336EjhwuELS.png
https://ithelp.ithome.com.tw/upload/images/20210918/20140893WvMQi4BvH1.pnghttps://ithelp.ithome.com.tw/upload/images/20210918/20140893g4BcSGxV4l.pnghttps://ithelp.ithome.com.tw/upload/images/20210918/20140893plqzuDLxih.png

虽然以工程师的角度来看这样避免了错误,但以 UX (User Experience) 角度来看根本就是莫名其妙,为什麽删除一笔日志还要限制不能有空的日志?所以就要用第三种方法。

第三种是建立 ViewModel,画面的CRUD都针对 ViewModel 处理,之後才一一 Mapping 回去 Model。

所谓的 ViewModel 是指不存在於资料库但又希望呈现在画面上的栏位,例如有张 table Employee里面有两个栏位FirstNameLastName,存进资料库时分开存,但显示时希望动些手脚 (例如要组合起来且全大写),可以把两个栏位都丢到前端後再处理,由使用者的浏览器分担,也可以先在後端处理好再用 ViewModel 承接丢到前端。

另一个例子是信用卡,table CreditCard存有使用者的信用卡号、三位数认证码、出生年月日,大家应该常常网购,刷卡时会让使用者看到信用卡末四码,这种机密隐私资料总不可能 16 码都丢到前端处理吧?这时就需要在後端处理後再由 ViewModel 传到前端了。

我们先建立 BlogViewModelPostViewModel,因为是 ViewModel 所以不需要用跟资料库相关的[Key]attribute,有使用到Model的地方都改成ViewModel
https://ithelp.ithome.com.tw/upload/images/20210918/20140893RBojTbfITT.pnghttps://ithelp.ithome.com.tw/upload/images/20210918/201408935nDTwSmvNN.png

接着修改後端BlogRepository.cs,画面呈现改成 ViewModel,资料存取沿用 Model,可以看到 28 到 48 行手动做 Mapping。
https://ithelp.ithome.com.tw/upload/images/20210918/20140893kijKXPY7EM.pnghttps://ithelp.ithome.com.tw/upload/images/20210918/20140893W9GNQCFNOR.png

PostRepository.csCreatePost()也是一样,DeletePost()则把原本的else区块对Blog.Posts的判断移除。
https://ithelp.ithome.com.tw/upload/images/20210918/201408931NKUN2tbvV.pnghttps://ithelp.ithome.com.tw/upload/images/20210918/20140893jPCnJ5g1Lt.png

BlogBase.razor.csPostBase.razor.cs把原本用到的 Model 改成 ViewModel。
https://ithelp.ithome.com.tw/upload/images/20210918/20140893QajIbq8UWT.pnghttps://ithelp.ithome.com.tw/upload/images/20210918/20140893L32mxV5cPU.pnghttps://ithelp.ithome.com.tw/upload/images/20210918/20140893Ve4vTDJPIc.png

这时候来建立新资料,不过建立第二笔後紧接着要删除第二笔,却发生找不到 Post的问题,这是为什麽?
https://ithelp.ithome.com.tw/upload/images/20210918/20140893KRzRhYl1vo.png

原来第二笔虽然进入资料库了,但我们没有重新将资料捞回来,画面的Blog.Posts第二笔的PostId仍然是0。
https://ithelp.ithome.com.tw/upload/images/20210918/20140893R8gcZlQGOt.png

为了让Blog.Posts知道要重捞资料库,我们要在PostBase.razor.cs新增EventCallback,告知BlogBase.razor.cs再执行一次loadData(),因为是告知而已,就不用传<TValue>
https://ithelp.ithome.com.tw/upload/images/20210918/20140893i9aBOJbfF3.png
https://ithelp.ithome.com.tw/upload/images/20210918/20140893KpPKmWy6Oi.pnghttps://ithelp.ithome.com.tw/upload/images/20210918/2014089302zsZxwxUI.pnghttps://ithelp.ithome.com.tw/upload/images/20210918/20140893Vn2RcWbChg.png

然後在新增第二笔之後立刻删除,就会正常了。新增第二笔後再新增第三笔,删除第二笔也会正常。

(注:如果看到下图的错误讯息,有可能是 Visual Studio 的问题,先试试重开Visual Studio。)
https://ithelp.ithome.com.tw/upload/images/20210918/20140893aiEm5L6jZr.png

Ref: .NET Stack and Heap

Ref: In C#, why is String a reference type that behaves like a value type?

Ref: What is ViewModel in MVC?

Ref:Understanding ViewModel in ASP.NET MVC


<<:  [Day 3] php阵列与资料结构

>>:  JavaScript型别、物件与纯值

Day 09 Summary

Introduction to embedded system Components and app...

TypeOrm | Repository APIs 用法纪录 3

https://typeorm.io/#/repository-api 关於 save() \ de...

POCO_设定所有Table继承同一个class

因为专案刚好需要用到 所以纪录一下参数在哪边 纪录一下 使用Visual Studio Entity...

[DSA] Overview: Complexity Analysis

Data Structure How to manipulate data? Data struc...

【心得】你今天种菜了吗? grid之路-grid的使用(1)

前言 最近练习切版,遇到这种排版时就开始算尺寸 想着要怎麽切才好(思考) flex、float、in...