TypeORMに触れてみる 1
はじめに
久々すぎて書き方も忘れつつある今日このごろ。 プライベートでは TypeScript ばかり触れているわけなのですが、今回は TypeORM を試すことにしました。
あらかじめ DB(テーブル)を用意する方法もありますが、今回はコードから生成してみます。
- GitHub - typeorm/typeorm
- TypeORM - Amazing ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases. Works in NodeJS, Browser, Ionic, Cordova and Electron platforms.
インストールとプロジェクトの作成
インストール
もちろん TypeScript や TypeORM 、 PostgreSQL のドライバーは必要なのですが、それに加えて "reflect-metadata" が必要になります。
npm install --save typeorm reflect-metadata pg typescript tsc npm install --save-dev @types/node
あと必須ではありませんが、 JavaScript に変換せずに TypeScript そのままで実行できるよう、"ts-node" も入れておきます。
npm install --save ts-node
準備
TypeORM のコマンドでプロジェクトを作ることもできます。
npx typeorm init --name gen-typeorm-sample --database postgres
今回はこれで生成されるファイルを参考に、順を追ってプロジェクトを作成してみます。
フォルダーとファイルの追加
とりあえず tsconfig.json を作っておきます。
npx tsc --init
lib やデコレーターの有効化などを行います。
tsconfig.json
{ "compilerOptions": { /* Basic Options */ "incremental": true, "target": "es5", "module": "commonjs", "lib": [ "es5", "es6" ], "sourceMap": true, "outDir": "./build", /* Strict Type-Checking Options */ "strict": true, "noImplicitAny": true, "strictNullChecks": true, "alwaysStrict": true, /* Additional Checks */ "noUnusedLocals": true, /* Module Resolution Options */ "moduleResolution": "node", "esModuleInterop": true, /* Experimental Options */ "experimentalDecorators": true, "emitDecoratorMetadata": true, /* Advanced Options */ "skipLibCheck": true, "forceConsistentCasingInFileNames": true } }
で、 TypeORM で使用するファイルを格納するフォルダーを追加しておきます。
typeorm-sample(my project name) L node_modules L src L entity L migration L package-lock.json L package.json L tsconfig.json
PostgreSQL の接続文字列などを設定する、 ormconfig.json を追加します。
ormconfig.json
{ "type": "postgres", "host": "localhost", "port": 5432, "username": "test", "password": "test", "database": "test", "synchronize": true, "logging": true, "cache": false, "entities": [ "src/entity/**/*.ts" ], "migrations": [ "src/migration/**/*.ts" ], "subscribers": [ "src/subscriber/**/*.ts" ], "cli": { "entitiesDir": "src/entity", "migrationsDir": "src/migration", "subscribersDir": "src/subscriber" } }
※テーブルはコードから生成できますが、データベースは先に作っておく必要があります。
というのを忘れて実行したせいでエラーになりました\(^o^)/
Entity クラスを追加する
sample-user.ts
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm"; @Entity("SampleUser") export class SampleUser { @PrimaryGeneratedColumn() id: number = -1; @Column() firstName: string = ''; @Column() lastName: string = ''; @Column() age: number = -1; }
テーブル名はファイル名から決定されます。
そのため、今回のサンプルでそのまま実行するとテーブル名は "sample_user" となります。
@Entity() に "SampleUser" のように設定することで、テーブル名を指定できます。
余談ですが、 TypeORM のコマンドでプロジェクトを生成すると "user" というテーブルが作られることになるのですが、 "SELECT * FROM user" としてしまうと PostgreSQL のユーザー情報が出てきてしまうので、コマンドで作る場合もテーブルの名前は変えたほうが良いかもしれません。
テーブルを作成する
テーブルは最初にデータベースのテーブルにアクセスしたときに作成されます。
[2020-06-05 Update] テーブルが作成されるのはデータベースに接続したとき、でした。。。
index.ts
import "reflect-metadata"; import {createConnection} from "typeorm"; import { SampleUser } from "./entity/sample-user"; createConnection().then(async connection => { const user = new SampleUser(); user.firstName = "Timber"; user.lastName = "Saw"; user.age = 25; // テーブルが生成される. await connection.manager.save(user); // 上のコードがない場合、ここで空のテーブルが生成される. const users = await connection.manager.find(User); console.log("Loaded users: ", users); }).catch(error => console.log(error));
Primary key(自動採番)
"@PrimaryGeneratedColumn()" を指定すると、カラムの型は "serial" になり自動採番されます。
注意が必要なのは、この値(今回のサンプルでは "id" )は自分で値を変えることができない、ということです。
Create のときは "id" に入れた値が無視され、 Update のときは "id" の値を変えると別物として扱われます(新規登録される)。
NOT NULL
デフォルトでは "NOT NULL" としてカラムの型が設定されます。
Nullable にする必要がある場合は @Column() にオプションを追加します。
sample-user.ts
@Entity("SampleUser") export class SampleUser { ... @Column({ nullable: true }) age: number = -1; }
@Column() で指定できるオプションはデフォルト値や型などいろいろな種類があります。 typeorm/entities.md at master · typeorm/typeorm · GitHub
また、 PostgreSQL で使用できる型の種類はこちらから。
typeorm/entities.md at master · typeorm/typeorm · GitHub
トランザクションを利用する
トランザクションを利用するのはどうすれば良いでしょうか。
調べてみるといくつか方法があるようですが、今回は QueryRunner を使うことにしました。
index.ts
import "reflect-metadata"; import {createConnection } from "typeorm"; import { SampleUser } from "./entity/sample-user"; createConnection().then(async connection => { const queryRunner = connection.createQueryRunner(); await queryRunner.startTransaction(); try{ const secondUser = new SampleUser(); secondUser.firstName = 'Hello2'; secondUser.lastName = 'World2'; secondUser.age = 36; await queryRunner.manager.save(secondUser); queryRunner.commitTransaction(); }catch(error) { await queryRunner.rollbackTransaction(); } finally { await queryRunner.release(); } }).catch(error => console.log(error));
注意点として、 "queryRunner.startTransaction()" や "queryRunner.rollbackTransaction()" は "connection.manager" には効かない、ということです。 上記の "queryRunner.manager.save(secondUser)" を "connection.manager.save(secondUser)" にしても動作はしますが、途中で例外が発生してもロールバックはされません。
続きます。
Babelを使ってみようとしたらハマった話とドラッグで要素を動かしてみたい話
はじめに
投稿が大変遅れましたが、この記事は JavaScript Advent Calendar 2019 の13日目の記事です。
要素をドラッグして動かしたい。
せっかくなので JavaScript で書いてみよう。
と思ったら Babel ハマったという話です。
Babel と Webpack を使う
TypeScript で書いていたときは、(Promise などを除いて) IE でも動くようトランスパイルしてくれていました。
じゃあ JavaScript では?というと、いくつか方法はあると思うのですが、今回は Babel を使ってみることにしました。
このときと同じく、 Webpack も使います。
インストール
インストール、セットアップは下記ドキュメント( Build system として Webpack を選択)に従えば OK でした。
Babel 、 Webpack に加え、ドラッグで要素を移動させるときに使用する RxJs も追加しておきます。
npm install --save webpack webpack-cli rxjs babel-loader @babel/core @babel/preset-env
設定ファイル
Babel ver.7 では .babelrc というファイルを設定ファイルとして使用するようです(ググってると babel.config.js とか出てきて混乱しました)。
.babelrc
{ "presets": ["@babel/preset-env"], }
サンプルコード(失敗)
MainPage.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Hello</title> </head> <body> <button onclick="Page.doSomething()">Click</button> </body> <script src="../js/main.bundle.js"></script> </html>
mainPage.js
import { AsyncSample } from "./asyncSample"; export async function doSomething() { const s = new AsyncSample(); await s.doSomethingAsync(); }
asyncSample.js
export class AsyncSample { async doSomethingAsync() { return new Promise((resolve, reject) => { console.log('Hello World!'); resolve(); }) .catch((reason) => reject(reason)); } }
webpack.config.js
var path = require('path'); module.exports = { mode: 'development', entry: { 'main': './src/js/mainPage.js', }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } ] }, resolve: { extensions: [ '.js' ] }, output: { filename: '[name].bundle.js', path: path.resolve(__dirname, './dist/js'), library: 'Page', libraryTarget: 'umd' } };
そしてエラー
npx webpack -w とやるとコンパイルは成功します。
で、ボタンを押すとエラー発生と。
ReferenceError: regeneratorRuntime is not defined
regeneratorRuntime ってなんぞ?というと、async/await を Babel でトランスパイルするときに使われるもののようです。
なんとかする
で、これが足りないのでどうするか。
ぐぐると @babel/polyfill の話( ver.7.4? で Deprecated )が大量に出てきてこれまた混乱するのですが、今は regenerator-runtime と core-js を使うようです。
npm install --save regenerator-runtime core-js
どう使うのか、というと、 async/await や Promise を使うところで import します。
mainPage.js
import { AsyncSample } from "./asyncSample"; import "regenerator-runtime/runtime"; export async function doSomething() { const s = new AsyncSample(); await s.doSomethingAsync(); }
asyncSample.js
import "core-js"; import "regenerator-runtime/runtime"; export class AsyncSample { async doSomethingAsync() { return new Promise((resolve, reject) => { console.log('Hello World!'); resolve(); }) .catch((reason) => reject(reason)); } }
直接使っているわけでないものをインポートするのはしっくりこない気はしますが、別途 Polyfill を入れなくても Promise などに対応してくれるのはありがたいですね。
※ここより先は後ほど追記します(..)_
Angular で PWA に挑戦したい話
はじめに
※2019/12/06 一旦書き終わったところだけ公開いたします。
残りは仕事が終わったあと追記予定です(..)_
この記事は PWA Advent Calendar 2019 の 6 日目の記事です。
JSConf China 2019 で聴いて以来気になってはいた PWA(Progressive Web Application)。
Advent Calender に勢いで参加してみたは良いものの、思った以上に手が回らなかったので今回はとりあえず作って動かしてみる、ということで。
せっかくなので SheetJS を使って Excel 読み込み→表示をしてデスクトップアプリ感?を楽しんでみたいと思います。
インストール
とりあえず雑にプロジェクトを作りまして。。。
ng new pwa-xlsx-sample
ng add で PWA 用のパッケージを追加してもらう、と。
ng add @angular/pwa --project pwa-xlsx-sample
更に SheetJS を追加しておきます。
npm install xlsx --save
で ng s とかすればよいのかしら?と思ったらそうではなく、ビルドしてサーバー上で動かす( ng s ではなく)必要があるようです。
npm install http-server --save ng build --prod
ようやく?準備ができたので、早速実行してみます。
npx http-server -p 8080 -c-1 dist/pwa-xlsx-sample
PC の Chrome などではどこからたどれば良いのかわからなかったのですが、 Android の Chrome からは Home に追加することも確認できました。
PWA 完全に理解した。
【TypeScript】type の使いどころが知りたい話
はじめに
※2020/04/12
お前の仕事はいつまで続くんだという話ですが、ようやく書き終わりました。。。
この記事は TypeScript Advent Calendar 2019 の 6 日目の記事です。
ふとコードを書いていて気になったのが、例えばサーバー側から受け取った値を JSON に変換したいとき、 class を使うべき? interface を使うべき? それとも type ?ということでした。
C# であれば class を選択することになります。
では TypeScript の場合は?
というか、 C# にも存在する class と interface はともかく、 type って何よ?どう使うの?となったので調べてみることにしました。
type について
まず type (正確には Type Aliases) とは何か、という話から。
alias (別名)の名前通り、型に対する別名をつけるためのものです。
使い方はこんな感じです。
type SampleType = string; let sample: SampleType = "hello"; type SampleType2 = { id: number, name: string, } let sample2: SampleType2 = { id: 0, name: "world", }
interface と type aliases
共通点
よく比較される interface と type aliases ですが、実際使い方によっては全く区別がつかない場合もあります。
interface SampleInterface { id: number; name: string; } let sample3: SampleInterface = { id: 1, name: "!!!", }
どちらも JavaScript への変換後は消えてしまうためあくまで TypeScript 内での振る舞いに留まる点や、同じプロパティを持っていればその interface / type aliases として扱われる点など。
よく言われる(?)のが、(type aliases は継承ができないため)継承が必要なら interface を使う、という内容。
ver.3.8.3 現在、Classが実装する(implements)場合はどちらも可能です。
interface ISample{ name: string; } type TSample = { message: string }; /** OK */ class IClassSample implements ISample { public name: string = 'Hello' } /** OK */ class TClassSample implements TSample { public message: string = 'World' }
interface / type aliases で違いはあるのか?その使い分けは?というのが気になったので調べてみることにしました。
(口調がいかがでしたかブログっぽいな)
違い
declaration merging
interface の大きな特徴(だと思う)は "declaration merging" です。
同じ namespace 、同じファイル、同じモジュール内に同名の interface がある場合、ひとまとまりの interface として扱われます。
interface ISample{ name: string; } interface ISample{ message: string; } function main() { // 1つ目、2つ目の ISample が統合される. const iSample: ISample = { name: 'Hello', message: 'World' }; }
同様のことは type alias で Cross 型を使って再現することはできますが、明示的に書く必要があります。
interface ISample{
name: string;
}
type TSample = {
message: string
};
type TCrossSample = ISample & TSample
function main() {
const tSample: TCrossSample = {
name: 'Hello',
message: 'World'
};
}
extends
これも interface のみの特徴ですが、 interface は interface や type aliases を拡張 (extends) できます。
interface ISample{
name: string;
}
type TSample = {
message: string
};
interface IExtendSample extends ISample, TSample {
id: number;
}
function main() {
const iSample: IExtendSample = {
id: 0,
name: 'Hello',
message: 'World'
};
}
これも同じく type alias で Cross 型を使って再現できます。
定義できる型
interface で定義できるのは下記のような形式のみです。
(propertyName はなくてもOK)
interface InterfaceName {
propertyName: Type
};
type aliases はあくまで型に別名をつけるだけ、ということもあってか、色々な種類の型を定義できます。
interface ISample{
name: string;
}
type TSample = {
message: string
};
/** Union type */
type TUnionSample = ISample|TSample;
/** Cross type */
type TCrossSample = ISample & TSample & { id: number; }
/** Tuple */
type TTupleSample = [string, number];
/** Function */
type TFunctionSample = (message: string) => void;
/** Object */
type TObjectSample = {
message: string
};
/** Mapped type */
type TBase = {
id: number,
name: string
};
type TMappedSample = { [P in keyof TBase]: string; };
/** Conditional type */
type TConditionalSample = T extends string? 'string': 'other';
function main() {
const crossSample: TCrossSample = {
id: 0,
name: 'Hello',
message: 'World'
};
const tupleSample: TTupleSample = ['hello', 0];
const mappedSample: TMappedSample = {
id: '0',
name: 'Hello'
};
const conditionalSample: TConditionalSample = 'string';
}
Union 型、 Tuple は type aliases のみで定義できるため、 interface は使えません。
いつ type aliases を使うべきか?
interface と type aliases の違いはわかった気がしますが、 ではいつ type aliases を使うか。
(どちらも使用可能な場合)
Effective TypeScript によると、プロジェクトのスタイルに合わせる・または "declaration merging" が必要かどうかで判断するべき、と。
個人的には C# に慣れていることもあり、振る舞い(メソッド)を定義して Class で実装する場合は interface 、Data Transfer Object の用に、データを格納するためのオブジェクトは type aliases を使いたい気がします。
参照
ASP.NET Core + Entity Framework Core のプロジェクトを 2.2 から 3.0 にアップグレードした話
はじめに
この記事は C# Advent Calendar 2019 の二日目の記事です。
冷静に考えるとこれを C# の話として書いていいのか?と今更ながら思ったりもするのですが、プロジェクトは C# で書いてるし、 C# に関連する話もないではないから許してください(..)_
環境
- .NET Core : ver.2.2.402 (from), ver.3.0.100 (to)
- Microsoft.EntityFrameworkCore : ver.2.2.6 (from), ver.3.0.0 (to)
- Npgsql.EntityFrameworkCore.PostgreSQL : ver.2.2.4 (from), ver.3.0.1 (to)
- Windows10 : ver.1903
- Rider : ver.2019.2.3
- PostgreSQL : ver.12.0
元のプロジェクト
まずはアップグレードする前のプロジェクトを用意します。
UpgradeSample.csproj
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp2.2</TargetFramework> <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel> <RuntimeIdentifier>win-x86</RuntimeIdentifier> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.App" /> <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.6" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.2.4" /> </ItemGroup> </Project>
- RuntimeIdentifier は Self-contained app (実行環境に .NET Core が入っていなくても動作するよう出力されたアプリケーション) のために追加しています。
- Program.cs も存在しますが、特にプロジェクト作成時から変更なく、 3.0 にアップグレード後も触らないため省略しています。
Startup.cs
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Serialization; using UpgradeSample.Models; using UpgradeSample.Products; namespace UpgradeSample { public class Startup { private IConfigurationRoot Configuration { get; } public Startup(IHostingEnvironment env) { // config ファイル読み込み. var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", false, true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", false, true) .AddEnvironmentVariables(); Configuration = builder.Build(); } public void ConfigureServices(IServiceCollection services) { // DB Connect services.AddDbContext<UpgradeSampleContext>(options => options.UseNpgsql(Configuration["DbConnect"])); // 生成される JSON のプロパティ名を大文字始まりで出力する. services.AddMvc() .AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new DefaultContractResolver(); }); // DI services.AddScoped<IProductService, ProductService>(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseStaticFiles(); app.UseMvc(); } } }
ApiController.cs
using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using UpgradeSample.Models; using UpgradeSample.Products; namespace UpgradeSample.Controllers { public class ApiController: Controller { private readonly IProductService _productService; public ApiController(IProductService productService) { _productService = productService; } [Route("/")] [Route("/Home")] public string Index() { return "hello"; } [HttpGet] [Route("/products")] public async Task<List<Product>> GetProducts([FromQuery] string[] names) { return await _productService.GetProductsAsync(names); } } }
Product.cs
using System.ComponentModel.DataAnnotations.Schema; using Newtonsoft.Json; namespace UpgradeSample.Models { [Table("Product")] public class Product { [Column("ProductId")] [JsonProperty("ProductId")] public int? ProductId { get; set; } [Column("ProductName")] [JsonProperty("ProductName")] public string ProductName { get; set; } } }
UpgradeSampleContext.cs
using Microsoft.EntityFrameworkCore; namespace UpgradeSample.Models { public class UpgradeSampleContext: DbContext { public UpgradeSampleContext(DbContextOptions<UpgradeSampleContext> options) :base(options) { } public DbSet<Product> Products { get; set; } } }
IProductService.cs
using System.Collections.Generic; using System.Threading.Tasks; using UpgradeSample.Models; namespace UpgradeSample.Products { public interface IProductService { Task<List<Product>> GetProductsAsync(string[] names); } }
ProductService.cs
using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using UpgradeSample.Models; namespace UpgradeSample.Products { public class ProductService: IProductService { private readonly UpgradeSampleContext _context; public ProductService(UpgradeSampleContext context) { _context = context; } public async Task<List<Product>> GetProductsAsync(string[] names) { return await _context.Products .Where(p => names.Length <= 0 || names.Any(n => p.ProductName == n)) .ToListAsync(); } } }
いかん。。。元のコードを並べるだけで結構スペースを取ってしまった。。。
3.0 にアップグレードする
気を取り直して早速 3.0 にアップグレードしてみますよ。
プロジェクトのバージョンを更新するには、 1 つまたは 2 つのファイルを変更する必要があります。
global.json
{ "sdk": { "version": "3.0.100" } }
UpgradeSample.csproj
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp3.0</TargetFramework> <RuntimeIdentifier>win-x86</RuntimeIdentifier> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.0.1" /> </ItemGroup> </Project>
- global.json は Rider でプロジェクトを作った場合は出力されていましたが、 Powershell で dotnet new empty を実行して作った場合はありませんでした。
- Rider ではもう一つ、 Run/Debug configuration のバージョンも上げる必要があるかもしれません。
- これらのファイルを変更後、自動で実行されない場合は dotnet restore を実行します。
エラーを修正する
メジャーバージョンアップということで、主に下記に関連してエラーや警告が出ました。
- Endpoint Routing への変更
- Newtonsoft.Json から System.Text.Json への変更
- Entity Framework Core で、 Linq のクエリをクライアントで実行しない
RuntimeIdentifier がロードできない
- What's new in ASP.NET Core 3.0 - Microsoft Docs
- Comparing Startup.cs between the ASP.NET Core 3.0 templates: Exploring ASP.NET Core 3.0 - Part 2 - Andrew Lock | .NET Escapades
- My First Look at ASP.NET Core 3.0 - Shawn Wildermuth
1. MVC から Endpoint Routing への変更
2.2 ではルーティングを使うのに AddMvc, UseMvc を使っていましたが、 3.0 からは AddControllers, UseRouting, UseEndpoints に変わります。
Startup.cs
using Microsoft.AspNetCore.Builder; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.AspNetCore.Identity; using Models; using UpgradeSample.Models; using UpgradeSample.Products; using UpgradeSample.Users; namespace UpgradeSample { public class Startup { ... public void ConfigureServices(IServiceCollection services) { ... // for setting JSON parameter names pascal case. services.AddControllers() .AddJsonOptions(options => options.JsonSerializerOptions.PropertyNamingPolicy = null); // DI ... } public void Configure(IApplicationBuilder app, IHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints(endpoints => { // [Routing("")] を使ってルーティングする場合 endpoints.MapControllers(); }); } } }
- ASP.NET Core 2.2 で追加された Endpoint Routing と Core MVC での互換性 - しばやん雑記
- Routing in ASP.NET Core - Microsoft Docs
- Understanding ASP.NET Core Endpoint Routing - aregcode
- Endpoint Routing in ASP.NET Core 3.0 - Shawn Wildermuth
また今回は使用していませんが、認証などを行う場合、 UseRouting の後、 UseEndpoints の前に UseAuthentication, と UseAuthorization を記載順に実行する必要があります(順序が違ったり UseAuthentication が実行されていないと実行時に例外が発生します)。
操作しようとした人が誰かを確認した後、その人がその操作を実行できるか?を確認する、ということで、考えれば順序がわかるのですが、実行時に例外が発生するという辺りはちょっとややこしいですね。
2. Newtonsoft.Json から System.Text.Json への変更
3.0 にアップグレードしたときにコンパイルエラーが発生したのは、この Json 関連だけでした。
今回のサンプルで対象となるのは 2 箇所です。
Startup.cs
using Microsoft.AspNetCore.Builder; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.AspNetCore.Identity; using Models; using UpgradeSample.Models; using UpgradeSample.Products; using UpgradeSample.Users; namespace UpgradeSample { public class Startup { ... public void ConfigureServices(IServiceCollection services) { ... // for setting JSON parameter names pascal case. services.AddControllers() .AddJsonOptions(options => options.JsonSerializerOptions.PropertyNamingPolicy = null); // DI ... } ...
Product.cs
using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; namespace UpgradeSample.Models { [Table("Product")] public class Product { [Column("ProductId")] public int? ProductId { get; set; } [Column("ProductName")] [JsonPropertyName("ProductName")] public string ProductName { get; set; } } }
基本的な使い方は変わらず、書き方が微妙に違っている、という感じです。
3. Entity Framework Core で、 Linq のクエリをクライアントで実行しない
個人的には一番影響が大きいのでは?と思っているところです。
Entity Framework Core で、 Linq を使ったコード( Where )が SQL に変換できない場合、例外を発生するようになりました。
これによってパフォーマンスの悪化が防げる、というメリットと、 List を Linq を使って検索するつもりで雑にアクセスしていると実行時例外を発生させまくるというデメリットがあります。
ProductService.cs
using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using UpgradeSample.Models; namespace UpgradeSample.Products { public class ProductService: IProductService { ... public async Task<List<Product>> GetProductsAsync(string[] names) { // 実行時に例外発生 return await _context.Products .Where(p => names.Length <= 0 || names.Any(n => p.ProductName == n)) .ToListAsync(); } } }
対策としては、 Any など SQL に変換できない条件で検索したい場合、先に AsEnumerable を実行する、というのが考えられます。
ProductService.cs
using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using UpgradeSample.Models; namespace UpgradeSample.Products { public class ProductService: IProductService { ... public async Task<List<Product>> GetProductsAsync(string[] names) { return await Task.Run(() => { return _context.Products .AsEnumerable() .Where(p => names.Length <= 0 || names.Any(n => p.ProductName == n)) .ToList(); }); } } }
ややこしいのが、例えば Where の中身が例えば Where(p => p.ProductId == id) のような内容なら( id は引数となどで渡されているとして)わざわざ AsEnumerable を含む必要がない、というところ。
全部に AsEnumerable を含む、というのはおかしな気がするので、できるだけ Any などを使わず、 SQL に変換できるように書くのが良い。。。のかしら?
- ASP.NET Core / Entity Framework Core 3.0 の気になった機能と 2.2 からの移行 - しばやん雑記
- Breaking changes included in EF Core 3.0
BeginTransaction
今回使用していませんが、 C# 8 で using 句に await が使えるようになった影響で、トランザクション開始時に実行する _context.Database.BeginTransaction() が await できるようになりました。
await using (var transaction = _context.Database.BeginTransaction())
public async Task SetUserNameAsync(ApplicationUser user, string userName, CancellationToken cancellationToken) { await using var transaction = _context.Database.BeginTransaction(); try { user.UserName = userName; _context.Users.Update(user); _context.SaveChanges(); transaction.Commit(); } catch (Exception e) { transaction.Rollback(); } }
Rider 先生曰く await using(var transaction = _context.Database.BeginTransaction()){ } より上記のように {} を使わない書き方を推奨、ということで、なぜかしら?と思っていたのですが、どうも単にネストを浅くしようとしているだけのようです。
4. RuntimeIdentifier がロードできない
3.0 にアップグレード後、 win-x86 のパッケージ?がロードできない旨の警告の後、全 C# クラスがエラーになる問題が発生してビビったという話です。
あれこれ試してみた結果、リストア -> 再ビルド で解決しました。
Single-file executable
また RuntimeIdentifier に関連して? Single-file executable というものが追加されています。
これまで Self-contained app で出力すると大量にファイルが出力されていたのですが、この設定をしておくとファイル数がぐっと少なくなります。
UpgradeSample.csproj
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp3.0</TargetFramework> <RuntimeIdentifier>win-x86</RuntimeIdentifier> <PublishSingleFile>true</PublishSingleFile> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.0.1" /> </ItemGroup> </Project>
これで通常通り dotnet publish -c Release -r win-x86 を実行すると下記ファイルが出力されます。
おわりに
まだおっかなびっくり試しているのと、慌ててやるのはダメゼッタイというところではありますが、壊滅的な状況になることもなく 3.0 に上げることができました。
サポート期間的なこともありますが、パフォーマンスなど多くの改善点が含まれているだけに、早めに更新していきたいところです。
C# Advent Calendar, 明日は @takayoshitanaka さんです。
よろしくお願いいたします(..)_
Angular(ver.8.2) のページを IE11 に対応させたい話
はじめに
この記事は Angular #2 Advent Calendar 2019 一日目の記事です。
以前からちょろちょろ触っていた Angular ですが、そろそろ試しに使ってみたい、ということで、実践を意識して(どうにも逃れられない) IE11 の対応に挑戦してみたいと思います。
Environment
IE11 でページを表示する
デフォルトでは真っ白のページしか表示されないので、まずは表示されるようにしてみます。
といってもやったことは下記に従っただけです。
一応変更した内容を挙げておきます。
- プロジェクトに tsconfig-es5.app.json というファイルを追加
- angular.json で 1.が読み込まれるよう変更
- ng serve --configuration es5 で実行( package.json の Scripts に追加しても良い)
2.について、サンプルを丸コピーしたらプロジェクトが見つからないとエラーになりました。
angular.json
"serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { ... }, "configurations": { "production": { ... }, "es5": { "browserTarget": "app:build:es5" ← ここでエラー。 app をプロジェクト名にする必要がある。 } } },
Grid layout を有効にする
Angular には 以前 PostCSS で利用していた Autoprefixer が組み込まれており、 Flexbox などは IE でもそのまま使用できます。
ただ、 Grid Layout は対応されていない(多分)ため、個別に対応が必要です。
PostCSS の場合は postcss.config.js に書いていたわけですが、 Angular の場合は CSS に下記を追加します。
/* autoprefixer grid: autoplace */ #grid-frame{ height: 30%; width: 90%; display: grid; grid-template-columns: 10% 10%; grid-template-rows: 15% 85%; } ...
これで IE11 でも Grid Layout が有効になります。
ただし、この状態だと grid-column と grid-row が聞かず、 要素を横に grid-template-columns の数だけ並べ、超過すると改行、という動作となります。
autoplace の部分を変更すれば良さげですが。。。
背景色に Alpha のある色を設定する
まぁ Angular 独自の話ではないのですが。。。
000000AA のように、 background-color で RGBA で色指定をしてしまうと、 IE では色が表示されません。
rgba(r, g, b, a) を使って指定する必要があります。
#frame-background{ background-color: rgba(17, 17, 17, 0.67); }
PostCSS を使っていたときは自動で変換してくれていたので、つい見逃していました orz
Pikaday を使う
Chrome や Firefox などのブラウザでは input type="date" とすれば Date Picker が使えるわけですが、 IE ではそうはいかない、ということで、以前使用した Pikaday を使えるようにしたいと思います。
本当は Angular 用に用意された Date Picker もあるようなので、そちらにした方が便利なのかもしれませんが、仕事でも既に Pikaday を使っているため、まずはそちらを試してみます。
Install
npm install --save moment pikaday @types/pikaday
正直なところ、基本的な使い方はほぼ一緒です。
CSS(pikaday.css) は各画面共通ということで styles.css でインポートすることにしました。
styles.css
@import '../node_modules/pikaday/css/pikaday.css';
あとは Pikaday に関連付けるだけです。
今回は top-page というコンポーネントで使うことにしました。
top-page.component.ts
import { Component, OnInit } from '@angular/core'; import * as Pikaday from 'pikaday'; ~省略~ export class TopPageComponent implements OnInit { ~省略~ ngOnInit() { let picker = new Pikaday({ field: document.getElementById('datepicker') }); } }
top-page.component.html
<input type="text" id="datepicker">
Data bindings
さて、せっかくの Angular なので、値の受け取りは Two-way data binding を使いたいところ。
が、何も考えずに [(ngModel)]="dateText" のように書いてしまうと、 Pikaday で値が入力された場合にイベントが発火せず、 dateText の中身は空のままです。
この原因は上記で設定されているイベントが (input) であるためで、これを (change) にしてやる必要があります。
top-page.component.html
<input type="text" id="datepicker" [value]="dateText" (change)="dateText=$event.target.value">
- top-page.component.ts に dateText: string を追加してください
- Template Syntax - Angular
おわりに
基本的には自動で良い感じにしてくれる、ということで大変ありがたいのですが、 Angular に任せておきさえすればすべてうまくいく、というわけでもない辺りがちょっと辛いですね。
(主に IE の特に CSS 周りの対応)
まぁあれこれ地雷を踏み抜きながら覚えていくしかないか。。。
個人的には JS Framework 無しから Angular に切り替えて、一番嬉しいのは HTML の部分を TypeScript(JavaScript) ではなく HTML として書ける、ということかなと思っています。
コードで生成しても良いのですが、規模が大きくなってくるとやっぱり分かりづらい。。。
React や Vue だとまた違っていると思うので、そちらも試してみたいなとは思っています。
さて Advent Calender ですが、明日は ringtail003 さんです。
よろしくお願いいたします(..)_
JSConf China 2019 in 上海参加記録( 2 日目)
- はじめに
- 会場
- Keynote. Learn as I write the Docs
- 2. Serverless is your BFF
- 3. Entropic package registry
- 4. Redux Based Modular Design
- 5. Diving into TypeScript design metadata with NestJS
- 6. Writing code to refactor code
- 7. JavaScript 的执行 & 性能
- 8. 去除迷雾, 漫谈 WebAssembly
- Panel discussion
はじめに
こちらも途中ですが、 JSConf China 2019 2日目の内容をメモっておきます。
※ 2019/11/09 会場でメモった内容をほぼそのまま書いています。
意味不明な部分や聞き間違い、解釈違いなど多数あると思います。
もうすぐ資料や動画が配信されるはずなので、その後更新する予定です。
会場
- 9:55開始
- 開場時点での参加者数は昨日より少ない。
- チケットのやりとりで Keynote に間に合わないのを恐れたため?(つまり自分と一緒)
- 開始時点でも昨日より少なかった(昨日飲みすぎ?)
- PC自体は共通のMacを使ってるっぽい
- 中国語セッションは 3/8
0. Opening
- 開発者同士の交流、ということを再度強調
Keynote. Learn as I write the Docs
資料
昨日のSessionについて
- 各Sessionの良いところをかんたんな概要に触れながら。
フロントエンド、React を学んできた4年間
- 新しい技術にふれるとき、たくさんのDocsを読むことになる
4 years as Front End
- 最初は何もわからなくてもとにかく楽しかった。
- 3年目、より多くを得るために、Conferenceに参加
- Redux
- React Redux
自分自身について。
- 十分なキャリアがあるか?
- 英語の解釈問題
- 書くことの難しさ
Richard Feynman
Names don't constitude knowlege
tool や library は開発者を助けてくれる
いつ、どこで学ぶか?
誰かに教えることは、学ぶための最も良い方法
- Docsを書くことはこのことに繋がる
- どう書けば相手(ユーザー)に伝わるか、を考えなければならないから
tackling the challenge
- 英語で書くこと。
- 細かい Typo やミスなど気にしない
- 完璧じゃなくても良い
- Tipsの共有による練習
- thesaurusのようなツールを使う
- proof reading
- 他の人に説明する
- 1-1 sharing
- 習慣にすること
- メンテナと直接やり取りできる素晴らしさ
- ライブラリのアーリーアダプターになる
- チームにおけるドメインエキスパートになること
Docs
2. Serverless is your BFF
start
- かんたんなアバターを作るサービス モバイル、PCそれぞれからBFFに接続-> 各Serviceに
Webの歴史
- 1.0はとてもシンプル
- 2.0はたくさんのサービス・機能が生まれた。
- Front end/backend に別れた
- Frontendはツールやフレームワークの登場によりかんたんに。
- Fullstack
Full stack?
- UI -> Product
- 仕様ぎめ、開発、Ops
- Node.jsによりFrontend、Backendで再利用できるコードも増えた
- ただしFrontend、Backendのやり取りは依然困難。
- Backend はとても複雑
- Scalable, Security, Debuggable...
- Prototype -> Production architectureに移るに当たり、気にするものがいっぺんに増える
- Autoscale
Cloud computing
すべて自前のマシンで用意するOn-premissから、FunctionsのみのFaaSへ。
- ビジネスにフォーカスする
Node.js
- Faas
- SCF
- Faas
tencent cloud でどのようにアップロードしているかデモ
- アップロードした写真に帽子をつける
- Ergonomic Upload code and go
Serverless is economic
- reduce team size
- 使っただけ払う
VMだと必要なときもそうでないときも同額かかる
Serverless is reliable
- No ops
summary
- Serverless の紹介。Webの歴史、良いところを取り上げ、Tencent cloudを使うデモも。
- Live coding, Live debugging
3. Entropic package registry
What is package manager?
- Registory <--> client
- Do install work?
- publish permission
What is package?
- Softwareをひとまとめにしたもの
- NPMなどであつかう
- Npm registry -> npm -> disk
- disk -> webpack -> bunndle ひとまとめに
Node.js
- Node.jsも一つの file manager
- disk -> Node.js -> commonjs
- 動的に package manager を動かす?
yarn
- cache -> yarn -> Pnt resolution
Plugin
- loader of Node.js
Tink
- Disk にキャッシュする
Recap
基本的に
- 全部 Package managerを使う。
Entropic
- Entropic は Package を一箇所にまとめるのではなく、個別に分かれている
- それらが連携しあって動く。
4. Redux Based Modular Design
Slide
Motivation
- build large app
- Modular -> 模块化
- サイズが大きくなってくると、
- Combine reducers,Middleware, connection
- 機能ごとにクラス分け
modularity
困難
- CoreのBuisiness logicのUIからの分離
- 状態管理
- カプセル化
Pros
- CDのしやすさ Divide, conquer
- テストの完全自動化
Motivation & Goal
Redux Cons
- Immutable data
- 実現が難しい?
- verbose update operations
- Boilerplate Code が増える
- Modularity
- Loose, difficult
- ロジックが分割しづらい(UIに入り込んでるとか)
Solution
usm-redux
- Reducer
- @state decorateを使って状態管理、
- @action で処理を定義。関数につく。
- 状態を変える
- Router 的な動きが実現できるので、switchなどを取り除ける
- 引数にstateを追加
Action type
dispatch
- イベント処理の分割
Module Dependency
- Module inject
- Customizable Iocのようなものをつかう
Immutable data update
immer
- 今の値をベースにImmutable stateを作る
benefit
5. Diving into TypeScript design metadata with NestJS
Slide
memo
- KoaもExpressやNest.JSと同じフレームワークらしい
Design-time metadata
- Reflection
Parameter type metadata
- extract type
- DI, Type -> Nest.js feature
DIの説明
- @Injectable() をつけることで、複数のDependencyを解決できる
Compiler metadata
decorate( [metadata('design:parametypes', [HttpService, Logger])], CatsService,
Reflection API
Reflect.getMetadata('design:parametypes',CatsService)
Pipes
- castなども使われる
PipeTransform{ transform(
) }
Reflection API challenges
interfaces
- 依存interfaceの場合、JSにしたときはObjectとなる
Potential solution
- abstruct solution
Generics
- Genericsの場合にどう解決する?
- JSにするとクラス情報はなくなってしまう
*
Circular dependency
- JSにするとクラス情報はなくなってしまう
*
- 依存クラスがJS変換時にundefinedに
- @Inject( =>
Type metadata
export class User{ email: string; name: string; } ↓ export class User{ @Property email: string; @Property name: string; }
複数プロパティがあるとき Reflect.getMetadata('design: type, new User(), 'email')だと正しくmetadata がundefined になってしまい、Typeがとれない
@Propertyを使う
What's the point
AST
TypeScript Plugins
Return type metadata
- OpenAPI gRPC, GraphQL mutations
戻り地がPromise< string>のとき、Reflect.getMetadata が Promiseになる。stringは?
Recap
design-time metada
TODO: news letter 登録
6. Writing code to refactor code
Slide
概要
- 今年のはじめに実施した大規模なリファクタリングの話
- AST(Abstruct Syntax Trees)
はじめに
- Zenhubでは CommonJS-> ES6に変更した。
- 1752files
- 5 years old
Large scale refactoring のながれ(失敗)
- Find and Replace
- どうにもならん\(^o^)/
ASTの紹介
codemod
find & replace inspect the code
- Prettier
- ESLint autofix
webpack, babel transpilationはcodemodではない
- Codemod in action
- Transforming requires to import
- CommonJSの var a = require("doge.js").wow => import { wow as amused } from "doge.js"
- iTerm2
- npx codemod としてるから、npm でインストールするツールか?
from が同じものがある場合、combine?
ASTはリファクタリングのときにも使える
- Codemodだけでなく、StaticTypesなどいろんなツールがある
7. JavaScript 的执行 & 性能
Slide
はじめに
- a.b = 123 と a[' b'].b のパフォーマンス差?
- esparse
- V8と? の比較
- d8でどう解釈されているか
- BinaryExpression
bytecode
- staGlobal
- jerry --show-opcodes add.js
- V8での解釈・実行の流れや発生したパフォーマンス問題など
WebAssembly
JavaScript の歴史
- Mocha、LiveScript,
- JavaApplet, ActionScript, SilverLight
V8
- V8によって、JavaScriptがWebフロントエンドでの立場を不動のものにした?
8. 去除迷雾, 漫谈 WebAssembly
- C/C++, Go などの複数のから WAコードを生成
- ブラウザ上では実行できない?
WAはJavaScriptにとって代わるか?
- 補うもの
- JSで直接WebAssemblyを書くことはできない
実行速度はあまり変わらない?
- V8の場合、JS -> AST -> ignition -> turofan -> local machine code
- WA(.wasm) -> Liftoff -> TurboFan -> local machine code
パフォーマンスは変わらない?
- WAはDownload, Decodeを同時にするのでパフォーマンスが上がる
- 最適化
WA moduleの構成
- function signature
- run time
おすすめの言語
- Rust
- WA を推し進めているMozillaが作っている言語
Demo
- 書くときは普通にRustをかく。実行時はバイナリコードに。
- バイナリコードに変換する?ツールあり。
Panel discussion
翻訳するときに、ネイティブのような表現ができない
問題のあるパッケージが混ざった場合にどう対応するか
- npmなどと同じ?
Nest.jsでInversifyJSを使わない理由?
- InversifyJS自体は使っていないから実際のところはわからないが、 使い方が違う?
- 少なくともInversifyJSなど他のものを使うことは進めない
ASTによるリファクタリングが向いてないとき?
CommonJS -> ES6には使えるが、JS -> TSではASTによるリファクタリングが有効か?
- おそらく
Jerry.js
WAのセキュリティ上の問題など?将来的な問題が発生する恐れはある?
- すでに対応はしている
ドキュメントが人に伝わらないとき?
- 言い方を変えてみる、細かく説明してみる
- 時間をあけてもう一度自分で読んでみる。
- 理解できる?
Middleware of NestJS?
- Expressのものと同じ?
- HTTPアプリケーションを作るときには依存する
Nest.js は GraphQL に対応している?
- Yes.
- GraphQL のデータの作り方は2種類あり、Code firstとData first?がある
WAはマルチスレッドなどの対応している?
- している?が使ったことはない
- 基本的にもとの言語に依存する
- ブラウザの機能を使う?
CherryScript Quick.js?
Entrophic Production ready?
- No
- いつ? -> まだわからなそう