花了一點時間才理解這個流程。
簡述
不知道你有沒有和我一樣,曾經想要對 Table 新增欄位,所以就直接這樣子做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 'use strict'; module.exports = { async up(queryInterface, Sequelize) { await queryInterface.createTable('Categories', { id: { allowNull: false, autoIncrement: true, primaryKey: true, type: Sequelize.INTEGER }, name: { type: Sequelize.STRING }, isDeleted: { type: Sequelize.BOOLEAN }, createdAt: { allowNull: false, type: Sequelize.DATE }, updatedAt: { allowNull: false, type: Sequelize.DATE } }); }, async down(queryInterface, Sequelize) { await queryInterface.dropTable('Categories'); } };
|
這時候你去執行 npx sequelize-cli db:migrate
會發現沒有用:
1 2 3
| Loaded configuration file "config\config.json". Using environment "development". No migrations were executed, database schema was already up to date.
|
這種時候該怎麼辦?你有兩條路可以走:
- 執行
db:migrate:undo
,接著再重新跑一次 db:migrate
- 執行
db:migration:create
,寫一個新的 migration 來處理
這兩個差在第一種會直接把 Table Drop 掉(通常是這樣,但實際上看內容決定),你原本存的資料就直接沒了,而第二種就是為了避免這個問題的解決方案,也是我這篇要講的解法。
假設我目前的 Table 長這樣:
現在我想對新增一個 isDeleted
欄位,所以就照剛剛說的,我先下指令來生一個新的 Migration 出來:
1 2
| sequelize migration:create --name 'add-isDeleted-to-category'
|
接著產生的檔案內容會是這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 'use strict';
module.exports = { async up (queryInterface, Sequelize) {
},
async down (queryInterface, Sequelize) {
} };
|
簡單來說,就是在裡面寫你想執行的操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 'use strict';
module.exports = { async up (queryInterface, Sequelize) { await queryInterface.addColumn( 'Categories', 'isDeleted', { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: false } ) },
async down (queryInterface, Sequelize) { await queryInterface.removeColumn('Categories', 'isDeleted'); } };
|
記得 up
和 down
之間要有對應關係,我在 up
裡做新增,down
就應該該做移除,這樣之後跑指令的時才不會出錯。
接下來就跟一開始一樣,下指令去跑 migration:
1
| npx sequelize-cli db:migrate
|
這樣就完美的保留資料,也新增了欄位,可喜可賀,可喜可賀。
之後如果又不想要這個欄位的話也很簡單,只要執行:
1
| npx sequelize-cli db:migrate:undo --name 20220331082106-add-isDeleted-to-category.js
|
就大功告成啦。
但是這樣還沒有完成,還有一件事情得做
故意用這麼大的字就是因為這件事很重要,絕對不能忘記。
總而言之,不要忘記 Model 的存在,當你用 JS 來操作資料庫時都是透過 Model 來執行的,所以雖然你剛剛透過 Migration 在 Table 新增了欄位,但這並不代表 Model 的資料結構會跟著自動更新,所以請務必記得:
- 跑完 Migration 後要再去重新設定 Model 的資料結構
- 跑完 Migration 後要再去重新設定 Model 的資料結構
- 跑完 Migration 後要再去重新設定 Model 的資料結構
這很重要,真的。
如果你沒有這樣做的話,就會有這個問題:
1 2 3 4 5 6 7
| const { Category } = require('./models');
~async function () { const categories = await Category.findAll(); console.log('result', JSON.stringify(categories, null, 4)); }()
|
輸出結果:
1 2 3 4 5 6 7 8 9 10 11 12 13
| Executing (default): SELECT `id`, `name`, `createdAt`, `updatedAt` FROM `Categor ies` AS `Category`;
result [ { "id": 1, "name": "JavaScript", "createdAt": "2021-03-31T00:00:00.000Z", "updatedAt": "2021-03-31T00:00:00.000Z" } ]
|
所以再說一次,記得去改 Model 的資料結構:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 'use strict'; const { Model } = require('sequelize'); module.exports = (sequelize, DataTypes) => { class Category extends Model { static associate(models) { Category.hasMany(models.Post); } } Category.init({ name: DataTypes.STRING, isDeleted: DataTypes.BOOLEAN, }, { sequelize, modelName: 'Category', }); return Category; };
|
一定要確保 Model 跟資料庫的狀態是同步的,在用的時候才不會出問題。
附註
如果有改 Migration 的名稱的話,記得到資料庫的 sequelizemeta 把原本的名稱也改掉,不然執行指令時會出錯。
想知道其他操作可以參考 這篇,主要就是換個 method 來執行而已,基本結構都大同小異。