2012/07/31

【Access】サブフォームからレコードを選択する方法

「同一のレコードソースを持つメインフォーム(帳票)とサブフォーム(データシート)を用意し、サブフォームのあるレコードを選択したら、メインフォームも同一レコードへ移動するように。」と部下に課題を出してみたところどうやら煮詰まってる模様。

こっちは解答も用意せず何気なしに出題したわけだけどそんな難しいことかいな?と自分でも作ってみると、意外とやっかいというか…。一般的なサンプルはメインフォームとサブフォームの関係は親子の関係であり、サブフォームは詳細表示に使いましょう的なものが多く、今回のように対等に扱うケースはあんまり見あたりませんでした。すまん、すまん。これはちょっとうっかり。そりゃ初心者じゃ煮詰まるわ。

せっかくなので要点だけのサンプルを載せときます。サブフォームのレコード移動時イベントで割と強引にメインフォームのレコードを移動させてます。

Option Compare Database
Option Explicit

Private Sub Form_Current()

DoCmd.GoToRecord acDataForm, "メインフォーム名", acGoTo, Me.CurrentRecord

End Sub

何でも屋の「DoCmd」ですが、こーゆー別フォームの操作ができちゃうのが良いとこなんだか悪いとこなんだか…。個人的にはもうちょいちゃんとした手続きが間に欲しかったりするとこだけど、そこまでやるんだったらVBAじゃなくてもいいだろって話になっちゃうので…。

後は追加・削除時に互いにRequeryかけるとか、細かいとこ見てやれば大丈夫なはず。次からはちゃんと解答用意してから出題しようと思いましたとさ。


2012/07/26

ラムダ式の自分なりの解釈2

前回に引き続きラムダ式の自分なりの解釈(自分用)です。

前回は匿名メソッドを「デリゲートを使用する際に外部に書き出してたメソッドをインラインで書けるようにしちゃおうというもの」という大雑把な解釈(?)で片付けて終わらせちゃいました。今回はそれを踏まえてラムダ式の解釈を行います。

早速、前回のサンプルをラムダ式で表してみましょう。ラムダ式の基本的な文法は「(入力パラメータ) => {式やステートメント}」となります。

static void Main(string[] args)
{
var message1 = "20の倍数です(スコープ外も参照できます)";

var thread1 = new Thread
(() =>
{
for (int i = 1; i < 100; i++)
{
Console.WriteLine(i.ToString());
Thread.Sleep(250);

if (i % 20 == 0)
{
Console.WriteLine(message1);
}
}
});

var thread2 = new Thread
(() =>
{
for (int i = 100; i >= 1; i--)
{
Console.WriteLine(i.ToString());
Thread.Sleep(500);

if (i % 20 == 0)
{
Console.WriteLine(message1);
}
}
});

thread1.Start();
thread2.Start();
}
}

…我ながらなんていまいちなサンプルなんだ。どこが変わったか分かんないじゃないか。パラメータもないし(パラメータが無い場合は先頭が「()」空の括弧となります。)。

このままではあんまりなのでLINQを使ったサンプルをひとつ考えます。まずは前準備として以下のようなコードがあるとします。

class Employee
{
public string Cd { get; set; }
public string Name { get; set; }
}

class Program
{
static void Main(string[] args)
{
var employeeList = new List
{
new Employee{ Cd = "001", Name = "Tanaka" },
new Employee{ Cd = "002", Name = "Sato" },
new Employee{ Cd = "003", Name = "suzuki" },
new Employee{ Cd = "004", Name = "Tanaka" }
};

//タナカさんを探せ!!
foreach (var employee in employeeList)
{
if (employee.Name == "Tanaka")
{
Console.WriteLine(string.Format(
"Cd:{0},Name:{1}",
employee.Cd,
employee.Name));
}
}

Console.ReadLine();
}
}

なんだか毎度気合いの抜けたサンプルですが、Employeeデータを作成し、そこからタナカさんを探してコンソールに出力しています。これをLINQとラムダ式を使って変更すると以下のようになります。

class Employee
{
public string Cd { get; set; }
public string Name { get; set; }
}

