きよくらの備忘録

「三日坊主と呼ばせない!日記」改め。主にソフトウェア開発関連の話題。

LGの42.5インチ4Kモニタ 43UN700T-B を買った

Prime Dayの日、セール対象ではなかったっぽいけどなぜか普段より1万円少々安い54,980円とかだったので購入。

ノートPCをHDMIとUSB Type-Cで接続、左右2画面分割(1920*2160を2枚)して都合トリプルディスプレイとして利用しはじめましたがいまのところこれ自体は快適です。

ただ、やはりそれなりの大画面を近い距離で使用するため、(自分の)視野角と距離、目線の高さなどの都合でPCデスクを新調したくなりました。 奥行700~800mm、幅1400~1600㎜、高さ700mmくらいで天板が一枚板*1、耐荷重50kgくらいいけそうなデスクを探す旅が始まりそうです。

*1:無垢一枚板である必要はないけど引き出しや補強版などが無いタイプが欲しい

DacFxで差分更新スクリプトを取得する際に特定のオブジェクトを除外する

更新用T-SQLスクリプト生成時に特定のオブジェクトを除外したい

先日書いたエントリ、 kiyokura.hateblo.jp

……の補足情報です。

特定のオブジェクトを更新スクリプトに含めたくない場合もあるのではないかと思います。私はあります。 DaxFxのSchemaComparisonでこれを実現する方をあれやこれや試行錯誤してみたところ、異なる二つのアプローチにたどり着きました。

SchemaComparisonResult.Exclude で抽出結果から除外してスクリプトを生成するアプローチ

まず一つ目は、SchemaComparison.Compare()で比較結果から除外対象を登録したうえでスクリプトを生成する方法です。

比較結果として返されるSchemaComparisonResultのメンバメソッド、Exclude()を使います。 以下のように、Excludeに除外したいオブジェクトをSchemaDifference側で渡してやればOK。

// パターン1:比較結果から除外するアプローチ
var newverdacpac = @"C:\newver.dacpac";
var oldverdacpac = @"C:\oldver.dacpac";

var newverEndpoint = new SchemaCompareDacpacEndpoint(newverdacpac);
var oldverEndpoint = new SchemaCompareDacpacEndpoint(oldverdacpac);

var comparison = new SchemaComparison(newverEndpoint, oldverEndpoint);
var comparisonResult = comparison.Compare();

// 削除(DROP)になる更新を除外して追加(CREATE)と変更(ALTER)のみのスクリプトを生成する
foreach (var diff in comparisonResult.Differences.Where(x => x.UpdateAction == SchemaUpdateAction.Delete))
{
  comparisonResult.Exclude(diff);
}

// スクリプト生成
var generationResult = comparisonResult.GenerateScript("HogeDb");
var scriptfile = @"C:\update-database-exclude.sql";
using (var sw = new StreamWriter(scriptfile, false, Encoding.UTF8))
{
  sw.Write(generationResult.Script);
  sw.Flush();
}

ポイントはExcludeメソッドを呼ぶたびに比較結果内で依存関係等のチェックが行われる点です。そのためExcludeメソッドの実行にある程度時間がかかることがあります。また依存関係チェックの結果、スクリプト生成時に除外されないケースもあります(除外しようとしたオブジェクトが、更新スクリプトに含まれるオブジェクトから依存されている場合) 端的に言うと、Visual Studio上でSSDTのスキーマ比較ツールを利用して比較結果表からチェックボックスでオブジェクトを選択して除外していくのとまったく同じ操作と結果を得ることがでるようでう。

 

ExcludedSourceObjectsとExcludedTargetObjectsに除外オブジェクトを登録した上で比較を行うアプローチ

もう一点は、SchemaComparison.ExcludedSourceObjectsSchemaComparison.ExcludedTargetObjectsに除外したいオブジェクトを登録した上でCompareメソッドを実行するアプローチです。 ExcludedSourceObjectsとExcludedTargetObjectsに登録されたオブジェクトは比較時に対象から除外されるため、これらに登録されているオブジェクトは(差異があっても)比較結果に差異として抽出されません。

