假设今天有个状况是这样:有一笔日志,新增第二笔但还没送出前,想将第一笔删除,这时会发生什麽事呢?
竟然出错了!明明只是将要删除的PostId
送到後端去,为什麽会有这样的错误讯息?
这就要说到 C# 的特性了,C# 是物件导向(Object-Oriented Programming, OOP)语言,也就是说任何东西包括资料、方法都能变成物件,Blog
、Post
就是一个个物件,除了物件这种参考型别,也有单纯的int
、bool
等实质型别。
(注:string
分类上是参考型别,但语法上却是实质型别,这是为了避免无数的 string 塞爆记忆体。)
实质型别的意思是:两个实质型别之间的异动不会影响彼此。定义一个变数int a = 0;
,再定义int b = a;
,b
等於 0 这没问题,这时候如果再赋值b = 3;
,a
跟b
就不相等了,彼此间不会影响对方。下图用 LINQPad 示范,Dump()
的意思是将该变数显示在下方Results
区块,可以看到即便中间改动b
的值,a
也不受影响。
参考型别则是:B 物件如果来自 A 物件,不论哪个物件变动,另一个就会跟着变动。可以看到下图在12行将B
物件的Title
改为"BB"
,结果A
物件的Title
也跟着变了。
那这些跟Blog
有什麽关系呢?我们看後端BlogRepository.cs
的GetBlog()
,可以看到这边将blog
回传,前端BlogBase.razor.cs
这边接起来後,一旦触发add()
就会在Blog.Posts
新增一笔PostModel
。
前端按下Delete
按钮後,後端PostRepository.cs
的DeletePost()
这边会触发SaveChanges()
,这时候的Blog.Posts
会有一笔没有Blog
、Title
跟Content
的PostModel
,这笔根本还没按过Submit
按钮经由後端存到资料库,是只存在於前端的资料,但是触发SaveChanges()
的时候却试图将这笔资料存进资料库,Title
跟Content
是不能为null
的,自然就出错了。
另外如果单纯将资料库的Posts
捞出来,是看不到那一笔资料的,因为那是跟着Blog
的PostModel
。
要解决这问题有几种方法,第一种是将Blog
跟Post
完全拆开,两者各有自己的前端画面,不过如果现实情况的专案遇到这种坑 (没错,这是笔者给自己挖的坑…),往往不会有时间做这种重构。
第二种方法是当後端PostRepository.cs
收到没有Title
的PostModel
时,回传提示讯息。
前端PostBase.razor.cs
改以deleted.IsSuccess
判断,删除成功则将PostId
传给Blog
将该笔Post
从画面删除,失败的话提示失败的原因。
虽然以工程师的角度来看这样避免了错误,但以 UX (User Experience) 角度来看根本就是莫名其妙,为什麽删除一笔日志还要限制不能有空的日志?所以就要用第三种方法。
第三种是建立 ViewModel,画面的CRUD都针对 ViewModel 处理,之後才一一 Mapping 回去 Model。
所谓的 ViewModel 是指不存在於资料库但又希望呈现在画面上的栏位,例如有张 table Employee
里面有两个栏位FirstName
跟LastName
,存进资料库时分开存,但显示时希望动些手脚 (例如要组合起来且全大写),可以把两个栏位都丢到前端後再处理,由使用者的浏览器分担,也可以先在後端处理好再用 ViewModel 承接丢到前端。
另一个例子是信用卡,table CreditCard
存有使用者的信用卡号、三位数认证码、出生年月日,大家应该常常网购,刷卡时会让使用者看到信用卡末四码,这种机密隐私资料总不可能 16 码都丢到前端处理吧?这时就需要在後端处理後再由 ViewModel 传到前端了。
我们先建立 BlogViewModel
跟 PostViewModel
,因为是 ViewModel 所以不需要用跟资料库相关的[Key]
attribute,有使用到Model
的地方都改成ViewModel
。
接着修改後端BlogRepository.cs
,画面呈现改成 ViewModel,资料存取沿用 Model,可以看到 28 到 48 行手动做 Mapping。
PostRepository.cs
的CreatePost()
也是一样,DeletePost()
则把原本的else
区块对Blog.Posts
的判断移除。
BlogBase.razor.cs
跟PostBase.razor.cs
把原本用到的 Model 改成 ViewModel。
这时候来建立新资料,不过建立第二笔後紧接着要删除第二笔,却发生找不到 Post的问题,这是为什麽?
原来第二笔虽然进入资料库了,但我们没有重新将资料捞回来,画面的Blog.Posts
第二笔的PostId
仍然是0。
为了让Blog.Posts
知道要重捞资料库,我们要在PostBase.razor.cs
新增EventCallback
,告知BlogBase.razor.cs
再执行一次loadData()
,因为是告知而已,就不用传<TValue>
。
然後在新增第二笔之後立刻删除,就会正常了。新增第二笔後再新增第三笔,删除第二笔也会正常。
(注:如果看到下图的错误讯息,有可能是 Visual Studio 的问题,先试试重开Visual Studio。)
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
Introduction to embedded system Components and app...
https://typeorm.io/#/repository-api 关於 save() \ de...
因为专案刚好需要用到 所以纪录一下参数在哪边 纪录一下 使用Visual Studio Entity...
Data Structure How to manipulate data? Data struc...
前言 最近练习切版,遇到这种排版时就开始算尺寸 想着要怎麽切才好(思考) flex、float、in...