class Program
{
static void Main(string[] args)
{
var employeeList = new List
{
new Employee{ Cd = "001", Name = "Tanaka" },
new Employee{ Cd = "002", Name = "Sato" },
new Employee{ Cd = "003", Name = "suzuki" },
new Employee{ Cd = "004", Name = "Tanaka" }
};

//タナカさんを探せ!!
foreach (var employee in employeeList.FindAll(n => n.Name == "Tanaka"))
{
Console.WriteLine(string.Format(
"Cd:{0},Name:{1}"),
employee.Cd,
employee.Name);
}

Console.ReadLine();
}
}

コードの量があんまり変わっていませんが、FindAllメソッドにラムダ式「n => n.Name == “Tanaka”」を割り当てています。FindAllメソッドに渡されるパラメータに「n」を指定していますが、別に「m」でも「c」でも「nn」でも「nnn」でも何でもよいです。最初は何だよ「n」だとか「p」だとかってと思ってましたがラムダ式の慣例だと思ってしまえば使っていく内に慣れると思います(下手な名付けするより使いやすいです)。ラムダ式を利用した結果、foreachの前にタナカさんが絞り込まれ、ループ回数が少なくなります。

このようにLINQ内で使う場合はラムダ式自体目的が明確になるので分かりやすいです。問題は以前の記事に出てくるようなメソッドのパラメータにラムダ式を入れなきゃいけないパターンです。まぁ、インテリセンスのパラメータのヒントにFuncデリゲートだとかActionデリゲートとかって出たらラムダ式かなって思うことにしようかと(自分的に)。

今更ながら全然解釈してない気もするけどまぁいいや。


2012/07/25

ラムダ式の自分なりの解釈

先日の記事でFuncデリゲートが苦手と書いたけど、Funcデリゲート自体よりラムダ式が苦手だったりするので改めて自分用に自分なりの解釈をしておこうと思った。Funcデリゲート自体はラムダ式のおまけだとだと思ってしまえばそれまでだし…。

基本的にLINQでの使用が多いラムダ式ですが、MSDNによると「ラムダ式は式とステートメントを含めることができる匿名関数であり、デリゲート型または式ツリー型を作成するために使用できます。 」(引用元:ラムダ式 (C# プログラミング ガイド))とのこと。はぁ…、まずは匿名関数(匿名メソッド)が分かってないと駄目ってことですよね。

というわけでまずは匿名メソッドを解釈してみます。まぁ読んで字のごとく名前の無いメソッドで、C#2.0から登場した機能です。正直、C#3.0でラムダ式というかLINQが登場するまでその存在をほとんど忘れてました。だっていまいち利便性が分からなかったし、使いどころがピンとこなかったから。放置してたらこの有様です。

では早速解説を…と思ったんですが、この匿名メソッド、なんだか難しく考えない方が良さそうな気がします。まぁ簡単に(ほんとうに簡単に)「デリゲートを使用する際に外部に書き出してたメソッドをインラインで書けるようにしちゃおうというもの」と考えるのが良さそうです。

デリゲートとはとか言い出すと切りがなくなるのでそれは勘弁してもらって、以下のようなサンプルがあるとします。

static void Main(string[] args)
{
var thread1 = new Thread(new ThreadStart(Plus));
var thread2 = new Thread(new ThreadStart(Minus));
thread1.Start();
thread2.Start();
}

static void Plus()
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(i.ToString());
Thread.Sleep(250);
}
}

static void Minus()
{
for (int i = 100; i >= 0; i--)
{
Console.WriteLine(i.ToString());
Thread.Sleep(500);
}
}

このサンプルを匿名メソッドを利用する形に変更すると以下のようになります。PlusとMinusそれぞのメソッドがインラインで表現されてるのが分かると思います。ついでにスコープ外の参照ができることも確認できると思います。

static void Main(string[] args)
{
var message1 = "20の倍数です(スコープ外も参照できます)";

var thread1 = new Thread
(delegate()
{
for (int i = 1; i < 100; i++)
{
Console.WriteLine(i.ToString());
Thread.Sleep(250);

if (i % 20 == 0)
{
Console.WriteLine(message1);
}
}
});

var thread2 = new Thread
(delegate()
{
for (int i = 100; i >= 1; i--)
{
Console.WriteLine(i.ToString());
Thread.Sleep(500);

if (i % 20 == 0)
{
Console.WriteLine(message1);
}
}
});

thread1.Start();
thread2.Start();
}