例えば以下のようなコードになります。

// パターン2:除外してから比較するアプローチ
var newverdacpac = @"C:\newver.dacpac";
var oldverdacpac = @"C:\oldver.dacpac";

var newverEndpoint = new SchemaCompareDacpacEndpoint(newverdacpac);
var oldverEndpoint = new SchemaCompareDacpacEndpoint(oldverdacpac);

var comparison = new SchemaComparison(newverEndpoint, oldverEndpoint);
var comparisonResultPre = comparison.Compare();

// 削除(DROP)になる更新を除外して追加(CREATE)と変更(ALTER)のみのスクリプトを生成する
foreach (var diff in comparisonResultPre.Differences.Where(x => x.UpdateAction == SchemaUpdateAction.Delete))
{
  if (diff.SourceObject != null)
    comparison.ExcludedSourceObjects.Add(new SchemaComparisonExcludedObjectId(diff.SourceObject.ObjectType, diff.SourceObject.Name));
  if (diff.TargetObject != null)
    comparison.ExcludedTargetObjects.Add(new SchemaComparisonExcludedObjectId(diff.TargetObject.ObjectType, diff.TargetObject.Name));
}

// 除外オブジェクトを登録した状態で再度比較を実行
var comparisonResult = comparison.Compare();

// スクリプト生成
var generationResult = comparisonResult.GenerateScript("HogeDb");
var scriptfile = @"C:\update-database-add-excludedlist.sql";
using (var sw = new StreamWriter(scriptfile, false, Encoding.UTF8))
{
  sw.Write(generationResult.Script);
  sw.Flush();
}

この方法の場合、ExcludedXxxObjectsに含まれるオブジェクトは依存関係のチェックも行われないようで、更新用T-SQLに含まれるオブジェクトからの依存があっても除外されたままになります。 上記サンプルではExcludedXxxObjectに登録するための情報を得るために一度Compareを行い、リスト登録後に再度Compareを実行するという一見二度手間のような処理をしていますが、依存関係チェックが行われないせいかトータルの処理時間としては短めになりました*1

 

まとめ

具体的な記述のあるドキュメント等が見つからないためあくまでも挙動からの推測にはなりますが、このような形で一応目的を達成することができそうです。

*1:状況によるとは思います

DacFxを使って(C#で書いたコードを動かして)SQL Serverの差分更新スクリプトを取得する

Visual StudioのアドオンSQL Server Data Tools(以下SSDT)の機能に「スキーマ比較」ツールがあります。 これは大雑把に以下のような機能を持っています。

  • 任意の "SQL Server データベースプロジェクト(以下DBプロジェクト)" / "(オンライン状態の)データベース" / "dacpacファイル" 間でスキーマの比較を行い差異を抽出する
  • 抽出した差異を解消するための更新スクリプトを生成する

これらと同等の操作はコマンドラインツールのSqlPackage.exeでも可能であり、これらの基盤となっているDacFxを利用すれば.NETのコードで上記の操作を記述・実行することが可能です。

(多分こんなことやる/やりたい人はそうそうそういないと思うのですが自分はまた今後もやりそうな気がするので、完全に自分用のメモです)

 

差分の抽出

利用するのは DacFxに含まれる Microsoft.SqlServer.Dac.Compare.SchemaComparison クラスです。

docs.microsoft.com

このクラスは、dacpacを指す SchemaCompareDacpacEndpoint またはオンラインのデータベースを指す SchemaCompareDatabaseEndpoint をソースおよびターゲットとして比較する機能を持っています。つまり、比較対象はdacpacファイルとオンライン状態のデータベースのいずれかの組み合わせとなります。もちろんdacpac同士やデータベース同士での比較だけではなくdacpacとデータベース間での比較もできます。

(なお、SSDTのスキーマ比較機能のようにDBプロジェクトを比較対象にしたい場合は何らかの方法でDBプロジェクトからdacpacを生成した上で比較する必要がある、という事になります。その場合は 以前書いたように、Microsoft.Build.Evaluation 名前空間のあれこれを利用すればdacpacの生成処理自体も.NETのコードで記述することが可能です)

例えば以下のようなコード新旧のバージョンのdacpacを比較して差分を抽出するできました。

var newVersion = new SchemaCompareDacpacEndpoint(@"C:\some_database_v1.1.dacpac"));
var oldVersion = new SchemaCompareDacpacEndpoint(@"C:\some_database_v1.0.dacpac"));
var comparison = new SchemaComparison(newVersion, oldVersion);

