2012/08/31

【jquery】たまにはjqueryもやってみたり…

 まぁ、大したことはしないんですが。ふと自分のブログに「ページトップへ」ってのが無いことに気がついたわけですよ。んで、普通にフッターにでも設置してもよかったんですけど、それじゃあ面白味がないよねってことで以前作ったホームページにjqueryである程度スクロールするとページトップへのリンクが表示されるプラグイン(jQuery Scroll to Top Control v1.1)使ったの思い出したわけです。
 それじゃ早速設置してみようかなぁって思ったんですけど、Bloggerってテキストファイルのアップロードができないんじゃない?ってことに気がついてしまったわけで…。じゃあ直接書くしかないじゃんってわけです。

 とりあえず最初にリンク用の画像を用意します(別に普通のテキストリンクでも動きます)。まぁこれは適当に作成します。




 次に<body>タグの適当な場所に<a>タグと<image>タグを置きます。まぁ<body>タグの最後辺りが無難ではないでしょうか?

<div id='pagetop'>
    <a href='#header'>
        <img alt='pagetop' src='画像リンク先'/>
    </a>
</div>


 次はCSSです。見ての通り対象idにfixed属性を付けて右下に固定しています。<a>タグに対してはテキストリンクにしている場合はもうちょっと工夫した方がいいかもしれません。

#pagetop {
    position: fixed;
    bottom: 5px;
    right: 5px;
}
 
#pagetop a
{
    width: 50px;
    height: 100px;
    display: block;
    text-decoration: none;
}


 最後にスクリプトを書きます。対象idを非表示化→スクロール幅判定→対象idフェードイン→(クリックで)ページトップへアニメーションって感じです。スクロール幅の判定とアニメーションスピードの数値は任意で変更できます。自分は大体ヘッダーが見えなくなったくらいで表示するように調整してます。

<script type='text/javascript'>
    $(document).ready(function () {
        $('#pagetop').hide();
 
        $(window).scroll(function () {
            if ($(this).scrollTop() > 400) {
                $('#pagetop').fadeIn();        
            } else {            
                $('#pagetop').fadeOut();
            }
        });
       
        $('#pagetop a').click(function () {
            $('body,html').animate({
                scrollTop: 0
            }, 400);
            return false;
        });
    });
</script>


 以上です。実際の動作はこのブログ上で確かめてみて下さいってことで、たまにはjqueryを載せてみたの巻でした。


 参考にさせていただいたサイト:「スクロールでtopへ戻るボタンを表示するjQueryコード(スムーススクロール)」「jQueryでスクロールすると表示する系いろいろ

2012/08/30

【雑記】本日の箇条書き

