星期日, 5月 31, 2020

[EF] Code First 變更 Table Schema 觀察

延續該筆記 Entity Framework Code First 產生 Table,好奇 EF Code First 是如何變更 Table Schema,把連線轉向 MS SQL 並利用 SQL Server Profile 來觀察

直接修正 Author Table 的 Name 欄位,利用 Data Annotations 來指定資料欄位和長度

Author Name 欄位為 nvarchar(max)
using System.ComponentModel.DataAnnotations;

public class Author
{
    public int AuthorId { get; set; }
    [Required]
    public string Name { get; set; }
}
修正 Author Name 欄位為 nchar(20)
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

public class Author
{
    public int AuthorId { get; set; }
    [Required]
    [Column(TypeName = "nchar")]
    [MaxLength(20)]
    public string Name { get; set; }
}
SQL Server Profiler 觀察

EF Code First 變更 Table Schema 觀察

星期六, 5月 30, 2020

[EF] Code First 產生 Table

練習 ASP.NET Web API 2 中的屬性路由建立 REST API 時,第一個步驟是利用 EF Code First Migration 來建立 DB 和 Table,重點畫面截圖紀錄一下

建立一個空白 WebAPI 專案

預設是會啟動 HTTPS,練習用就把它取消



建立 Model

Author class
using System.ComponentModel.DataAnnotations;

namespace BooksAPI.Models
{
    public class Author
    {
        public int AuthorId { get; set; }
        [Required]
        public string Name { get; set; }
    }
}
Book Class
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace BooksAPI.Models
{
    public class Book
    {
        public int BookId { get; set; }
        [Required]
        public string Title { get; set; }
        public decimal Price { get; set; }
        public string Genre { get; set; }
        public DateTime PublishDate { get; set; }
        public string Description { get; set; }
        public int AuthorId { get; set; }
        [ForeignKey("AuthorId")]
        public Author Author { get; set; }
    }
}
建立完 Class,請先建置專案,以利後續操作

新增 Web API 控制器

新增 [具有動作、使用 Entity Framework 的 Web API 2 控制器]


假如建立 Model 後,沒有建置專案,新增控制器時會出現下圖錯誤


新增控制器後的專案,可以發現 BooksAPIContext 是放在 Data 資料夾內


透過 Scaffolding 產生的 BooksControl 內容
namespace BooksAPI.Controllers
{
    public class BooksController : ApiController
    {
        private BooksAPIContext db = new BooksAPIContext();

        // GET: api/Books
        public IQueryable GetBooks()
        {
            return db.Books;
        }

        // GET: api/Books/5
        [ResponseType(typeof(Book))]
        public IHttpActionResult GetBook(int id)
        {
            Book book = db.Books.Find(id);
            if (book == null)
            {
                return NotFound();
            }

            return Ok(book);
        }

        // PUT: api/Books/5
        [ResponseType(typeof(void))]
        public IHttpActionResult PutBook(int id, Book book)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            if (id != book.BookId)
            {
                return BadRequest();
            }

            db.Entry(book).State = EntityState.Modified;

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!BookExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return StatusCode(HttpStatusCode.NoContent);
        }

        // POST: api/Books
        [ResponseType(typeof(Book))]
        public IHttpActionResult PostBook(Book book)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            db.Books.Add(book);
            db.SaveChanges();

            return CreatedAtRoute("DefaultApi", new { id = book.BookId }, book);
        }

        // DELETE: api/Books/5
        [ResponseType(typeof(Book))]
        public IHttpActionResult DeleteBook(int id)
        {
            Book book = db.Books.Find(id);
            if (book == null)
            {
                return NotFound();
            }

            db.Books.Remove(book);
            db.SaveChanges();

            return Ok(book);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }

        private bool BookExists(int id)
        {
            return db.Books.Count(e => e.BookId == id) > 0;
        }
    }
}
Migration 功能

套件管理員內輸入指令:Enable-Migrations 來啟用


專案內會出現 Migrations 資料夾


Configuation  內設定 Seed 功能,建立 DB 和 Table 時也一併把資料塞進去
namespace BooksAPI.Migrations
{
    internal sealed class Configuration : DbMigrationsConfiguration
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(BooksAPI.Data.BooksAPIContext context)
        {
            context.Authors.AddOrUpdate(new Author[] {
                new Author() { AuthorId = 1, Name = "Ralls, Kim" },
                new Author() { AuthorId = 2, Name = "Corets, Eva" },
                new Author() { AuthorId = 3, Name = "Randall, Cynthia" },
                new Author() { AuthorId = 4, Name = "Thurman, Paula" }
                });

            context.Books.AddOrUpdate(new Book[] {
                new Book() { BookId = 1,  Title= "Midnight Rain", Genre = "Fantasy",
                PublishDate = new DateTime(2000, 12, 16), AuthorId = 1, Description =
                "A former architect battles an evil sorceress.", Price = 14.95M },

                new Book() { BookId = 2, Title = "Maeve Ascendant", Genre = "Fantasy",
                    PublishDate = new DateTime(2000, 11, 17), AuthorId = 2, Description =
                    "After the collapse of a nanotechnology society, the young" +
                    "survivors lay the foundation for a new society.", Price = 12.95M },

                new Book() { BookId = 3, Title = "The Sundered Grail", Genre = "Fantasy",
                    PublishDate = new DateTime(2001, 09, 10), AuthorId = 2, Description =
                    "The two daughters of Maeve battle for control of England.", Price = 12.95M },

                new Book() { BookId = 4, Title = "Lover Birds", Genre = "Romance",
                    PublishDate = new DateTime(2000, 09, 02), AuthorId = 3, Description =
                    "When Carla meets Paul at an ornithology conference, tempers fly.", Price = 7.99M },

                new Book() { BookId = 5, Title = "Splish Splash", Genre = "Romance",
                    PublishDate = new DateTime(2000, 11, 02), AuthorId = 4, Description =
                    "A deep sea diver finds true love 20,000 leagues beneath the sea.", Price = 6.99M},
            });
        }
    }
}
套件管理員內輸入指令:Add-Migration Initial,Inital 為識別名稱,可自行取名