var compareResult = comparison.Compare(); // 戻り値はSchemaComparisonResult型

結果は SchemaComparisonResult型 で戻されます。 例えば IEnumerable<SchemaDifference>型Differences プロパティには差分のオブジェクトの情報が列挙されますので、この中身を見ることで差分の種類やオブジェクトの名前などを取得することができます(ただし若干直感と反するようなプロパティ名かもしれない)。

foreach(var d in compareResult.Differences)
{
  // UpdateAction : 更新種別(Add or Change or Delete)
  // Name : オブジェクトの種別 (Table or Procedure or .... )
  // SourceObject.Name / TargetObject.Name : オブジェクト名
  Console.WriteLine($"更新種別:{d.UpdateAction}, オブジェクトタイプ:{d.Name}, 対象オブジェクト名:{(d.SourceObject?.Name) ?? (d.TargetObject?.Name)}");
}

 

更新用T-SQLスクリプトの取得

上記までのコードで比較はできましたが、この状態では差分を抽出しただけでありまだ更新用T-SQLスクリプトは生成されていません。これはGenerateScriptメソッドで生成できます。 GenerateScriptメソッドを実行すると戻り値としてSchemaCompareScriptGenerationResultが得られ、SchemaCompareScriptGenerationResultScriptプロパティにT-SQLスクリプトが格納されています。

雑にファイルに保存するならこんな感じでしょうか。

var generateResult = compareResult.GenerateScript("Hoge"); // 引数database name は任意の文字列。更新スクリプト中の USE [Hoge] などで使われる。
using (var sw = new System.IO.StreamWriter(@"C:\Update-Schema.sql", false, Encoding.UTF8))
{
  sw.Write(generateResult.Script);
  sw.Flush();
}

 

そのほか、Compareの第二引数側(古いバージョンのスキーマ側)がSchemaCompareDatabaseEndpointすなわちオンラインのデータベースの場合、GenerateScriptメソッドの代わりにPublishChangesToTargetメソッドを実行することで直接データベースを更新することができます。その際に実行されたT-SQLスクリプトはSchemaComparePublishResult型で返却されるPublishChangesToTargetの戻り値のScriptプロパティに格納されています。

 

まとめ

以上の通り、比較的簡単にSQL Serverのデータベースおよびdacpac間でスキーマ比較を行い差分更新用T-SQLスクリプトを生成できました。

……できましたが、オフィシャルのドキュメントが『ドキュメントコメントから生成されたシグネチャやメンバが書いてあるだけの極シンプルなAPIリファレンス』以上でも以下でもなく、利用方法などについての説明はほぼないためAPIリファレンスとインテリセンスであたりをつけつつ(基本的に同じことができるはずの)SqlPackage.exeのコマンドリファレンスを見たり おださん(@shinsukeoda) に教えてもらったサンプルコード を眺めてみたりと基本的に手さぐりだったので意外と時間がかかりました。

このほか、特定のオブジェクトを除外した更新スクリプトの生成にも無駄に時間を食ってしまったりしたのですが、またそのうち気が向いたら書いておこうと思います。

arcade1up:ディスプレイ横画面化(その1)

目指すところ

実際ギャラガ大好きだし思い入れもあるし、日本第一弾ラインナップの中ではおそらくギャラガとギャラクシアンが一番プレイ時間長いのでギャラガ/ギャラクシアンとしても楽しむ気持ちはあります。

……あるのですが、今回目指すのは購入報告のエントリ(?)でもちらっと述べた通り汎用の横画面ゲーム筐体への改造を目指そうと思ってます。

 

家庭用汎用アーケード筐体の素体としてのarcade1up