改めて見ても使いどころがピンときません。このサンプルももうちょっと他で使えるサンプルをと思ってたけどこんなのしか思いつかなかったし…。こんなんじゃ本質を理解していると言えない気がするけどラムダ式への通過点ということで…。

なんだか長くなりそうなので本命のラムダ式はまた次回で!


2012/07/24

コードファーストのMigration機能に感動した話

話の発端はコードファーストでは無く、複合キーの善し悪しについていろいろ調べてたことでした。自社システム更新にあたりデータベースまわりを見直してたんですが、複合キーが結構あったりして今考えてみるとこれって合理的なのか分かんないよなぁって思ったりなんかしたわけです。それで調べてみると複合キーは使わずにサロゲートキー+複合ユニーク制約(この辺の用語解説は長くなるので省略。)を使ってみなさいというお言葉があったので早速影響されてみました。

 …コードファーストで複合ユニーク制約ってどうすんのよ。この複合ユニーク制約、SQLServerで言うとこの「インデックスで一意キーの作成」になるわけですが、どうモデルに書けばいいんだ?そんな属性見当たらないんだけど?てなことで調べてみたわけですが、どうやらモデル作成時には無理っぽいです。

いきなり挫折しそうになりましたが、どうにかこうにか探ってみたところこの辺を参考にMigrationをいじれば出来そうな気がする。早速実験してみます。

 以下のようなEmployeeテーブルがあったとしてこれの「EmployeeCd」と「Name」に複合ユニーク制約を設けたいと考えます。

public class Employee
{
public int EmployeeId { get; set; }
public string EmployeeCd { get; set; }
public string Name { get; set; }
}



public class MyContext: DbContext
{
public DbSet Employees { get; set; }
}

当然このまま作成しても何もなりませんが、Migrationはテーブルが作成されてない状態では実行できないので一端これでテーブルを作成します。

 テーブルが作成できたら「ツール」から「パッケージマネージャーコンソール」を開き、「Enable-Migrations」を実行します(Enable-Migrationと打つだけです。)。すると以下のようなクラスが自動で作成されます。これがMigration時のコンフィグにたぶん使われるわけです。これの「AutomaticMigrationsEnabled = false;」を「AutomaticMigrationsEnabled = true;」に変えると自動Migrationを利用できたりしますが、今回はこのまま使います。

internal sealed class Configuration : DbMigrationsConfiguration
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}

protected override void Seed(PmsDataBase.PmsContext context)
{
//  This method will be called after migrating to the latest version.

//  You can use the DbSet.AddOrUpdate() helper extension method
//  to avoid creating duplicate seed data. E.g.
//
//    context.People.AddOrUpdate(
//      p => p.FullName,
//      new Person { FullName = "Andrew Peters" },
//      new Person { FullName = "Brice Lambson" },
//      new Person { FullName = "Rowan Miller" }
//    );
//
}
}

 続けてコンソールで、「Add-Migration CreateIndexEmployeeMigration」を実行します。CreateIndexEmployeeMigrationの部分は任意です。まぁ頻繁にMigrationしなければ主要な変更点を名前にした方が無難かなと思います。実行すると以下のようなクラスが作成されます。

public partial class CreateIndexEmployeeMigration: DbMigration
{
public override void Up()
{
}

public override void Down()
{
}
}

これのUpメソッドに変更処理を、Downメソッドに取消処理を書けばよいわけです。今回は以下のようになります。

public partial class CreateIndexEmployeeMigration: DbMigration
{
public override void Up()
{
CreateIndex("Employees", new[] { "EmployeeCd", "Name" }, true);
}

public override void Down()
{
DropIndex("Employees", new[] { "EmployeeCd", "Name" });
}
}

「CreateIndex(テーブル名, カラム名, ユニークの場合TRUE)」でユニーク制約付きのインデックスが作成され、「DropIndex(テーブル名, カラム名)」でそのインデックスが削除されます。インデックスに名前を付け、それを指定して削除することも可能です。

これでいよいよ更新です。コンソールで「Update-Database -TargetMigration:CreateIndexEmployeeMigration」を実行すると…エラーが起きます。エラーの内容はSQLServerが「文字数MAXのカラムをインデックス化できるか!」てなことみたいです。全然Migration関係無いエラーでした。なんてこった。

まぁせっかくなのでこれまたMigrationを利用してテーブルを修正しましょう。コンソールで「Add-Migration EmloyeeCdAndNameLengthMigration」(長すぎても気にしない)とし、以下のように編集します。