在 Migration 紀錄內就會出現 Initial 紀錄


套件管理員內輸入:Update-Database


從 Web.config 檔案內可以發現是連線至 LocalDB
<connectionStrings>
    <add name="BooksAPIContext" connectionString="Data Source=(localdb)\MSSQLLocalDB; Initial Catalog=BooksAPIContext-20200529235631; Integrated Security=True; MultipleActiveResultSets=True; AttachDbFilename=|DataDirectory|BooksAPIContext-20200529235631.mdf"
      providerName="System.Data.SqlClient" />
</connectionStrings>
利用 SSMS 來連線 LocalDB 來查詢 DB 和 Table

星期六, 5月 16, 2020

[WebApi] 專案起始動作

學習 WebApi2 時,發現每次啟動都會呼叫瀏覽器,並給出 403,在 Project 屬性內找到設定,可以變更該行為

建立一個只有 WebApi2 的空專案

[WebApi] 專案起始動作-1

一執行就可以看到瀏覽器上拋出 403

[WebApi] 專案起始動作-2

在 Project 屬性 => Web => 起始動作內,可以修改該設定,設定 [不要開啟頁面,等待來自外部外部應用程式的要求],當然還有其他選項可以設定,端看個人需求

[WebApi] 專案起始動作-3

再次執行時,一開始就只有看見診斷工作在那跑

[WebApi] 專案起始動作-4

利用 Postman 打進去,會看見輸出視窗會開始動起來

[WebApi] 專案起始動作-5

星期四, 5月 14, 2020

Godex G530 列印異常

使用者回報 Godex G530 列印異常,碳帶會破裂,輸出的 QRCode 則是一團亂

碳帶破裂

Godex G530 列印異常-1

輸出異常
Godex G530 列印異常-2

找些可能性來測試過後,最後是修改標籤機設定來修正,就不要用預設的 [明暗度] 就解決該問題,該標籤機應該也是差不多了,輸出時超大聲的

Godex G530 列印異常-3

星期六, 5月 09, 2020

[SQL] UNION ALL 發生隱含轉換 2

以前遇到的隱含式轉換都跟資料型別轉換有直接轉換關係,EX:char 轉為 int、bit 轉為 int,但這次是型別沒有變化,是型別大小造成的隱含式轉換 char(11) 被轉為 char(16)

在 TempDB 內建立 tblMaster、tblDetail1、tblDetail2 來模擬實務情況,這三個 Table 都只有一個 NO 欄位,只有 tblDetail2 是開 char(11),其他兩個 Table 都是 char(16)

CREATE TABLE [dbo].[tblMaster]
(
  [NO16] [char](16) NOT NULL, -- char(16)
  CONSTRAINT [PK_tblMaster] PRIMARY KEY CLUSTERED ([NO16] ASC)
)
GO

CREATE TABLE [dbo].[tblDetail1]
(
  [NO16] [char](16) NOT NULL, -- char(16)
  CONSTRAINT [PK_tblDetail] PRIMARY KEY CLUSTERED([NO16] ASC)
)
GO

CREATE TABLE [dbo].[tblDetail2]
(
  [NO11] [char](11) NOT NULL, -- char(11)
  CONSTRAINT [PK_tblDetail2] PRIMARY KEY CLUSTERED([NO11] ASC)
)
GO

模擬實務上 TSQL 語法並觀察執行計畫
SELECT *
FROM tblMaster AS M
  JOIN
    (
      SELECT
        [NO16] AS [NO]
      FROM tblDetail1
      UNION ALL
      SELECT 
        [NO11]
      FROM tblDetail2
    ) AS D ON M.[NO16] = D.[NO]

從執行計畫中可以看出,tblDetail1 是 Index Seek [綠框]、tblDetail2 是 Index Scan [紅框] 來搜尋資料

[SQL] UNION ALL 發生隱含轉換 2-1

SELECT 操作子內的警告

[SQL] UNION ALL 發生隱含轉換 2-2

tblDetail2 Predicate 關於隱含轉換的訊息

[SQL] UNION ALL 發生隱含轉換 2-3

另外測試兩種情況,想驗證看看是否會發生隱含轉換,結論:不會有隱含轉換

測試一:直接對 tblDetail1 和 tblDetail2 UNION ALL
SELECT
  [NO16]
FROM tblDetail1
UNION ALL
SELECT 
  [NO11]
FROM tblDetail2

[SQL] UNION ALL 發生隱含轉換 2-4

測試二:tblMaster 直接和 tblDetail2 JOIN

SELECT *
FROM tblMaster AS M
  JOIN tblDetail2 AS D ON M.[NO16] = D.[NO11]

[SQL] UNION ALL 發生隱含轉換 2-5

以上模擬和測試跟實務結果是一致,記錄一下