私としては現状、私が容易に安価で手にるものの中で改造の素体として最も都合が良いのがarcade1upだと考えています。

代表的なポイントはこんな感じです:

  • 比較的コンパクトでありながら小さすぎない
    • 標準で17インチLCD搭載、頑張れば19インチLCDも恐らくいける
    • 配線や機器を内蔵するための内部空間にも余裕がある
  • 適度な重さ
    • 25kgとアーケードゲーム筐体として必要十分な重さ(軽すぎると逆に使いにくさも出てくる)
    • 100kgオーバー(アストロシティ等)は移動も設置場所の選定も個人宅では色々難易度が高い
  • DIYしやすい素材
    • 構造材がMDFなので加工に必要な道具と技術が一般手な木工DIYの範囲内
    • パーツの修繕や新造も悩みが少なそう

 

横画面および2L12Bのコンパネ標準を装備している「スト2版」や「マーベルスーパーヒーローズ版」が容易に入手できればよかったのですが、現時点では国内向けには販売されておらず個人輸入するにしてもそのサイズ故に色々とハードルが高く。国内販売された中では「ランペイジ版」が横画面なのですが、気が付いたときにはどこも品切れで再入荷の見込みも薄そう……ということで一旦あきらめ、今回は安価で普通に入手できる3機種の中から一番好きなギャラガを選択することにしました。

 

LCDパネル横画面化の方針

横画面筐体にするための方法として何通りか考えました(例えば新規に17~19インチ程度のLCDパネル買って組み付ける等)。そのうえで最終的には以下の方針を取ることにしました:

  • LCDはギャラガに同梱されたものを使用
    • HDMI等汎用の入力を受け付けれるようにするLCDコントローラー(ドライバ)ユニットを購入する
  • ベゼルは横画面に合わせて新造

LCDは同梱のモノをそのまま利用し、通販で入手可能なM170ETN01.1用のHDMI対応のLCDコントローラーを購入して対応することにしました。通販で安いところで$15程度+送料、高くても国内のショップで4,000円程度で入手可能な模様。 ベゼルは9mmのMDFと2.5mmの合板で新造することにしました(12mmMDF一枚ではない理由は後述)。とりあえずホームセンターで1,500円もあれば購入できそうです。 また表面に張り付ける透明アクリルパネルは少し高くて、2,500円くらいは見積もった方が良いかもしれません。 合わせると、最大でも8000円程度の追加投資で横画面+HDMI入力対応化はできそう、という判断です。

 

横画面用ベゼルの設計

ベゼルの新造に当たって、一点だけ考えたのが「LCDを固定するための凹部」です。 f:id:kiyokura:20200922020131p:plain

この一段へこんだ部分。 f:id:kiyokura:20200923003540p:plain

オリジナルを真っ正直に再現するならば12mmMDF板を15mm程度の幅で2.5~3mm程度の高さを削ってやる感じになりますが、手持ちの工具と機材だとレベルを均一にしつつそこそこの長さを加工するのは正直ちょっと面倒だと感じました。

そこでアプローチを少し変えてみることに見ることにしました。

ベゼルをオリジナルと同様に一枚の12mmのMDFで作成するのではなく、9mmのMDFと2.5~3mm程度のMDFまたは合板*1などを張り合わせることで実現する方法です。 断面はこんな感じ。 f:id:kiyokura:20200923163050p:plain

加工は断然楽で、特に2.5mm~3mmの板の方は穴のサイズは少々大きくなっても特に問題はないはずです。唯一心配なのは強度ですが、LCD自体そこまで重くないので9mmのMDFが主の構造材であればさして影響はなさそうと思ってます。仮に問題が出ても適当な角材で裏打ちでもしてやればクリアできそう、という目算もあります。

 

ということで、ざっくり以下のような寸法で行けるかなと踏みました。 f:id:kiyokura:20200923005337p:plain

 

次回(?)

次はベゼルの新造、もしくはLCDパネルの汎用化のテストあたりをやってみようかなと思ってます。