public partial class EmloyeeCdAndNameLengthMigration: DbMigration
{
public override void Up()
{
AlterColumn("Employees", "EmployeeCd", c => c.String(maxLength: 5));
AlterColumn("Employees", "Name", c => c.String(maxLength: 20));
}

public override void Down()
{
AlterColumn("Employees", "EmployeeCd", c => c.String());
AlterColumn("Employees", "Name", c => c.String());
}
}

「AlterColumn(テーブル名, カラム名, Funcデリゲート)」でカラムの編集が可能です。Funcデリゲート苦手です。これと先ほど作ったMigrationをUpdate-Databaseコマンドで順次実行すると今度こそ思った通りのテーブルになります。

実際のプロジェクト見ると分かると思いますが、Migrationはクラス名の前にタイムスタンプが付けられ世代と整合性保持に使われてるようです。Update-Databaseコマンドにパラメータなしに実行するとこのタイムスタンプ順に全てのMigrationが実行されます。よって今回の作り方だと先にインデックス作成が走るのでエラーになるので注意下さい。

ちなみに「Update-Database –TargetMigration: “0”」とすると全てのMigrationのDownメソッドが実行され、初期化されます。

かなり長くなりましたが、こんな感じでMigrationの便利さを感じた今日この頃でした。


2012/07/21

VBAのTypeステートメント

後輩指導してたらTypeステートメントなんてのに初めて気がついたのでメモ。

Typeステートメントはユーザー定義型が作れる模様。.Netでよくデータモデルを扱う人間には使い勝手が良さそうです。定義してあればタイプミスしてもコンパイルで引っかかってくれます。テキストデータやレコードの読み込み時なんかで使えそうです。

以下、サンプル。テキストデータから「店舗レコード」型配列を作成するイメージです。
'店舗レコード
Type StoreRecord
StoreCd As String
Name As String
OpenDate As String
CloseDate As String
End Type

'店舗レコード作成
Function CreateStoreRecords(lines() As String) As StoreRecord()
Dim fso As FileSystemObject
Set fso = New FileSystemObject
Dim stream As TextStream
Set stream = fso.OpenTextFile(fileName, ForReading)
Dim results() As StoreRecord
Dim count As Integer
count = 0
Do Until stream.AtEndOfLine
Dim elements() As String
elements = Split(stream.readLine, ",")
Dim store As StoreRecord
store.StoreCd = Format(elements(0), "000000")
store.Name = elements(1)
store.OpenDate = CDate(elements(2))
store.CloseDate = CDate(elements(3))
ReDim Preserve results(count)
results(count) = store
count = count + 1
Loop
Set stream = Nothing
Set fso = Nothing
CreateStoreRecords = results
End Function

普段はあまりVBAを書くことがありませんが、たまにこういう発見も悪くないなぁって感じです。まぁ昔作った物を異常に修正したくなったりしますが…。

ちなみに久しぶりにSyntax Highlighter使ったらまったくもって使い方を忘れた上に、なんだかうまいこと投稿できず、削除と投稿を繰り返したりしました。

2012/07/20

【アイデア】新システムに関するメモ2

会社の更新するシステムに関するアイデア。
今回はクライアントソフトのプロジェクト構成のアイデアをメモ。

構成

1.サービスレシーバー

 正直書くのが面倒な気もするけど…。あちこちでサービス本体を呼び出さず、余計なconfigファイルも作らなくてよいのでは的な考え。
 

2.レポートマネージャー

 VB-Report7を導入するのでそれに併せたマネージャーを用意する。
 エクセルファイル自体はNAS(1台しかないサーバーはWebとWCFサービス経由でしか接続しないから)に置く予定。


3.本体

 クライアントソフトの本体部分。今のところ現在のWindowsフォームアプリの形式を継承する予定。Silverlight5なんてのがいつの間にか登場してるみたいだけどWPFはもう死んでしまったのだろうか?
 XAMLというかデザイン部分を一人で書く根性がついたらそっちもありかも。


以上、そんな感じ。
Silverlightに手出したら確実に年度が変わっちゃいそうな気がしないでもない。絶対デザインに凝り始めるから。


2012/07/19

【アイデア】新システムに関するメモ