1.単体テストでのCollection要素の比較方法

 VisualStudioの単体テスト機能を使ってCollection要素を比較する方法です。って言っても「CollectionAssert.AreEqual メソッド(http://msdn.microsoft.com/ja-jp/library/ms243763(v=vs.100))」で一発です。Collection要素を比較するってこと事態まれではあると思いますが、たまたま使ったので…。
 以下、しょうもないサンプルです。
[TestMethod()]
public void MyMethodTest()
{
    MyClass target = new MyClass();
    var expected = new Collection<string> { "Tanaka", "Suzuki", "Yamada" };
    Collection<string> actual;
    actual = target.MyMethod();
    
    //デフォルトのAssert.AreEqualだとこけます
    //Assert.AreEqual(expected, actual);
    
    CollectionAssert.AreEqual(expected, actual);
 
    //Assert.Inconclusive("このテストメソッドの正確性を確認します。");
}


2.LINQでテーブルを多重結合する際の悩み

 LINQでちょこちょこテーブルを多重に結合する訳なんですが、これが下記のコードのように多重化すればするほどややこしくなっていかんって話。
var myQuery = from p in 
        (from p in
            (from p in elements1
             join g in elements2
             on p.ACd equals g.ACd
             select new { p, g })
         join g in elements3    
         on p.p.BCd equals g.BCd
         select new { p, g })
    join g in elements4
    on p.p.p.CCd equals g.CCd
    select new
    {
        elements1 = p.p.p,
        elements2 = p.p.g,
        elements3 = p.g,
        elements4 = g
    };
見ての通り、pとgのオンパレードです。とりあえず最終的にテーブル名に戻していますが、何かもうちょっとスマートにならんかなぁって感じです。かといっていちいちSELECT句で名前付けたり、LINQを小分けする気にもならないし…。

2012/08/29

【Access】複数のテーブルを1つのフォームで更新する方法を考える【VBA】

 後輩君が何やら頭を抱えて悩んでいたので聞いてみた。

「現在とある業務で使用してるAccessのテーブル「A」と「B」と「C」は正規化の観点からするとひとつのテーブルに集約することができるが、フィールド数が多くなることと、業務の流れとして「A」が確定してから「B」と「C」を入力するということであえて分割してある。現在、それぞれのテーブルを更新・削除を行う場合、個別のフォームを用いているが、作業量が増えてきた関係で煩わしくなってきた。更新・削除を1つのフォームから行うことはできないだろうか?」

 …というのが悩みの種のようです。ちなみに後輩君のレベルは簡単なクエリとそれに基づいたフォームが作れるくらいです。確かにちょっと悩んじゃうかもしれません。個人的には追加部分を工夫するとして正規化しちゃえばいいじゃんってとこですが、まぁ、そうできないケースも今後発生するかもしれないのでとりあえずこのまま挑戦させてみようかなぁってことで、このブログでは後輩君をほっておいて自分的な考察やらコードをあげちゃいます。聞くだけ聞いてなんなんですが後輩君には自分で考えてもらいます。

 とりあえずサンプルデータです。「A」「B」「C」のテーブルは「A」を基点としてそれぞれ1対1で連結しています(フィールド数が少ないことは気にしない)。

A B C
Id AName Id AId BName Id AId CName
1 太郎 1 1 1 1 オス
2 花子 2 2 2 2 メス


 次に根本的な考え方ですが、フォームとテーブル(クエリ)の連結は最初からあきらめちゃいます。どうやら後輩君が1番引っかかってる部分のようですが、1つのフォームに複数のテーブルを連結することはできません(たぶん)。サブフォームを2つ使えばって考えもありますがその場合、更新のタイミングの問題も出てくると思います(例えばBを更新する際、AとCの更新は?みたいな)。イメージ的には下図のようになります。

フォームに表示する流れ
image

更新・削除をする流れ
image

 VBA上でSQL文を使用して「A」「B」「C」各テーブルと「A」「B」「C」を結合した独自型をつなげ、その独自型とフォームを連動させます。独自型を使うのははっきり言って趣味です。飛ばして直でフォームにいっちゃっても問題ないと思います。それでは流れに基づいてメソッドを作ってみます。

Option Compare Database
Option Explicit
 
'独自型
Public Type MyData
    Id As Integer  'このIdはAのId
    AName As String
    BName As String
    CName As String
End Type
 
'生成メソッド
Public Function GetMyData(targetId As Long) As MyData
 
On Error GoTo ErrProcess
 
    Dim cn As ADODB.Connection
    Set cn = CurrentProject.Connection
    
    Dim selectSql As String
    selectSql = "SELECT A.Id, A.AName, B.BName, C.CName " _
        & "FROM (A INNER JOIN B ON A.Id = B.AId) " _
        & "INNER JOIN C ON A.ID = C.AId WHERE A.Id = " & targetId
    
    Dim rs As ADODB.Recordset
    Set rs = New ADODB.Recordset
    rs.Open selectSql, cn, adOpenKeyset, adLockReadOnly
    
    Dim result As MyData
    
    '0件はエラー、2件以上については今回は考えません
    If (rs.RecordCount = 0) Then
        MsgBox "レコードを取得できませんでした。", vbExclamation, "エラー"
    Else
        result.Id = rs!Id
        result.AName = rs!AName
        result.BName = rs!BName
        result.CName = rs!CName
    End If
    
    rs.Close: Set rs = Nothing
    cn.Close: Set cn = Nothing
    
    GetMyData = result
    
Exit Function
 
ErrProcess:
 
    rs.Close: Set rs = Nothing
    cn.Close: Set cn = Nothing
    
    MsgBox Err.Description, vbCritical, "エラー"
    
End Function
 
'更新メソッド
Public Sub UpdateMyData(updateData As MyData)
 
On Error GoTo ErrProcess
 
    Dim cn As ADODB.Connection
    Set cn = CurrentProject.Connection
    
    Dim updateSqlA As String
    updateSqlA = "UPDATE A SET AName = '" & updateData.AName & "' " _ 
        & "WHERE Id = " & updateData.Id
 
    Dim updateSqlB As String
    updateSqlB = "UPDATE B SET BName = '" & updateData.BName & "' " _ 
        & "WHERE AId = " & updateData.Id
    
    Dim updateSqlC As String
    updateSqlC = "UPDATE C SET CName = '" & updateData.CName & "' " _ 
        & "WHERE AId = " & updateData.Id
    
    cn.Execute (updateSqlA)
    cn.Execute (updateSqlB)
    cn.Execute (updateSqlC)
    
    cn.Close: Set cn = Nothing
    
Exit Sub
 
ErrProcess:
 
    cn.Close: Set cn = Nothing
    
    MsgBox Err.Description, vbCritical, "エラー"
    
End Sub
 
'削除メソッド
Public Sub DeleteMyData(deleteData As MyData)
 
On Error GoTo ErrProcess
 
    Dim cn As ADODB.Connection
    Set cn = CurrentProject.Connection
    
    Dim deleteSqlA As String
    deleteSqlA = "DELETE FROM A WHERE Id = " & deleteData.Id
    
    '連鎖削除設定してたら以下のBとCのDELETE文は不要
    Dim deleteSqlB As String
    deleteSqlB = "DELETE FROM B WHERE AId = " & deleteData.Id
    
    Dim deleteSqlC As String
    deleteSqlC = "DELETE FROM C WHERE AId = " & deleteData.Id
    
    cn.Execute (deleteSqlA)
    cn.Execute (deleteSqlB)
    cn.Execute (deleteSqlC)
    
    cn.Close: Set cn = Nothing
 
Exit Sub
 
ErrProcess:
 
    cn.Close: Set cn = Nothing
 
    MsgBox Err.Description, vbCritical, "エラー"
    
End Sub


 やってることはシンプルにしたつもりです。あとはリスト型のフォームと入力用のフォームを用意して各メソッドを呼び出してやれば完成です。さすがにフォームは省略させて下さい。


 以上が自分なりの後輩君の悩みへの解決方法です。いやぁ、後輩君はどんな方法で解決するか楽しみですね~。簡単にギブアップしないことを祈ってます。

2012/08/28

【C#】LINQPadのご紹介

 手軽さと便利さに感動したのでLINQPadを軽く紹介します。その名の通り、LINQに関連するアプリで、LINQ単体でのテスト・評価を行うことができ、自分のように開発中にLINQの成否に自信がないとか、結果セットが思った通りになっているか気になるって人にとてもおすすめです。上記リンク先からダウンロード&インストールができます。起動画面は下画像のようにいたってシンプルで、データベースを選択し、LINQを書いて実行してみるって感じです。

デフォルト画面
linqpad0

 実行するとLINQの成否だけでなく下画像のように結果セットやラムダ式等も表示してくれます。

結果セットやラムダ式の表示
linqpad1linqpad2


画像では単純なLINQを書いていますが、もっと複雑なLINQを組む時はこれを使うだけで安心感がぐっとアップします。また、一度作成したLINQの式は保存することもできます。

 自分はデータベースでしか使ったことはないのですがWCFデータサービスでも使えるようです。また言語はC#の他にVBとF#等に対応してます。

 表記が英語なのでメニュー等は分かりづらく感じるかもしれませんが、シンプルだし慣れれば大丈夫だと思います。

 以上、LINQPadのご紹介でした。

2012/08/25

【C#】CodeFirstのMigrationが元に戻せない

 以前、CodeFirstのMigrationに感動したって記事でマイグレーションで制約(インデックス作成)を作成したんですけど、分け合って一度それを初期化したいなぁって思ったんですよ。

 それで初期化方法である「パッケージマネージャーコンソール→コマンド実行:Update-Database –TargetMigration: “0”」ってやって処理が走ったんですけど最後に赤字で「Automatic migration was not applied because it would result in data loss.」と仰るわけです。「データがロスするから自動実行せんかったでー。」って意味っぽいなと思ってデータベース自体を見てみるとやっぱりインデックスが削除されてない。

 どうやらSeedメソッドでテストデータを突っ込んだんでレコードが存在するんでストップをかけてくれてるみたいですね。親切は親切なんだけどこれじゃ元に戻せないじゃないか…。とは言ってもまだ全然作り始めなんでデータベース自体をサクッと削除して再度コード走らせれば済むんですけどね。こういう点はやっぱりコードファーストは便利がいいです。

 しかし実際、稼働後にそういう自体になったらどう対応するかって話しですよ。直接データベース触っちゃったらMigrationと差異が出ちゃうわけだし。う~ん、どうしよ。とりあえず明示的にインデックスを削除するマイグレーションを実行してみようかな。

  という訳で実験です。テストテーブルに下記のようなマイグレーションを作成・実行しデータベースにインデックスを作成したとします。

//インデックス作成マイグレーション
public partial class CreateIndexMigration : DbMigration
{
    public override void Up()
    {
        CreateIndex("TestTable", new[] { "TestIndex1", "TestIndex2"}, true);
    }
 
    public override void Down()
    {
        DropIndex("TestTable", new[] { "TestIndex1", "TestIndex2"});
    }
}


 これのUpメソッドとDownメソッドの中身を入れ替えインデックス削除マイグレーションを作成します。

//インデックス削除マイグレーション
public partial class DropIndexMigration : DbMigration
{
    public override void Up()
    {
        DropIndex("TestTable", new[] { "TestIndex1", "TestIndex2"});
    }
 
    public override void Down()
    {
        CreateIndex("TestTable", new[] { "TestIndex1", "TestIndex2"}, true);
    }
}


 それでこれを実行してみると…。

PM> Update-Database -TargetMigration:DropIndexMigration
Specify the '-Verbose' flag to view the SQL statements being applied to the target database.
Applying code-based migrations: [201208250220033_DropIndexMigration].
Applying code-based migration: 201208250220033_DropIndexMigration.
Running Seed method.

 はい、あっさり成功しちゃいました。このようにマイグレーションで定義したインデックスを削除したい場合はインデックスの削除を行うマイグレーションを作成してやれば実行可能なようです。

2012/08/24

【SQLServer】ID(Identity設定)を初期化する方法

ちょっと短いけどメモ。
適当なくせに割と細かい性格なもんでテストデータとかを挿入・削除した後のテーブルのIDが中途半端な数値になったりするのが気になったりする今日この頃。
ちょっと調べてみる(http://msdn.microsoft.com/ja-jp/library/ms176057.aspx)とTransact-SQLの「DBCC CHECKIDENT 」なる構文で初期化できる模様。

例文:「DBCC CHECKIDENT ("テーブル名", RESEED, 1);」

ちなみにテーブルにレコードが残っている場合、上記の場合「1+現在の増分値」が使用されるとのこと。下手すると既存のレコードのIDと競合しちゃいますってことみたいです。

2012/08/23

【雑記】本日の箇条書き

いやぁー、ろくに仕事もせずにブログいじくってましたって感じです。

  1. ブログにGoogleAdsenseとAmazonアフェリエイトを突っ込んでみた

     まぁ、ソース突っ込んだけなんですけどね。上下ともBloggerの機能で言うとこの「レイアウト」で「ガジェット追加」から「HTML/JavaScript」を選択し、それにソースを貼り付けています。あと、CSSを効かせるために<div></div>で括っています。CSSは「テンプレート」の「HTMLの編集」で直接書き込んでます。「カスタマイズ」の「CSS追加」だと挿入位置が何だか分からなくなりそうだったので。


  2. SQLServerのSQL文のパラメータに日付を渡す時の話

     ちょっと通常業務とは外れたイレギュラーなデータ抽出を頼まれたのでSQLServerのManagementStudio使って直接SQL文を書いたんですがちょっと分かんないことがあったので残しときます。
     そのSQL文っていうのが特定の日付以降のデータを抽出するっていうSQL文なんですが、何気なしに日付のパラメータを文字列で指定してたんですが問題なく通りました。それでそのSQL文を元にもうちょっと複雑なのを書こうとした時、面倒なところがあってデザイナ使ったんですが、デザイナを間にかますと日付の文字列をConvert文でわざわざ変換してくれてるんですよね。
     なぜなんでしょう?前者は内部的にSQL文の実行時に変換してくれたりしてるんだろうか?というわけで実験してみました。
    //見ての通り有り得ない日付を渡してみます。
    SELECT * FROM DummyTable
    WHERE TargetDate >= '51234597'
    

     実行してみると「文字列から日付と時刻、またはそのいずれかへの変換中に、変換が失敗しました。」とエラーが返ってきました。やっぱり親切にも実行時に変換してくれてるみたいです。じゃぁデザイナは何のために?って気もしますがそっちはそっちで親切なんでしょうってことにしとこうかなぁ。

以上、本日の箇条書きでした。

2012/08/22

FC2から移転しましたよ。

FC2ブログから移転しちゃいました。

インポートで日付が飛んだりしたけれど私は元気です。しかも、後から気がついたけどSyntaxHightlighterさんの表示がおかしい。

どうやら元々Chromeだと表示が崩れるらしい。BloggerにするついでにChrome使ってみて初めて気がついた。

一応以下に解決方法。こちらとかこちらを参考にさせてもらいました。
    <!-- SyntaxHightlighter定義 -->
    <link href='http://alexgorbatchev.com/pub/sh/current/styles/shCore.css' rel='stylesheet' type='text/css'/>
    <!-- Crome対策 -->
    <style type='text/css'>
    <!--
    .syntaxhighlighter {
      overflow: auto !important; 
      overflow-y: hidden !important;
    }
    .syntaxhighlighter table td.gutter .line {
      padding: 0px !important;
    }
    -->
    </style>

shCore.cssの後に.syntaxhighlighterと.syntaxhighlighter table td.gutter .lineを再定義し、上書きしています。

2012/08/21

【雑記】本日の箇条書き

まじめに書くことというか変わったことがなかったので今日やったことの箇条書き。

  1. コードファーストのConfigurationクラスにただひたすらテーブルのテスト用データを書き込んでたよ。以下サンプル。

    //Entityクラス
    class Employee
    {
    public int Id { get; set; }
    public string EmployeeName { get; set; }
    }
     
    //Contextクラス
    class MyContext : DbContext
    {
    public DbSet<employee></employee>Employees { get; set; }
    }
     
    //Configurationクラス
    internal sealed class Configuration : DbMigrationsConfiguration<mydatabase .mycontext=".mycontext"></mydatabase>
    {
    public Configuration()
    {
    AutomaticMigrationsEnabled = false;
    }
     
    //ここに各テーブルのレコード追加処理を書いておくと自動的に追加されます。
    protected override void Seed(MyDataBase.MyContext context)
    {
    context.Employees .AddOrUpdate(
    new Employee
    {
    EmployeeName = "Testくん1号"
    },
    new Employee
    {
    EmployeeName = "Testくん2号"
    });
    }
    }
    
  2. Wordで改行後にスペースが入らずに字下げが入ってしまうと相談を受けたよ。オートコレクトのオプション(行の始まりの~とTab/Spase/BackSpaceキーで~)を変更することで防げるはず。
  3. Accessのコンボボックスで選択後の表示を2列にするには?って聞かれたよ。基本的にそんな機能は無いのでクエリで「式:[1列目]&” | ”&[2列目]」とするしかないんじゃない?個人的にはいまいち不細工な気がするからコンボボックスの隣にラベルでも置いた方がいいと思うんですが…。


2012/08/19

【C#】いつの間にやら2012

いつの間にかVisualStudio2012の開発が終了してるみたいです(Visual Studio 2012)。それに合わせてNuGetの方でEntityFramework5.0の配布も始まってるようです。Enumがサポートされるようですが、いまいちピンときてませんのでこれはまた今度ってことで…。このへん読んだら何となくは分かるかも…。

とりあえず現在開発中のものを何も考えず5.0に引き上げてみました。今のところTable属性の名前空間が変更になってる(System.ComponentModel.DataAnnotations → System.ComponentModel.DataAnnotations.Schemaになってる模様)以外特に手直しした点はありません。

それにしてももう2012リリースすんのかよ…。ついこの間2010買ってもらったばっかりな気がするんだけど…。


2012/08/15

【C#】EntityFrameworkとWCFに関する悩み事

前々からの悩みと言えば悩みだけど、EntityFrameworkで作成したエンティティをWCFのデータコントラクトで使っちゃっていいのかって話。

現行システムはデータベースからエンティティを生成した関係でWCFでそのままデータコントラクトで公開しちゃうとフィールド以外の内包されたデータが見えちゃうのが何か嫌でエンティティをコピーしたモデルを作ってそれでWCFでやりとりしたりしたんだけど、ぶっちゃけかなり面倒だったりした(特にデータベース修正時)。

今回はコードファーストでやってるからエンティティにデータコンテキスト属性つけてそのまま使っちゃえばいいんじゃないか?って最初考えてたんだけど、たとえばテーブルを結合したビューとかはどうすんの?って考えたときコードファーストのエンティティ達とそれらとは役割的に別物なんじゃないの?って思っちゃたりしたわけで…。じゃあWCF上でデータコントラクトで括って同一視したらまずいんでないの?って話になっちゃうんですよね。

こんなこと考えてると制約ばっかりになりそうな気もするし、社内向けのシステムでそこまで凝る必要ないだろって突っ込まれそうな気もするけど性分なんで…。

とりあえず面倒だけど現行を踏襲する方向かなぁ。なんかただの愚痴みたな感じなってしまった気がする。


2012/08/11

【雑記】本日の箇条書き

今日したこと、あったことをメモっとく。


コードファーストのテーブル名

コードファーストでデータベースを作成したわけだけど、テーブル名が1個だけ変。特にテーブル名に括りがないからそのままデータベースを作成したんだけど何故か1個のテーブルだけ「s」が付かずにそのままエンティティ名になっちゃう。どうやら調べるにデータベース構築時に複数形にできないと判断された場合「s」が付かずにそのままになる模様。そのエンティティ名って普通の英単語なんですが…。このままでも支障があるわけじゃないけど気持ちが悪いので全エンティティにTable属性([Table(“名前”)])を指定して任意の名前にした。


ホームページ作りませんか?

なんて営業が先日あったことを思い出した。いゃいゃ、うち自作のHPもうあるんだけど?調べずに電話してきてんの?って思ったんだけどよく考えたら自作したのって親会社のHPだった(その中にちょこっとだけ自社の紹介が載ってる)。う~ん、特別載せること亡いけどとりあえずHPくらい作った方がよいのか?たぶんうちのHPなんてA4で1ページくらいで収まりそうなんだけど…。そんなもんにレンタルサーバー借りるのか?FC2に無料のレンタルサーバーあるっぽいけど企業のHPに広告が出ちゃうってのもちょっとなぁと思ったりした。とりあえず保留。


2012/08/09

【雑記】たまにはこんな日もあるってことで…

ちょっとした理由でやる気が削がれちゃったりしたのでちょっと気分転換に前から気になってた「Rainmeter」と「RocketDock」を使ってデスクトップをいじくってみたので各アプリを簡単に紹介しときます。


Rainmeter

こちらは今は亡きWindowsガジェットってイメージで良いのかな?デスクトップ上に多様な通知領域を作成できます。残念なことになってしまったWindowsガジェットと違い、種類(テーマ・スキン)が非常に豊富です。
使ってみた感想的には若干スキンの設定が難しいと感じました。スキンによってはiniファイルを直接操作しないといけない場合があります。
どうでも良いことですが自分こーゆーのって最初はいろいろのっけてみたりするんですが、結局シンプルなのに落ち着くっていうかむしろ全部削除しちゃえばいいじゃね?というひどいパターンに陥りやすいです。とりあえず今のところ全削除は間逃れています。


RockDock

こちらはいわゆるランチャーです。最近、デスクトップがごちゃごちゃしてきたのとあわよくばWindowsスタートを使わない方向にと考えて入れてみました。こちらもアイコン等が豊富です。「Stacks docklet」という拡張機能も導入してみました。
使ってみた感想ですが設定が簡単だし、Stacks dockletでショートカットもまとめれたしで結構満足です。ただ、どうやら64bitはサポートしてないようで64bit用にインストールされたアプリはショートカットでも起動しない模様です(自分はVirtualBoxが動きませんでした)。そんなわけでWindowsスタートはまだ必要そうです。残念。


それでは最後にカスタマイズされたデスクトップを披露しときます。



…なんか改めて見ると地味だな。ちなみに背景はWin7のテーマ「Bing Dynamic」です。RSSから自動で新しい壁紙をダウンロードしてくれます。当たり外れが激しいですが自分的には飽きがこないのでそれなりに気に入ってます。


2012/08/08

【C#】コードファーストでくだらないミス

いやぁー、参りました。ここ2、3日コードファーストからデータベースが作成できなくてモデルとにらめっこしてました。また、忘れて同じ事しないようにメモっとく。

原因1

テーブルAとテーブルBを複合外部キーを利用してリレーションしてやろうと思ってモデル側に「ForeignKey」属性をつけてみたんだけど、どうやら複数指定できない模様。じゃぁFluentAPIか?と思ってDbModelBuilder.Entity<>().HasRequired().WithMany().HasForeignKey()ってのを見つけたけどこれまた複数指定の方法がないっぽい。
…う~ん。複数指定ができないなら複合型を使ってモデル側でフィールドをまとめちゃえばできなくはないかって気はするけど、外部キーにしたいからって複合型使うのってどうなんだろ?
結局のところ、とりあえず複合外部キーはあきらめてIDを振ってそれを外部キーにすることにしました。フィールドにIdとは異なるコードとかあるからごちゃごちゃするの嫌だったんだけどしょうがないか。

原因2

原因1を解決して、さぁ今度こそデータベース作成しちゃうぞって思った矢先に「System.NotSupportedException Message=PrimitiveType 'SByte' の概念側の型 'Edm.SByte(Nullable=True,DefaultValue=)' に対応する格納型がありません。」なんてよく分からんエラーが…。
これ、原因言っちゃうと「string型をsbyte型にしてた」っていう馬鹿なタイプミスなんですど、これにたどり着くまでこれまた馬鹿みたいに時間食っちゃいました。だってsbyteなんて馴染みがなさすぎて…。まぁエラー自体、そんな型使えませんよ的なエラーだったからもっと早く気がつくべきだったんでしょうけどsbyte自体馴染みが無かったもんで…。散々悩んでソース内を検索してみたら一発でタイプミスの箇所が出てきてかなりショックでした。

てな感じで困った数日間でした。原因1に関してはもうちょと整理できたら複合型も考えてみてもよいかもって気もします。


2012/08/04

【Access】自作自演で課題をやってみる4

前回の続きです。フォームとレポートを簡単にやっつけちゃいます。

まずフォームから作ります。選出の際、年と月が必要になるのでそれを決定し、レポートを表示するってイメージで。



あっさりしてますがこんな感じでよいでしょう。決定ボタンのイベントはレポートが完成してから書きます。

次にレポートです。とりあえずレポート用のクエリを作ってやります。クエリのSQLビューをまんまコピーするとこんな感じです。

SELECT Employees.EmployeeNo, Employees.EmployeeName, Employees.ChoseDate
FROM Employees
WHERE (((Month([ChoseDate]))=[Forms]![MainForm]![MonthComboBox]));

月で抽出するため、WHERE区で先ほど作ったフォーム(MainForm)の月のコンボボックスを参照しています。

ではクエリを元にレポート作成します。



「ChoseDate」を週単位でグループ化しています。レポートビューにするとこんな感じ。



まぁ、サイズのことは気にしないで下さい。後はこれをフォームの決定ボタンで表示されるようにするだけです。

以下、決定ボタンのクリックイベントです。

'決定ボタンクリックイベント
Private Sub CommitButton_Click()

Dim targetYear As Integer
targetYear = Me.YearCombobox.Value

Dim targetMonth As Integer
targetMonth = Me.MonthComboBox.Value

Dim resultDay As Variant

For Each resultDay In Work.GetSunday(targetYear, targetMonth)
Work.ChoiceEmployee (CDate(resultDay))
Next

DoCmd.OpenReport "ChoiseReport", acViewPreview

End Sub

これで無事完成です。まぁ本当はもっといろんなケースを考えた方がよいでしょうけど(重複実行した場合とか)、とりあえずはこれにて第1回「自作自演で課題をやってみる」は終了です。


2012/08/03

【Access】自作自演で課題をやってみる3

前回の続き。

SQL文が出来たのでそれを使ってVBAを組んでみます。個人的な趣味で変数の使い回しやレコードセットの使い回しは禁止してますので若干長くなりがちですが気にしません。

'targetDate = 抽出日
Sub ChoiceEmployee(targetDate As Date)

'選出人数管理用
Dim choiceNum As Integer
choiceNum = 0

Dim cn As ADODB.Connection
Set cn = CurrentProject.Connection

'最初の選出SQL文
Dim selectSql1 As String
selectSql1 = "SELECT TOP 5 * FROM Employees WHERE ChoseDate = #9999/12/31# ORDER BY Rnd([Id])"

Dim rs1 As ADODB.Recordset
Set rs1 = New ADODB.Recordset
rs1.Open selectSql1, cn, adOpenKeyset, adLockOptimistic

'もしレコードが0件なら1巡してるので初期化
If rs1.RecordCount = 0 Then
Dim updateSql1 As String
updateSql1 = "UPDATE Employees SET ChoseDate = #9999/12/31#"
cn.Execute updateSql1
Else
Do Until rs1.EOF
'選出日をtargetDateで更新
Dim updateSql2 As String
updateSql2 = "UPDATE Employees SET ChoseDate = #" & targetDate & "# WHERE Id = " & rs1![Id]
cn.Execute updateSql2
choiceNum = choiceNum + 1
rs1.MoveNext
Loop
End If

rs1.Close
Set rs1 = Nothing

'選出人数が足りない場合
If choiceNum < 5 Then
'2回目の選出SQL文
Dim selectSql2 As String
selectSql2 = "SELECT TOP " & 5 - choiceNum & " * FROM Employees WHERE ChoseDate <= #" & DateAdd("m", -3, targetDate) & "# ORDER BY Rnd([Id])"

Dim rs2 As ADODB.Recordset
Set rs2 = New ADODB.Recordset
rs2.Open selectSql2, cn, adOpenKeyset, adLockOptimistic

Do Until rs2.EOF
'選出日をtargetDateで更新
Dim updateSql3 As String
updateSql3 = "UPDATE Employees SET ChoseDate = #" & targetDate & "# WHERE Id = " & rs2![Id]
cn.Execute updateSql3
Loop

rs2.Close
Set rs2 = Nothing
End If

cn.Close
Set cn = Nothing

End Sub

またまた個人的な趣味ですがレコードセットを直接更新かけるのが嫌いなのでUPDATEのSQL文を書いてます。別にレコードセットを直接「rs![ChoseDate] = 日付」&「rs.Update」とかやっちゃってもかまいません。

次にレポート作成の前にもう1つレポート印刷時に実行する処理をついでに組んでしまいましょう。作成条件の「毎月、月初めにその月の掃除当番表を作成し、出力する。」ってやつで選出の実行はこれが出力されるタイミングでやっちゃえば良いでしょうなんて考えてました。なので流れは…月を入力→各週の当番選出→印刷って感じかなぁ。

選出メソッドは出来てるので、対象月の各週毎にそれを実行すること考えます。まず基準日がいりますね。これは特に指定してませんので各週の日曜日にしちゃいます。なので対象月の全ての日曜日の日付を取得する必要があります。

'targetYear = 対象年
'targetMonth = 対象月
Function GetSunday(targetYear As Integer, targetMonth As Integer) As Date()

'その月の1日の曜日番号を取得
Dim targetWeekNum As Integer
targetWeekNum = Weekday(DateSerial(targetYear, targetMonth, 1))

'最初の日曜日を取得
Dim firstDay As Date

If 1 = targetWeekNum Then
firstDay = DateSerial(targetYear, targetMonth, 1)
Else
firstDay = DateSerial(targetYear, targetMonth, 1 - targetWeekNum + 8)
End If

Dim tempDay As Date
tempDay = firstDay

Dim result() As Date
Dim arrayNum As Integer
arrayNum = 0

'月が変わるまでループ
Do While targetMonth = Month(tempDay)
ReDim Preserve result(arrayNum)
result(arrayNum) = tempDay
tempDay = tempDay + 7
arrayNum = arrayNum + 1
Loop

GetSunday = result

End Function

こんな感じでどうでしょう。最初の日曜を求める部分の「DateSerial(targetYear, targetMonth, 1 - targetWeekNum + 8)」は分かりづらいかもしれませんが計算したらそうなったって感じです。

だいぶ完成が見えてきました。次回はフォームとレポートをちゃっちゃっと作ってしまいましょう。


【Access】自作自演で課題をやってみる2

さて前回の続きです。今回はとりあえずクエリかSQL文の作成から始めます。クエリとSQL文どっちにしようか迷いましたが、個人的な趣味でSQL文を使用することにします。こう書くとクエリとSQL文が別物のように受け止められそうですが根本的にはクエリもSQL文です。アプリ上で定義するかしないかの違いだけです。個人的にパラメータクエリが面倒なのでSQL文の方が好きなだけです。

では早速1つめのSQL文(VBAに書くことを前提にしてます)です。社員テーブル(Employees)からまだ掃除当番に選出されていない人を5名ランダムに抽出します。

=“SELECT TOP 5 * FROM Employees WHERE ChoseDate=#9999/12/31# ORDER BY Rnd([Id])”

前回、載せ忘れてましたが、未選出の人は9999/12/31にしちゃいます。要はこれが選出・未選出のフラグです。SQL文のポイント最後のORDER BY区です。ここにAccessの持つ「Rnd関数」に自身の「Id」を割り当て、レコード毎に乱数を発生させ、それを並べ替えちゃってます。「Id」をseed値として扱うため基本的に乱数が重複することはありません。若干分かりにくい気もしますがRnd関数についての詳しい解説はヘルプを参照してみてください。

次に2つめ。上記SQL文で5名決定できなかった場合、つまり未選出が5名以下になった状態での抽出です。

=“SELECT TOP “ & <必要選出数> & “ FROM Employees WHERE ChoseDate <= #“ & <3ヶ月前> & “# ORDER BY Rnd([Id])”

<>部分がパラメータです。TOPとWHERE区に対してVBA上でパラメータを当てます。ちなみにクエリのデザイナじゃTOPに対してうまいことパラメータが当てられないかもしれません(他の方法を考えた方が早そうな気がします)。

うーん。ループを使わなくても意外とシンプルに解決しちゃっいました。もうちょっと問題捻ったようが良かったかなぁと思ったり…。まぁ、後輩は十中八九クエリでやろうとするからちょっと見物かも。もし、自分がクエリを定義してやるとしたら2つめのクエリはTOPの条件を1にしてしまって、WHERE区に「DateAdd関数」でも使って5名抽出できるまでループにかけちゃうかなぁ。その場合、抽出されるたびにそのレコードを抽出済みにする必要がある気がします。

ここからVBAまで書いちゃうとまた長くなっちゃうので次回で。


2012/08/01

【Access】自作自演で課題をやってみる

何のことやら分からん題名になってしまったけど、以前の反省を踏まえ、後輩に出題する課題をとりあえず自分でやってみておこうということです。

では今回の課題はこちら。


ストーリー
あなたの勤める会社(従業員数200名)では毎週5名ずつ掃除当番を選出することになっている。現在は総務のAさんが社員名簿とにらめっこして極力連続して当たったりしないように苦心して選出しているが、いい加減面倒くさくなりAccessを勉強中のあなたに「何とかして!!」と泣きついてきた。

条件
  • 毎週、社員名簿から5名を選出する。1度選出されたら全社員が1巡するまで再度選出されることはない。また、再度選出される際でも前回の選出から最低3ヶ月は間をおかなければならない。
  • 毎月、月初めにその月の掃除当番表を作成し、出力する。


以上。さて、早速ですがこの話題のキーワードをまとめておきましょう。「ランダムな選出」「期間込みのフラグ」「月区切りのレポート」といったところでしょうか。軽く順番に考えてみます。

「ランダムな選出」
社員テーブル(仮)を作成し、そこからランダムに5名選出する。まぁ、ループとクエリかSQL文を組み合わせれば済みそうな気がします。

「期間込みのフラグ」
選出時に2つ条件があります。「1巡するまで再選出されないこと」と「再選出は前回の選出から3ヶ月以上経過していること」です。これを実現しようと思った場合、社員テーブル(仮)に日付を持ったフィールドを作ってやるのが無難かぁと思いました。もしくは別のフラグ管理用テーブルを作るかってとこかなぁ。

「月区切りのレポート」
最終的にこれが結果として出力されるものですね。まぁ月を指定するパラメータを持ったクエリでレポート作成するのが無難そうです。週の区切りはレポートのグルーピング機能で出来ないかしら?って感じです。ちなみに選出の実行はこれが出力されるタイミングでやっちゃえば良いでしょう(選出タイミングは条件にないので)。

自分的に何となく出来そうな気がしてきたのでテーブルを設計してみます。とりあえず、テーブルは社員テーブル1個で済ます方向でいこうと思います。社員テーブルのフィールドは以下の通り。


テーブル
論理名社員テーブル
物理名Employees
フィールド
論理名物理名
IDIdlong(increment)
社員番号EmployeeNostring
社員氏名EmployeeNamestring
選出日ChoseDatedate


選出日は日付/時刻型でしちゃってます。文字列型にすべきかとも思いましたがそんなに日付比較に精度を求める必要もないのでこのままでいきます。SQLServerのように単独で日付型、時間型を持っていればそちらを使いたいとこなんですが…。

長くなっちゃったので今回はここまで。次回はさくっとクエリかSQL文とVBAを書いちゃいます。


pagetop