*1:構造材というわけでもないのでMDFじゃなくてもっ安くて軽い適当な合板でも問題なさそう

arcade1up:全体的な構造についての所感やLCD パネル部の構造と(雑な)寸法など

先日購入したarcade1upギャラガ、なかなか全体を開封して組み立てる時間はないので取り急ぎディスプレイ部だけ開梱して分解・採寸したりしたのでメモ。

 

【各部品の名称は、付属の組み立て説明書に準じます(オフィシャルサイトでPDFで提供されています: https://arcade1up.jp/ のページ下部のリンクを参照)】

全体の構成

基本的には12mm程度のMDFで組まれた中空の箱です。 電気的に動作する部品は「LCD パネル(ディスプレイパネルの部分)」と「コントロールユニット(ジョイスティックなどの部分)」の部分の二か所。

CPUとかROMといった心臓部はLCD パネルにくっついています(後述)。 コントロールユニットはMDFの板に樹脂製のボックスがとりつけられていて、その中にはスイッチ類の他にスピーカーが組み込まれています。

いわば『中空の箱に薄いLCDとコントロールパネルが付いてるだけ』、といった構造のた非常にシンプルで空間に余裕がある筐体です。 排熱の対策さえできればラズパイやコンシューマゲーム機はおろかミドルタワークラスのPCでも仕込むのは難しくなさそうです。

f:id:kiyokura:20200922101038p:plain
背面から見た内部の様子(添付の組み立て説明書より抜粋、背面パネルは未取り付けの状態)

MDFなのであまり頻繁なネジの開け閉めはしないほうが良いと思いますが、木材なので加工も比較的容易ですからメンテナンスハッチを付けるなどやりようはいくらでもありそうです(そのうちやりたい)。

 

何にせよ、DIYでぼちぼち手を入れて改造していくには格好の素体だと感じています。

 

LCD パネル部の外観

表面はこんな感じ。17インチディスプレイが垂直レイアウトでベゼルに収まってる感じです。 f:id:kiyokura:20200922020404p:plain

こちらが裏側。 f:id:kiyokura:20200922020656p:plain

LCDの真ん中あたりにへばりついている生えた長方形の箱が、LCDのコントローラー(ドライバー)やらROMやらが入った、いわばこのarcade1upの本体というべきユニットです。

 

LCDの素性

LCDのラベルに「M170ETN01 1」というモデル名?のようなものが印字されているので調べてみると、どうやらOEM向けとして販売されているLCDモジュールのようです。世間では(?)「M170ETN01.1」で通っているようで、TFT方式で解像度は1280x1024…などといった情報が出てきました。 通販サイトなどでも$50から$90くらいで販売されているようで、万一つぶしてしまっても同じものは比較的容易に手に入りそうではあります。

またHDMI等の汎用的な映像入力を受け付けて駆動するLCDドライバモジュールなども通販で容易に手に入るようです(これについては後日あらためて触れる予定)。このたりも改造したいとしての素性の良さを感じます。

 

LCD パネル部の構造

LCD パネル部は大雑把に以下の3つで構成されています。

  • 透明パネル(アクリル……かもしかしたらPETかも?厚みは2mm程度)
  • LCD(と本体)
  • ベセル(MDF/12mm程度)

これらが適宜ねじ止めされています。

またベゼルは中央に穴があるだけではなくLCDの耳(?)を固定する部分が凹モールドになっています。 ざっくり図示するとこんな感じ。 f:id:kiyokura:20200922020131p:plain

断面図だとこんな感じです。 f:id:kiyokura:20200922020953p:plain

非常にシンプルで合理的な構造だと思いました。

 

採寸

LCD パネルの箱(だけ)を開梱したついでに、ざっくり採寸してみました。

f:id:kiyokura:20200922232354p:plain
arcade1up ギャラガ筐体LCDパネル部のだいたいの寸法

ただし、ノギスや指金がすぐ手元になくたまたまポケットに入ってたメジャーで雑に測っただけですので、あまり鵜呑みにはしないで欲しいなと思います*1

*1:「信じてカットしたのに合わなかった!」などと言われても責任取れません……