会社の更新するシステムに関するアイデア。
今回はWCFサービスのプロジェクト構成に関して頭の中にあるのを形にしとこうって感じ。

構成

1.サービスゲートウェイ

 サービスするメソッドの多さが今の悩み。とりあえず今2つあるゲートウェイは統合して、再構築しようかなって思ってる。場合によってはそこから再分割してゲートウェイを増やして1ゲートウェイに対するメソッド数を減らすという手もありなような、でもめんどいような。


2.データアクセス

 EntityFramework4以降追加されたコードファーストを使ってみようかなぁって感じ。モデル変更した場合の面倒臭さが難点だったけど4.3で自動マイグレーションなんて機能が追加されのである程度柔軟にいけるはず。
 現行では1テーブルに対してLINQをまとめたクラスを作ってたけどLINQの柔軟性といまいちマッチしてない気がしてたので今回は作らない方向で。LINQの発行はビジネスロジックで。


3.ビジネスロジック

 フォルダを機能カテゴリ別に作ってたけど、結局まとまってない気がするのでフォルダは極力作らないことにしよかと思ってる。名前がかぶるとこもうまいこと助長にならない程度に変更していく。今まで嫌ってた省略形解禁してもいいかも。
 .NetFrameworkを4.0にあげるからParallel LINQ (PLINQ)を使ってみたい。


以上が今のところの頭の中。
特にサービスゲートウェイに関してはビジネスロジックの出来次第って部分もある気がする。


2012/07/18

サーバーメンテ記録

サーバー再起動時にイベントログにSQLServerのReportingServiceのサービス起動エラー(レポート サーバー Windows サービス (MSSQLServer) 107)が発生して気になってた。


原因は不明というか、実際ReportingServiceは再起動後、起動して、サイトにもアクセスできてる。ググったりしてみたけどあまり有効な情報は出てこなかった。


考えた結果、もしかしたらReportingServiceの起動がSQLServer自体よりも先に起動しちゃってるんじゃ?と思いついたので下記のようにいじってみた。


サービス からSQLServerReportingServiceを開いてサービスの実行を「自動」から「自動(遅延)」に変更。


んで再起動してみたらログが出なくなった。ん~、これでいいのか?とりあえずサービス自体ちゃんと動いてるのでこのままにしとこう。ってことでメモっとく。

忘れないように個人的な目標をまとめとく

会社のメインシステムの更新

3年前に完成させた会社のメインシステムをそろそろ更新しようかなと思ってる。

現行のシステム体系(WCFサービス&クライアント、社内向けASP.Netサイト)とサーバー&DBは変えずに、.NetFrameworkを3.0から4.0へ、EntityFrameworkを3.0から4.3(出るなら4.5)へ、ASP.Net2.0をASP.NetMVC3へ移行したい。

それとクライアントのレポート系に先日買ってもらったVB-Report7を導入してやろうかなと。現行のVisualStudioのデフォルトのReport機能で作ったけど編集がしんどい上にローカルで動かしてるからいちいち配布し直さないといかんから。

あとはタブレット対応。もうこれが真の目的と言っても過言ではないって感じ。はっきり言って趣味。ぶっちゃけ自分のシステムをタブレットで動かしてみたいだけだっりする。でも「何を動かすの?」という最大の問題が…。まぁやるとしたらWebとjQueryでやると思う。Eclipseでjavaって気分でもないし、Objective-Cまではさすがに手が出ないし。


後輩の指導

微妙。

うちはしがない中小なので中途でプログラミング経験者なんて雇えませんって話らしい。だからって全くの素人にC#までたどり着かせることができるだろうか?

愚痴ってもしょうがないので今年度中に何とかAccessとVBA位は使い倒せるようになってもらいたいなぁ。


英語を読めるように…

なんかこれは目標というか願望に近い気がする。とりあえず総合英語Forest 6th editionDUO 3.0 は買ってみた。

これで洋書もばっちり!!って感じになればいいんだけど…。


なんだか…

いろいろと忘れやすくなってきたみたいなので、C#やらWebサービスやら今度こそ続けて書いていこうかと思ったり。
特にサーバーのどこいじったかとか。
なんかいつの間にか再起動するとイベントビューアーに原因不明のエラーが…。
Diagnosis-DPSでいろいろやってるらしい。イベントID175のエラー後に、イベントID165のエラーが発生してを繰り返している模様。ググっても分からん。


pagetop