Quantcast
Channel: ささいなことですが。
Viewing all 104 articles
Browse latest View live

OperationTypeInfoとSystem.__ComObject

$
0
0

Friendlyのマニアックな機能にOperationTypeInfoというものがあります。
僕以外に知っている人は、世界に10人いないでしょうねw
OperationTypeInfo - 株式会社Codeer (コーディア)

例えば、こんなクラスを操作するとして、

class Parent
{
    int data;
}

class Child : Parent
{
    //同名の変数が存在するint data;

    //オーバーロードされた関数が存在するpublicvoid Test(Form form)
    {
        //...処理
    }
    
    publicvoid Test(Control control)
    {
        //...処理
    }
}

普通に書くと

//子供しか取れないint data = child.data;
//例外発生//どっちのメソッドか分からない
child.Test(null);

で、これに対応するためにOperationTypeInfoがあります。操作時の型と引数の型を確定させるのです。Asyncと同じく、引数のどこかに混ぜてください。
あ、propertyとかfieldの場合も()つけて渡せばOKです。これもマニアックな話ですねw

//対象クラスを指定int data = child.data(new OperationTypeInfo(typeof(Parent).FullName);
//対象クラスと引数を指定
child.Test(new OperationTypeInfo(typeof(Parent).FullName, typeof(Form).FullName);

System.__ComObjectにも利用

対象プロセス内のオブジェクトを操作するときにCOMのオブジェクトが混ざっていることがあります。COMのオブジェクトは型が確定しているものもありますが、大部分はGetType()をするとSystem.__ComObjectというクラスが取得されます。Friendlyは対象プロセス内ではリフレクションを元に処理を実行するので、これでは本当に操作したい型が取れずにエラーになってしまいます。ココでもOperationTypeInfoが役に立ちます。

例えば、WebBrowserを使っているこんなFormがあって

using System.Windows.Forms;
namespace ComTarget
{
    publicpartialclass MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
            var web = new WebBrowser();
            web.Dock = DockStyle.Fill;
            Controls.Add(web);
        }
    }
}

Friendlyで操作すると

var app = new WindowsAppFriend(Process.Start(Target.Path));
var web = app.Type().System.Windows.Forms.Application.OpenForms[0].Controls[0];
web.Url = new Uri("http://www.codeer.co.jp/");
while ((WebBrowserReadyState)web.ReadyState != WebBrowserReadyState.Complete) 
{
    Thread.Sleep(10);
}
            
var iframeWindow = web.Document.Window;

//COMアクセス//IHTMLWindow2は操作できるが・・・
var iframeWindowCom = iframeWindow.DomWindow;
var sel = iframeWindowCom.document.selection;

//これは失敗する//IHTMLSelectionObjectはGetType()でSystem.__ComObjectを返すので操作できない
var type = sel.type;

こんな時でも、OperationTypeInfoを使えば、目的の操作を実行することができます。

var type = sel.type(new OperationTypeInfo(typeof(IHTMLSelectionObject).FullName));

まあ、DLLインジェクションを使った方が早いですけどね

書き方難しいのは、普通に実装してDLLインジェクションした方がいいですね。その関数呼び出すだけなら簡単な書き方なので。

[TestMethod]
publicvoid Test()
{
    var app = new WindowsAppFriend(Process.Start(Target.Path));
    WindowsAppExpander.LoadAssembly(app, GetType().Assembly);
    var web = app.Type().System.Windows.Forms.Application.OpenForms[0].Controls[0];
    web.Url = new Uri("http://www.codeer.co.jp/");
    while ((WebBrowserReadyState)web.ReadyState != WebBrowserReadyState.Complete)
    {
        Thread.Sleep(10);
    }

    //面倒な処理は相手プロセスに挿入して実行すればよい
    app.Type(GetType()).Func(web);
}

staticvoid Func(WebBrowser web)
{
    var iframeWindow = web.Document.Window;
    var iframeWindowCom = (IHTMLWindow2)iframeWindow.DomWindow;
    var sel = iframeWindowCom.document.selection;
    var type = sel.type;
}

次回へ続く

なんで今さらこんなマニアックな話を出したかというと、実は次回へのフリだったのです。
と言うわけで次回へ続く・・・


Friendly.PinInterface.1.3.1をリリースしました。

$
0
0

Friendly.PinInterface.1.2.01.3.1*1をリリースしました。
内容は、対象のオブジェクトがSystem.__ComObjectだった場合の対応です。
前回の話はこの前振りでした。

Friendly.PinInterfaceって何?

Friendlyの操作に型を与え、実装時にインテリセンスが使えるようにすることと、タイポを防ぐことが目的です。(最終は別プロセス操作になるのでどうしても型安全とまでは行かないですが)
vshtc/Friendly.PinInterface · GitHub
これはVSハッカソン倶楽部というコミュニティと共同で作成しています。

例えば、操作対象がSystem.Windows.Forms.Formだった場合以下のようなインターフェイスを定義するとその型で操作できます。

interface IForm
{
    string Text { get; set; }
    void Activate();
}
[TestMethod]
publicvoid Test()
{
    var app = new WindowsAppFriend(Process.Start(Target.Path));
    AppVar src = app.Type().System.Windows.Forms.Application.OpenForms[0];
    var form = src.Pin<IForm>();
    form.Activate();
}

インテリセンスが効きます。
f:id:ishikawa-tatsuya:20150723230052p:plain

でも、あんまり使っていませんでした。と言うのはテスト時は最終的にはアプリケーションドライバでラップするので、その内側で使うものに関してはdynamicでも別にいいじゃんってなっていました。自動で生成するなら別ですが、わざわざインターフェイス定義するの面倒ですしね。

実はCOMの操作にピッタリだったのです!

きょう再発見しましたw
COMって全部インターフェイスで提供されています。インターフェイスを自分で定義するのは面倒でも既にインターフェイスが提供されているなら話は別です。それともう一つPinInterfaceには特徴があって、戻り値がインタフェースの場合は、それも対象アプリ内のオブジェクトのプロキシとして返ってきます。前回のを書き直すと以下のようになります。

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Codeer.Friendly.Dynamic;
using Codeer.Friendly.Windows;
using System.Diagnostics;
using System.Windows.Forms;
using System.Threading;
using mshtml;
using Codeer.Friendly;
using VSHTC.Friendly.PinInterface;

namespace ComTest
{
    [TestClass]
    publicclass Sample
    {
        WindowsAppFriend app;

        [TestInitialize]
        publicvoid TestInitialize()
        {
            app = new WindowsAppFriend(Process.Start("ComTarget"));
        }

        [TestCleanup]
        publicvoid TestCleanup()
        {
            Process.GetProcessById(app.ProcessId).CloseMainWindow();
        }

        [TestMethod]
        publicvoid Test()
        {
            var web = app.Type().System.Windows.Forms.Application.OpenForms[0].Controls[0];
            web.Url = new Uri("http://www.codeer.co.jp/");
            while ((WebBrowserReadyState)web.ReadyState != WebBrowserReadyState.Complete)
            {
                Thread.Sleep(10);
            }

            var iframeWindow = web.Document.Window;
            AppVar com = iframeWindow.DomWindow;

            //IHTMLWindow2にピン止めする//実体は対象アプリ内にある
            var win = com.Pin<IHTMLWindow2>();
            //IHTMLDocument2で返ってくる//コピーではなく実体は対象アプリ内にある
            var doc = win.document.selection;
            //IHTMLSelectionObjectで返ってくる
            var sel = win.document.selection;
            var type = sel.type;
        }
    }
}

ちなみにこのコードは以下からダウンロードできるので、実際に動かしたり、インテリセンスを使ったりしてみてくださいね。
Ishikawa-Tatsuya/Sample_PinInterface_COM · GitHub

PinInterfaceなら、実行時に型が分かるから、OperationTypeInfoを書かなくてもいい

そう、それで今回の対応は何かというと、対象のオブジェクトがSystem.__ComObjectの場合は固定されている型の情報を使って内部的にOperationTypeInfoを作ってCOMオブジェクトを操作できるようにするというものでした。

//selはSystem.__ComObjectだけど、//IHTMLSelectionObjectだと分かっているのでライブラリの内部で//OperationTypeInfoを生成して操作できる。
var type = sel.type;

でもCOM操作なんていつやるの?

あのアプリを操作する時ですよ。アレです。アレ。その詳細は、7/25のめとべや大阪#31 - めとべや大阪 | Doorkeeperで話しますw(LTだけど)
気になる方は聞きに来てくださいね。

*1:すみません。微調整入って1.3.1になりました

VisualStudio2015を外部から操作する

$
0
0

前回からの流れですね。
インプロセスで動くCOMを使っているアプリであれば、Friendlyを使ってまるでアウトプロセスCOMのように簡単に外から操作できるという話でした。で、VisualStudioには外部仕様として公開されているCOMがあるのです。

あ、この内容はめとべや大阪#31 - めとべや大阪 | DoorkeeperでLTしてきました。

DTE2

VisualStudioは拡張機能を作ることができます。そして拡張機能はもちろんVisualStudioを操作できる必要があるので、そのためにDTE2というCOMオブジェクトが提供されているのです。これを使いましょう。

テストプロジェクト作成

テストプロジェクトである必要はないのですが、お手軽なので。作成したら、参照にEnvDTEとEnvDTE80を設定します。
f:id:ishikawa-tatsuya:20150726091948p:plain
で、NuGetからFriendkly.WindowsとFriendly.PinInterfaceを取得します。
f:id:ishikawa-tatsuya:20150726091951p:plain

コード

こんな感じです。Friendly.PinInterfaceのおかげでインテリセンスが効きます。それから、このコードはVS2013にもそのまま使えます。まだ2015持ってない人は途中のpathを2013用に書き換えてくださいね。

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Codeer.Friendly;
using Codeer.Friendly.Dynamic;
using Codeer.Friendly.Windows;
using EnvDTE;
using VSHTC.Friendly.PinInterface;
using EnvDTE80;
using System.IO;
using Codeer.Friendly.DotNetExecutor;

namespace VsDte
{
    [TestClass]
    publicclass Demo
    {
        string SolutionDir = Path.GetFullPath("TestDir");
        System.Diagnostics.Process vsProcess;

        [TestInitialize]
        publicvoid TestInitialize()
        {
            //ソリューション作成用のディレクトリを作成while (Directory.Exists(SolutionDir))
            {
                try
                {
                    Directory.Delete(SolutionDir, true);
                    break;
                }
                catch { }
                System.Threading.Thread.Sleep(10);
            }
            Directory.CreateDirectory(SolutionDir);

            //VS起動//パスを書き換えると2013も操作できるよ。
            var path = @"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe";
            vsProcess = System.Diagnostics.Process.Start(path);
            while (0 == vsProcess.MainWindowTitle.Length) 
            {
                System.Threading.Thread.Sleep(10);
                vsProcess = System.Diagnostics.Process.GetProcessById(vsProcess.Id);
            }
        }

        [TestCleanup]
        publicvoid TestCleanup() 
        {
            vsProcess.Kill();
        }

        static Type DTEType { get { returntypeof(_DTE); } }

        [TestMethod]
        publicvoid Test()
        {
            //アタッチ
            WindowsAppFriend app = new WindowsAppFriend(vsProcess);

            //DLLインジェクション
            WindowsAppExpander.LoadAssembly(app, GetType().Assembly);

            //Microsoft.VisualStudio.Shell.Package.GetGlobalServiceの呼び出し
            var dteType = app.Type(GetType()).DTEType;
            AppVar obj = app.Type().Microsoft.VisualStudio.Shell.Package.GetGlobalService(dteType);

            //注目!//インターフェイスでプロキシが作成できる!
            var dte = obj.Pin<DTE2>();
            var solution = dte.Solution;

            //ソリューション作成
            solution.Create(SolutionDir, "Test.sln");
            string solutionPath = Path.Combine(SolutionDir, "Test.sln");
            solution.SaveAs(solutionPath);

            //プロジェクト追加
            solution.AddFromTemplate(
                @"C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\ProjectTemplates\CSharp\Windows\1041\WPFApplication\csWPFApplication.vstemplate",
                Path.Combine(SolutionDir, "WPF"), "WPF", true);

            //保存
            solution.SaveAs(solutionPath);

            //閉じる
            solution.Close();

            //再度開く
            solution.Open(solutionPath);

            //クリーン→ビルド
            solution.SolutionBuild.Clean(true);
            solution.SolutionBuild.Build(true);

            //デバッグ
            solution.SolutionBuild.Debug();

            //デバッグ対象プロセスを操作
            var debProcess = System.Diagnostics.Process.GetProcessById(dte.Debugger.DebuggedProcesses.Item(1).ProcessID);
            using (var debApp = new WindowsAppFriend(debProcess)) 
            {
                debApp.Type().System.Windows.Application.Current.MainWindow.Close(new Async());
            }

            //編集モードに戻るまで待つwhile (dte.Debugger.CurrentMode != dbgDebugMode.dbgDesignMode) 
            {
                System.Threading.Thread.Sleep(10);
            }
        }
    }
}

このソースコードはこちらからダウンロードできます。

Ishikawa-Tatsuya/Sample_VS_DTE · GitHub

COM操作アイデア募集中

COMとFriendly.PinInterfaceの相性の良さは異常w
と言うことで、他に「このアプリも内部にCOM持ってるから簡単に操作できるよ」みたいなアイデアがある人はブログとか書いてくれたら嬉しいなーw。

Friedlyハンズオン開催 - 東京9/19(土) - 大阪9/24(木)

$
0
0

Friendlyのハンズオンを開催します。
しかも、東京と大阪両方で!

東京会場はSHIFT様(9/19)madoguchi100.connpass.com

大阪会場はベリサーブ様(9/24)vshtc.doorkeeper.jp

どちらもテストのスペシャリストの会社ですね。

Friendlyって難しいの?

Friendlyって覚えることは本当に少ないのです。
でも、独学では意外と理解できないっていうケースも聞きます。
(いや、もちろんこのブログとか、公式ページで独学で体得することも可能ですよ)

大げさに言うと、普段やってるプログラムとはパラダイムが違うのです。
マルチプロセスプログラミング。
COMのアウトプロセスサーバーに近い感じです。
でも、もっと簡単に透過的に操作できるので、逆に混乱してしまうのです。

ハンズオンに来ると、Friendlyのコツを体得できます!

Friendlyのコツを掴むには、実際にコードを書いて「なるほど、そうだったのか!」「あれ、それってそんなに簡単にできたの!?」って体験してもらうのが一番はやいのです。

アハ体験

まさに、アハ体験。「そんなことできるの!?」「あ、それってこれと繋がってるの!?」みたいな驚き連続!頭の体操にもどうぞ。

是非ご参加を

ということで、お近くの方は是非ご参加ください。
実は東京はSQiPの次の日だったりします。(僕はSQiPでも登壇しますよー)
ちょっと遠い方で、SQiP来たかたはこちらもどうぞ!

Friendly、ラムダ送れるってよ

$
0
0

いや、ちょっとタイトルは正確さに欠けますが、ご容赦を。

今日は、驚きの発見がありました。
FriendlyユーザーのITO(仮名)さんと話していると

ITO「こんな感じでテスト書いたらいいと思うんですよね。」
石「でもこのコード、ラムダ使ってますよ。プロセス違うからラムダなんて送れるわけないじゃん。」
ITO「え?でも送れましたよ?」
石「なん…だと…!?」

こんなコードが書けたのです。

上の話の時のコードとは違うけど、つまりは以下のようなコードが書けたのです。

publicvoidラムダが送れた()
{
    WindowsAppExpander.LoadAssembly(_app, GetType().Assembly);

    //メインウィドウのタイトルを表示
    Action a1 = () => MessageBox.Show(Process.GetCurrentProcess().MainWindowTitle);

    //Actionを転送
    var a2 = _app.Copy(a1);

    //対象プロセス内で実行
    a2.Invoke();
}

???なんで?
作者は想定外ですw

Friendlyで送れるのは以下のもの
シリアライズ可能なオブジェクト
②AppVar
③DynamicAppVar
④IAppVarOwner

②③④ではありえないので、
Actionは①ってことになります。

つまりDelegateシリアライズ可能なこともある

ということですね。
知らんかった。
「可能なこともある」
ってボンヤリしてますね。
以下ちょっと実験しましたけど、可能/不可能の境界線はまだ調べられてないです。

publicvoidシリアライズ可能()
{
    var formatter = new BinaryFormatter();
    using (var stream = new MemoryStream())
    {
        //これはシリアライズできた
        Action a1 = () => MessageBox.Show("");
        formatter.Serialize(stream, a1);

        //これは無理string text = "";
        Action a2 = () => MessageBox.Show(text);
        formatter.Serialize(stream, a2);
    }
}

どこまでいけるんだろ?
IL調べたらわかるのかな?
@Posauneさんとか、この辺りブログ書いてくれないかなー。

何か面白い機能追加できそう!

とにかく可能性が広がりました!
なんか面白機能をFriendlyの基本部分に追加できそうです。
ITOさん、センキューで~す(チャラい

そしてこんな面白いことができるFriendlyのハンズオンがあります。

宣伝w。残席まだあります。ご参加お待ちしております!

東京会場SHIFT様(9/19)madoguchi100.connpass.com

大阪会場ベリサーブ様(9/24)vshtc.doorkeeper.jp

Friendly.XamControls 1.0.0 をリリースしました。

$
0
0

Infragistics様のコントロールを外部プロセスから操作するためのライブラリ、
Friendly.XamlControlsを正式版にしてリリースしました。www.nuget.org

αが外れて正式版になりました

いや、これほんと、どこまで作ったら正式版にするとか難しくて。全部テストコード書いてるしいいかなーと。正式版の方が使ってくれる人も増えるでしょうし。
正式版になったとは言え、追加にはフレキシブルに対応しますので、「こんな機能欲しい!」とかはお気軽に言ってくださいね。

ラインナップ

・XamCalendarDriver
・XamComboEditorDriver
・XamContentPaneDriver
・XamDataGridDriver
・XamDataGridCellDriver
・XamDataGridCheckCellDriver
・XamDataGridComboCellDriver
・XamDataGridTextCellDriver
・XamDataTreeDriver
・XamDataTreeCheckNodeDriver
・XamDataTreeNodeDriver
・XamDataTreeTextNodeDriver
・XamDockManagerDriver
・XamGridDriver
・XamGridCellDriver
・XamGridCheckCellDriver
・XamGridComboCellDriver
・XamGridTextCellDriver
・XamOutlookBarDriver
・XamOutlookBarGroupDriver
・XamRibbonDriver
・XamApplicationMenu2010Driver
・XamApplicationMenu2010ItemDriver
・XamApplicationMenuDriver
・XamToolMenuItemDriver
・XamTabControlDriver
・XamTabItemDriver

結構頑張って実装しましたよー。とは言え、Infragisticsさんのコントロールは種類豊富なんで、コンプリートしているわけではないです。僕が使う範囲だけです。
使い方は一例を書くと

var main = WindowControl.FromZTop(_app);

//バインディングからグリッドを取得
var gridSrc = main.VisualTree().ByBinding("DisplayData");
var grid = new XamDataGridDriver(gridSrc);

//コンボを編集
var cellCombo = _grid.GetCell(99, 1).AsCombo();
cellCombo.EmulateEdit(1);

//チェックを編集
var cellCheck = _grid.GetCell(99, 2).AsCheck();
cellCheck.EmulateEdit(false);

//テキストとして編集
var cellText = grid.GetCell(99, 3).AsText();
cellText.EmulateEdit("2015年09月06日");

f:id:ishikawa-tatsuya:20150906213023p:plain
簡単でしょ?

他のFriendlyのライブラリと組み合わせて使う必要があります。

単品で使うものではなく、Friendlyの他のライブラリと組み合わせて使うものです。Friendlyの基本を学んでおかないと使えないのです。Frienldyはコツさえつかめば簡単なんで問題ないです。
え?それもできるだけ少ない時間で習得したい?そんな都合のいいもの・・・
あったw
はい、東京と大阪でハンズオンやります。特に東京の方は半日でFriendlyの全てをマスターできるフルコースです。これはお得!残席まだあります。このチャンスに是非!

東京会場SHIFT様(9/19)madoguchi100.connpass.com

大阪会場ベリサーブ様(9/24)vshtc.doorkeeper.jp

コードにはコメントないですw

えーと、Infragisticsさんの開発部隊やユーザーは海外におられるということで、その辺からプルリク欲しいなーってことで、コメントなしでGitHubに入れてます。変な英語のコメント書いたら混乱させるしね。それに、海外のライブラリとかコメント内の多いしね・・・(手を抜いたわけではない・・・、いや日英両方のコメント書くのは疲れたのは否定しません)
てことでプルリク待ってますよー。誰となく。

Friendly.FarPoint 1.0.0、Friendly.C1.Win 1.0.0 をリリースしました!

$
0
0

連続リリースです。
GrapeCity様のFpSpread、C1FlexGridを別プロセスから操作するためのライブラリであるFriendly.FarPoint 1.0.0、Friendly.C1.Win 1.0.0 をリリースしました!www.nuget.org
www.nuget.org

Friendly.FarPointはもちろんFpSpreadの操作だけですし、Friendly.C1.WinもまだC1FlexGridの操作だけですね。C1に関しては、もっと便利なUIクラスが多数定義されていますが、いかんせん僕がまだ使ったことないんで、操作クラスが上手く実装できないんですよねー。(誰か手伝って

ご要望受付中

こちらもご要望うけつけますよ。まだまだ使いやすく進化させる予定です。

操作コード

FpSpreadの操作です。

dynamic main = app.Type(typeof(Application)).OpenForms[0];  
var spread = new FpSpreadDriver(main._grid);

//sheets.int count = spread.Sheets.Count;
int activeSheetIndex = spread.ActiveSheetIndex;
spread.EmulateChangeActiveSheet(1);
var sheet = spread.Sheets[1];
sheet = spread.ActiveSheet;

//cell.
var cell = sheet.Cells[0.3];
cell = sheet.ActiveCell;
string text = cell.Text;
int rowIndex = cell.Row.Index;
int rowIndex2 = cell.Row.Index2;
int rowIndex = cell.Column.Index;
int rowIndex2 = cell.Column.Index2;
sheet.EmulateChangeActiveCell(3, 5, true);
sheet.EmulateAddSelection(1, 2, 3, 5);
sheet.EmulateRemoveSelection(1, 2, 3, 5);
sheet.EmulateClearSelection();

//edit.
sheet.EmulateChangeActiveCell(0, 1, true);
spread.EmualteEditText("abc");

sheet.EmulateChangeActiveCell(1, 1, true);
spread.EmualteEditValue(2);

sheet.EmulateChangeActiveCell(0, 3, true);
spread.EmualteEditSelectedIndex(1);

sheet.EmulateChangeActiveCell(0, 2, true);
spread.EmualteEditCheckState(CheckState.Checked);

f:id:ishikawa-tatsuya:20150906220858p:plain
C1FlexGridの操作です。

dynamic main = app.Type(typeof(Application)).OpenForms[0];  
var grid = new C1FlexGridDriver(main._grid);

//select cell.
grid.EmulateSelect(1, 2);
grid.EmulateSelect(1, 2, 5, 3);

//get selection.int row = grid.Row;
int col = grid.Col;
int rowSel = grid.RowSel;
int colSel = grid.ColSel;

//add row selection.
grid.EmulateAddSelectedRow(2);
grid.EmulateAddSelectedRow(3);

//get row selection.int[] rows = grid.SelectedRows;

//get cell text.
grid.GetCellText(1, 2);
grid.GetCellTexts(0, 0, 2, 4);

//get cell object.//it can use for serializasble objects.
grid.GetCellObjects(0, 0, 2, 4);
grid.GetCellObject(1, 2);

//edit.
grid.EmulateSelect(1, 1);
grid.EmulateEditText("1-1");
grid.EmulateSelect(1, 2);
grid.EmulateEditCombo(2);
grid.EmulateSelect(1, 3);
grid.EmulateEditCheck(false);

f:id:ishikawa-tatsuya:20150906220908p:plain
どちらも、非常に直感的ですよねw

他のFriendlyのライブラリと組み合わせて使う必要があります。

単品で使うものではなく、Friendlyの他のライブラリと組み合わせて使うものです。Friendlyの基本を学んでおかないと使えないのです。Frienldyはコツさえつかめば簡単なんで問題ないです。
え?それもできるだけ少ない時間で習得したい?そんな都合のいいもの・・・
あったw(すみません二回連続)
はい、東京と大阪でハンズオンやります。特に東京の方は半日でFriendlyの全てをマスターできるフルコースです。これはお得!残席まだあります。このチャンスに是非!

東京会場SHIFT様(9/19)madoguchi100.connpass.com

大阪会場ベリサーブ様(9/24)vshtc.doorkeeper.jp

Friendlyハンズオンを東京で開催しました!

$
0
0

9/19(土)にFrienlyハンズオンを東京で開催しました。madoguchi100.connpass.com

シルバーウィーク初日にも拘わらず、大勢の方のご参加ありがとうございました!
f:id:ishikawa-tatsuya:20150923214903j:plain

会場提供はSHIFT様。
ありがとうございました!
f:id:ishikawa-tatsuya:20150923214739j:plainf:id:ishikawa-tatsuya:20150923214745j:plain

オシャレですねー。東京タワー見えてるし。
なんと、ドラマの「リスクの神様」の撮影にも使われたそうです。

講師陣

  • 株式会社OSK 小井土亨様
  • 株式会社SHIFT 玉川紘子様
  • 株式会社SHIFT 太田健一郎様
  • 株式会社SHIFT 高山 洪銘様

講師陣がこれまた豪華でしたね。
実は私は、この人数を対象にしたハンズオンって初めてでして。
そこは、他の講師の皆様に助けていただきました。
(ハイペースなときに、スローダウンしてもらったりとか)
ありがとうございました!

ハンズオン項目

今回はですね、目標は以下の7項目を全てやってみようと思ってました。
これだけ、覚えれば少なくともWinFormsのアプリのテストは作れるはず!
Win32WPFは他にも若干覚えることありますが)

  1. テストでよく使う.NetのクラスとVSTest
  2. Friendlyの基本
  3. Friendlyの旧インターフェイスと上位ライブラリ
  4. WinFormsのコントロールドライバ
  5. WindowControlと画面遷移
  6. .Netで使うネイティブウィンドウ
  7. アプリケーションドライバを作ろう

しかし・・・

しかし、やっぱり無理でしたねw人間こんなに詰め込めません。しかも、Friendlyは覚えることは少ないのですが頭の使い方が通常のプログラミングと異なるので慣れるまでは非常に疲れるのです。

でも皆さん、私のスパルタなペースに食らいついてきてくれました!そして、4の「WinFormsのコントロールドライバ」が終わったころには参加者も講師陣もHP、MP共に尽き果てて朦朧としていましたねw

まあ、反省点は色々ありますねー。ペース配分とか、資料の配布方法とか。(すみませんorz

とは言え、プロセス操作、Friendlyの基本、コントロールドライバの考え方は身に着けてもらえたかなーと。特にFriendlyの基本が重要です。ここさえ押さえれば通常のプログラムでできることが全て別プロセスから利用可能になりますからね。後は応用なのです。

続きは公式ページと私のブログで何とか頑張ってください。(重ね重ねすみませんorz
問い合わせも受け付けてますので、いつでも質問してくださいねー。

あ、もちろんもっと深く知りたいと思ったら、自動化支援への申し込みもお願いしますw


Friendly(基本編)ハンズオン大阪、大盛況でした!

$
0
0

そして、9/24には大阪でもハンズオンを開催させていただきました。vshtc.doorkeeper.jp
平日にもかかわらず、大勢のご参加本当にありがとうございました!
f:id:ishikawa-tatsuya:20150929231434j:plain

場所は、SCSK様です。SCSKグループで品質保証を担ってきたベリサーブ様の協賛を得ての開催となりました。
ありがとうございました!www.veriserve.co.jp
会場の前はこんな感じ。
f:id:ishikawa-tatsuya:20150929231449j:plain
更に主催はVSハッカソン倶楽部と、もはや色んな人におんぶにだっこ状態ですねw

講師陣

支えてくれた講師陣は、東京に負けず劣らず超豪華!
ホントありがとうございます!
もう安心感がすごい! 参加者のサポートやトラブル対応は丸投げで、ハイペースでやりきりましたw(おい・・・

やっぱりこれくらいの時間がちょうどいいよねー

はい。先日の東京ハンズオン5時間はハードすぎましたw
皆さんバテバテでしたものね・・・。
で、企画時は大阪1時間30分は物足りないかなーと思いましたが、ちょうどよかったですね。内容も、前回の経験を活かして重要なポイントの演習時に体力が残るように調整しました。

Friendlyの基本を身に着けてもらえました。

そう、こちらは最初から基本のみの予定だったのです。
実はWinFormsのアプリのテスト自動化を実施しようと思うと以下の知識が必要だったりします。(ネイティブ、WPFの場合は、また別の情報が必要です)
今回は2までやりました。

  1. プロセス操作の基本
  2. Friendlyの基本1-内部API
  3. Friendlyの基本2-DLLインジェクション
  4. Friendlyの旧インターフェイス
  5. WinFormsのコントロールドライバ
  6. WindowControlと画面遷移
  7. .Netで使うネイティブウィンドウ
  8. アプリケーションドライバの作り方

ご好評につき、次回予定してます。

実はひそかに連載をたくらんでいましたw
で、最後に恐る恐る挙手アンケートとったら皆さん次回にも期待してくれてそうだったので、調子に乗ってやっちゃいます。
現在、会場を調整中です。決まったら公開しますので、次回もよろしくです!

10/30に第二回Friendlyハンズオンを開催します。

$
0
0

最近、告知ばっかりですけどw
(そのうち、ちゃんとしたのも書きますよ・・・vshtc.doorkeeper.jp

第一回を逃した人も是非

そうなんです。
連載物なんですよね。
だから、基本的には一回目の知識が必要です。
でも、前回分をこっちにアップしているので、
これ見ると参加可能です。www.codeer.co.jp

わからないところあったら、質問してください。
希望があれば、Skypeかスタバで補修しますよw

今回は憧れのDLLインジェクションです

Win32時代からプログラムしている人なら一回はやってみようと思うテクニックですね。
一般的には、やたら面倒なことしてネイティブのdllをインジェクションできるくらいですけど、Friendlyなら、一行で.Netのアセンブリをインジェクションできます。
これは、去年のグローバルサミットの MVP Showcase で2位を取った時に使ったデモですが、これもDLLインジェクションを使って実現しています。youtu.be

テストなのにDLLインジェクションなんて何に使うの?

いえいえ、真に価値あるテスト自動化を実現するためには手段は選びません!これくらいの強力な操作手法が必要になってくるのですよw
・アトミックな操作(安定操作のため)
・高速な操作
・より複雑な操作
・ネイティブDLL関数操作
・モックの動的な挿入
・etc...

是非参加して、この異次元の操作手法を身に着けてください!

Friendly.WPFStandardControls 1.7.0をリリースしました。

$
0
0

www.nuget.org

機能追加しました。 何をってこれです。 blog.okazuki.jp

やっぱり、コントロールドライバの対応があった方が安心感ありますよね。 ちなみにご意見いただいた、かずきさんは開発メンバーに加わっていただきました(無理やりw

それから、WPFのコントロールの取り方ですが、 現在ライブラリで対応しているのは、こんな感じです。

基本的な取得方法

ishikawa-tatsuya.hatenablog.com

そうなんです。 AppVarとIAppVarOwnerに対して拡張メソッドでLogicalTreeとVisualTreeをたどれるようにしています。 解説ありがとうございます。 blog.okazuki.jp

ボタンに特化した検索方法追加

ishikawa-tatsuya.hatenablog.com

他にも、こんな取り方便利ってあったら足すかも。

リクエスト待ってます。 とは言え、Friendlyの生の呼び出と、DLLインジェクションとか使ったら普通のプログラムで取得できる範囲は全部とれるので、色々お試しください。

Friendly.UWP_α 0.0.1 をリリースしました!

$
0
0

Friendly.UWP_α 0.0.1 をリリースしました!
なんと、種も仕掛けもないUWPアプリのAPIを外部から呼べるのです!
(制限はあるw
www.nuget.org

まずは体験してみてください。
ここからサンプルをダウンロード
github.com

ダウンロードはこのボタンです。
f:id:ishikawa-tatsuya:20160103132353p:plain

解凍前にブロックを解除してくださいねー。
f:id:ishikawa-tatsuya:20160103132606p:plain

それで、Sample_UWP_1-master/Project/Sample/Sample.slnを開きます。
実行すると・・・
f:id:ishikawa-tatsuya:20160103134458p:plain

解説

コードは次のようになっています。

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Friendly.UWP;
using Codeer.Friendly.Dynamic;
using EnvDTE80;
using System.IO;
using VSHTC.Friendly.PinInterface;

namespace Sample
{
    [TestClass]
    publicclass Demo
    {
        [TestMethod]
        publicvoid Test()
        {
            using (var app = new UWPAppFriend(new ByVisualStudio(Path.GetFullPath("../../../TargetApp/TargetApp.sln"))
            {
                //Visual Studio 2015のパス 違ったら変えてね。
                VisualStudioPath = @"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe",

                //ソリューションの構成//Debugのx86を選択
                ChangeVisualStudioSetting = (vs, dteSrc) =>
                {
                    var dte = dteSrc.Pin<DTE2>();
                    dte.Solution.SolutionBuild.SolutionConfigurations.Item(3).Activate();
                }
            }))
            {
                var current = app.Type("Windows.UI.Xaml.Window").Current;
                var mainPage = current.Content.Content;

                //定義したメソッドを呼びだしstring ret = mainPage.Func(10);
                Assert.AreEqual("10", ret);

                //背景色変更
                var color = app.Type("Windows.UI.Colors").Blue;
                var brush = app.Type("Windows.UI.Xaml.Media.SolidColorBrush")(color);
                mainPage.Content.Background = brush;
            }
        }
    }
}

α0.01では対象のUWPをVisualStudioを使って起動するようにしています。対象のプロジェクトを開いたVisualStudioがあれば、それを使います。なければ、VisualStudioPathでパスを設定したものを起動して対象のソリューションを開きます。ChangeVisualStudioSettingは何かというと、ソリューションを開いた後のVisualStudioの設定を変えています。デバッグモードで実行する必要があるので、ソリューションの構成を変えています。これも開いた状態で期待のソリューション構成になっているなら、設定しなくてもOKです。
残りのコードは普通の(?)Friendlyのコードです。内部メソッドを呼び出せています。
あ、ちなみにChangeVisualStudioSettingは、Friendly.WindowsとFrinedly.PinInterfaceを使ってVisualStudioを外部から操作します。こちらを参照お願いします。
ishikawa-tatsuya.hatenablog.com

内部解説

Friendyはご存知の通り、操作したいプロセスの内側に、言うことを聞いてくれるモジュールを送り込み、それに指令を送って処理を実行させます。通常のデスクトップアプリ版はDLLインジェクションとCOMによるCLR操作でそれを実現していますが、UWPにはこの方法が効かないのです。
で、どうしたかと言うと、VisualStudioのイミディエイトの機能を使っています。App.InitializeComponentという関数でブレイクさせて、そこでイミディエイトでアセンブリのロードと指令を受信するモジュールの起動を実行しているのです。
あ、App.InitializeComponentという関数を消したり名前を変えている人はByVisualStudioのInjectionBreakPointというプロパティーでブレイクする関数を変更できます。

インジェクションしなくてもいいですよね?

今はインジェクションしてます。結構無理やりですw
でもここ以外には無理はないのですよね。
通信とリフレクションだけなので。
f:id:ishikawa-tatsuya:20160103141708p:plain

だから、そこは無理せずに対象のアプリにロードしておいてもらうのもいいかなーって思っています。
f:id:ishikawa-tatsuya:20160103141829p:plain

こんな構成ならいいでしょ?
f:id:ishikawa-tatsuya:20160103141856p:plain

次のバージョンからは

対象アプリに最初からロードしてもらう手法も詰めていきます。
それから、基本的なコントロールの操作のラッパークラスもこのライブラリに定義していこうかなー。
乞うご期待!

あ、それからOSSですので一緒に作ってくれる人も募集中です!

ViewModelをシンプルに書きたい! - ⑦MVVMを考える編 - 本編 -

$
0
0

ViewModelとはViewをモデル化したものである

ある人に教えてもらったのですが、その瞬間に色々なことがスッキリしました。ViewModelの役割って様々な主張があるけど、骨子はこれですね。

モデル化とは・・・

検索したら++C++がかかりました。さすが岩永さんは色々書いてますねー。
http://ufcpp.net/study/dsl/mdd.html

  • モデル化とは、 現実の問題から、問題解決に必要な部分だけを抜き出して簡単化・抽象化することです。
  • 「よいモデル化」というのは、 問題の要件を必要十分に、過不足なく表せるモデルを作ることです。 情報は多すぎても少なすぎてもダメ。

このViewをモデル化してみます。この画像は出来上がったもののキャプチャですが、こんな画面仕様書が来たと思ってください。コンボボックスの選択を変えたら、そのミュージシャンのアルバム一覧が表示されるというものです。
(※リリース日は年はあってますが、月日はわからなかったので嘘ものです)
f:id:ishikawa-tatsuya:20160612140052p:plain
この画面は具体的ですね。ComboBoxとかDataGridViewとか。
これを心の目で見るのですw
f:id:ishikawa-tatsuya:20160613090909p:plain
見えましたか?GUIの詳細はフィルタされてます。それから固定値のTextBlockもフィルタしました。そしたら、これをコードに落としましょう。

publicclass MainWindowVM
{
    //ミュージシャンpublic ObservableCollection<Musician> Musicians { get; } = new ObservableCollection<Musician>();

    //現在選択されているミュージシャンpublic ReactiveProperty<Musician> SelectedMusician { get; } = new ReactiveProperty<Musician>();

    //アルバムpublic ObservableCollection<Album> Albums { get; } = new ObservableCollection<Album>();
}

次に、これを操作します。操作は・・・、WinFomrsでコードビハインドに実装しているイメージでいいんじゃないですかね。操作対象が具体的なGUIコントロールではなく抽象化されたオブジェクトを操作するように変わっただけです。

using Reactive.Bindings;
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using System.Linq;
using System;

namespace WpfApp
{
    publicclass MainWindowVM
    {
        MusicInfo _info = new MusicInfo();

        //ミュージシャンpublic ObservableCollection<Musician> Musicians { get; } = new ObservableCollection<Musician>();

        //現在選択されているミュージシャンpublic ReactiveProperty<Musician> SelectedMusician { get; } = new ReactiveProperty<Musician>();

        //アルバムpublic ObservableCollection<Album> Albums { get; } = new ObservableCollection<Album>();

        public MainWindowVM()
        {
            //初期化
            _info.GetMusicians().ToList().ForEach(e => Musicians.Add(e));

            //選択ミュージシャン変更イベント
            SelectedMusician.Subscribe(_AppDomain => MusicianChanged());

            //最初は0番目を選択
            SelectedMusician.Value = Musicians[0];
        }

        publicvoid MusicianChanged()
        {
            //変わったらそのミュージシャンのアルバム一覧を取得
            Albums.Clear();
            _info.GetAlbums(SelectedMusician.Value.Id).ToList().ForEach(e => Albums.Add(e));
        }
    }
}

*1
過不足なく抽象化されたViewModelのオブジェクトを操作するのであれば、ハマりポイントの多いGUIコントロールと違って素直に実装できます。しかもテストコードで確認しながら実装できるのです。(理論的には。みんなやってるんですよねw)
ポイントは、あくまでViewをモデル化したものなので、View起因で設計されるものです。決してモデル起因ではありません。

ViewMdoelは薄く

抽象化かかってるとはいえ、あくまでViewをモデル化しただけです。そこにロジックをモリモリ載せるのではなくモデル層でしっかり設計、実装したロジックを呼び出すだけにしましょう。でも、Viewとは違って抽象化かかってるから、コントローラを挟まず直接モデルを呼び出してもOKという判断なのでしょう。

View

Viewは極力Xamlで書くのがいいようですね。とは言え、Viewのお仕事はGUIコントロールの操作なのでそこから外れなければコードビハインドもOKではないでしょうか。

Model

Modelは、まあMVCでやる場合とあんまり変わらない印象です。前回でも書きましたが、MVナントカ関係なくしっかり設計しましょう。

MVVM怖くない

こう考えるとMVVMは怖くなくなるのではないでしょうか。

  • Modelはあんまり変わらない
  • ViewModelはコードビハインドに書くより簡単
  • Viewは・・・、Xamlが怖いかw

チームで導入する場合、デザイナとプログラマで仕事を分けるとかありますけど、現実的には先行でWPFの知識を持った人とその他のプログラマで分けたらいいかもしれないですね。そしたら他の人が慣れてくるまで、その人にひたすらXaml書いてもらえばよいのではないでしょうかw

短期集中連載終わり

2日間で7本は自分の中では記録ですね。また何か便利な書き方学んだり、書きたいことが出てきたらアウトプットしていきます。

連載で使ったMarkup拡張のコードはこちらにおいてます。

github.com

おまけ

サンプルのViewとModelのコードです。

<Window x:Class="WpfApp.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:l="clr-namespace:WpfApp"xmlns:c="clr-namespace:VVMConnection;assembly=VVMConnection"Title="MainWindow"Height="350"Width="525"><Window.DataContext><l:MainWindowVM/></Window.DataContext><Grid><Grid.RowDefinitions><RowDefinition Height="auto"/><RowDefinition Height="auto"/><RowDefinition Height="*"/></Grid.RowDefinitions><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="auto"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions><TextBlock Text="ミュージシャン"Grid.Column="0"Margin="10,0,0,0"/><ComboBox ItemsSource="{Binding Musicians}"SelectedItem="{Binding SelectedMusician.Value}"Margin="10,0,10,0"Grid.Column="1"DisplayMemberPath="Name"/></Grid><TextBlock Text="アルバム一覧"Margin="10,30,0,0"Grid.Row="1"/><DataGrid ItemsSource="{Binding Albums}"AutoGenerateColumns="False"CanUserAddRows="False"Grid.Row="2"><DataGrid.Columns><DataGridTextColumn Header="リリース日"Binding="{Binding ReleaseDate, StringFormat={}{0:yyyy年MM月dd日}}"Width="150"IsReadOnly="True"/><DataGridTextColumn Header="アルバムタイトル"Binding="{Binding Title}"Width="*"IsReadOnly="True"/></DataGrid.Columns></DataGrid></Grid></Window>
using System;

namespace WpfApp
{
    publicclass Musician
    {
        publicint Id { get; set; }
        publicstring Name { get; set; }
    }

    publicclass Album
    {
        publicstring Title { get; set; }
        public DateTime ReleaseDate { get; set; }
    }

    publicclass MusicInfo
    {
        public Musician[] GetMusicians()
        {
            returnnew Musician[]
            {
                new Musician() { Name = "Kenny Burrell", Id = 0},
                new Musician() { Name = "Aerosmith", Id = 1},
                new Musician() { Name = "BLANKEY JET CITY", Id = 2},
            };
        }

        public Album[] GetAlbums(int musicianId)
        {
            switch (musicianId)
            {
                case0:
                    returnnew Album[]
                    {
                        new Album() { Title = "Introducing", ReleaseDate = new DateTime(1956, 1, 1)},
                        new Album() { Title = "Kenny Burrell",  ReleaseDate = new DateTime(1957, 2, 2)},
                        new Album() { Title = "Blue Lights Vol.1", ReleaseDate =  new DateTime(1958, 3, 3)}
                    };
                case1:
                    returnnew Album[]
                    {
                        new Album() { Title = "Aerosmit", ReleaseDate = new DateTime(1973, 4, 4)},
                        new Album() { Title = "Get Your Wings",  ReleaseDate = new DateTime(1974, 5, 5)},
                        new Album() { Title = "Toys in the Attic", ReleaseDate =  new DateTime(1975, 6, 6)}
                    };
                case2:
                    returnnew Album[]
                    {
                        new Album() { Title = "Red Guitar And The Truth", ReleaseDate = new DateTime(1991, 4, 12)},
                        new Album() { Title = "Bang!",  ReleaseDate = new DateTime(1992, 1, 22)},
                        new Album() { Title = "C.B.Jim", ReleaseDate =  new DateTime(1993, 2, 24)}
                    };
            }
            returnnew Album[0];
        }
    }
}

追記

@yone64さんに、「こう書いたらもっとReactiveだよ。」って教えていただきました。ありがとうございます!

using Reactive.Bindings;
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using System.Linq;

namespace WpfApp
{
    publicclass MainWindowVM
    {
        MusicInfo _info = new MusicInfo();

        //ミュージシャンpublic ObservableCollection<Musician> Musicians { get; } = new ObservableCollection<Musician>();

        //現在選択されているミュージシャンpublic ReactiveProperty<Musician> SelectedMusician { get; } = new ReactiveProperty<Musician>();

        //アルバムpublic ReactiveProperty<ObservableCollection<Album>> Albums { get; }

        public MainWindowVM()
        {
            //初期化
            _info.GetMusicians().ToList().ForEach(e => Musicians.Add(e));

            //最初は0番目を選択
            SelectedMusician.Value = Musicians[0];

            //選択ミュージシャンとAlbumsをReactiveな感じでつなぐ
            Albums = SelectedMusician.Select(e => new ObservableCollection<Album>(_info.GetAlbums(SelectedMusician.Value.Id))).ToReactiveProperty();
        }
    }
}

*1:こっちも@yone64さんにReactiveなイベントのつなぎ方を教えていただいたのでちょっと修正

LambdaでSQLを書きたい - ①LambdicSql始めました

$
0
0

文字列でSQL書くのが苦手なのです。

SQL弱者としてはインテリセンスなしでSQL書くの辛いんです。(Friendyでdynamic使わせてるのにって怒られそうですが)コード中に文字列演算がバンバン入るのがイマイチですしね。何とかしたいよなー。

LambdicSql始めました。

EntityFrameworkみたいにラムダでSQL書ければいいじゃん。ってことで、そんなライブラリ作り始めました。Lamdicなんて単語はないですけどねw。まあ雰囲気伝わるでしょう。.Net3.5から使えます。(EF使えないプロジェクトとかレガシー資産にこそ多い)
www.nuget.org
github.com

ラムダでSQLが書けます。

publicclass Staff
{
    publicint id { get; set; }
    publicstring name { get; set; }
}

publicclass Remuneration
{
    publicint id { get; set; }
    publicint staff_id { get; set; }
    public DateTime payment_date { get; set; }
    publicdecimal money { get; set; }
}

publicclass DB
{
    public Staff tbl_staff { get; set; }
    public Remuneration tbl_remuneration { get; set; }
}

publicclass SelectedData
{
    publicstring Name { get; set; }
    public DateTime PaymentDate { get; set; }
    publicdecimal Money { get; set; }
}
//これ重要using Dapper;
using LambdicSql;
using LambdicSql.feat.Dapper;
usingstatic LambdicSql.Keywords;
usingstatic LambdicSql.Funcs;
usingstatic LambdicSql.Utils;

publicvoid TestStandard()
{
    var min = 3000;

    //これが(割と)そのままSQLになる
    var query = Sql<DB>.Create(db =>
        Select(new SelectedData()
        {
            Name = db.tbl_staff.name,
            PaymentDate = db.tbl_remuneration.payment_date,
            Money = db.tbl_remuneration.money,
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_staff.id == db.tbl_remuneration.staff_id).
        Where(min < db.tbl_remuneration.money && db.tbl_remuneration.money < 4000));

    //文字列生成
    var info = query.ToSqlInfo(_connection.GetType());
    Debug.Print(info.SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}

こんなSQLが出力されます。

SELECT
    tbl_staff.name AS Name,
    tbl_remuneration.payment_date AS PaymentDate,
    tbl_remuneration.money AS Money
FROM tbl_remuneration
    JOIN tbl_staff ON (tbl_staff.id) = (tbl_remuneration.staff_id)
WHERE ((@min) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@p_1))

このブログで機能紹介していこうと思っています。

仕様変更がありました。

2016/07/04 ソースコード修正
2016/07/09 SQL修正 プリペアド対応
2016/09/02 β版

2016 MVP アワードを受賞いたしました!

$
0
0

f:id:ishikawa-tatsuya:20160703224319p:plain
今年も受賞することができました!
これも開発者コミュニティーの皆さまのおかげです。
ジャンルはVisual Studio and Development Technologiesです。

より一層OSSに取り組みます。

もちろん、テスト自動化もより一層やりやすいものにするべく新機能を追加していきます。
Friendly系
github.com

Selenium
github.com

今一押しはLambdicSqlです。

まだαですが、どんどん機能追加していきます。Expressionは面白いですねー。土日もゴリゴリ実装していましたw。
github.com

今年もよろしくお願いします。

何かしらの形でコミュニティーに貢献していきたいと思っております。OSSのライブラリ開発はもちろん、共有できる知識があれば、どんどん登壇させていただきます。よろしくお願いします!


LambdicSql - 機能紹介 - Where

$
0
0

GitHub - Codeer-Software/LambdicSql

Whereです。
私の少ないSQL経験ではプログラムで動的にSQLを組み立てる場合、これが一番面倒でした。なので色々できるように工夫しました。
サブクエリ使えるようにできたときは、めっちゃテンション上がりましたw

操作対象はテーブルが二つあるDBです。

//テーブルが二つあるDBpublicclass Staff
{
    publicint id { get; set; }
    publicstring name { get; set; }
}
publicclass Remuneration
{
    publicint id { get; set; }
    publicint staff_id { get; set; }
    public DateTime payment_date { get; set; }
    publicdecimal money { get; set; }
}
publicclass Data
{
    public Staff tbl_staff { get; set; }
    public Remuneration tbl_remuneration { get; set; }
}

普通にWhere

まずは普通に。Whereの中では()つけたり&&とか||とかで条件を指定できます。

publicvoid Where1()
{
    var query = Sql<Data>.Create(db =>
    Select(new Asterisk()).
    From(db.tbl_remuneration).

    //条件指定
    Where(3000< db.tbl_remuneration.money && db.tbl_remuneration.money < 4000));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT *
FROM tbl_remuneration
WHERE (((@p_0) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@p_1)));

変数で指定

条件は変数で指定することも可能です。

publicvoid Where2()
{
    //min,maxを変数で指定decimal min = 3000;
    decimal max = 4000;

    var query = Sql<Data>.Create(db =>
    Select(new Asterisk()).
    From(db.tbl_remuneration).

    //条件指定
    Where(min < db.tbl_remuneration.money && db.tbl_remuneration.money < max));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);
}
SELECT *
FROM tbl_remuneration
WHERE (((@p_0) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@p_1)));

可変の条件組み立て

Conditionメソッドにより、条件が簡単に組み立てられます。第一引数でその条件を有効か否かを指定できます。すべての条件が無効になったらWhere句は消えます。ORの組み立てとか()を付けたりするのはLinqよりも書きやすいと思います。

publicvoid Where3(bool minCondition, bool maxCondition)
{
    var query = Sql<Data>.Create(db =>
    Select(new Asterisk()).
    From(db.tbl_remuneration).

    //条件指定
    Where(Condition(true, 3000< db.tbl_remuneration.money) || Condition(false, db.tbl_remuneration.money < 4000)));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT *
FROM tbl_remuneration
WHERE ((@p_0) < (tbl_remuneration.money));

Between、Like、Inにも対応

publicvoid Where6()
{
    var query = Sql<Data>.Create(db =>
    Select(new Asterisk()).
    From(db.tbl_remuneration).

    //条件指定
    Where(Between(db.tbl_remuneration.money, 3000, 4000)));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT *
FROM tbl_remuneration
WHERE (tbl_remuneration.money BETWEEN @p_0 AND @p_1);
publicvoid Where7()
{
    var query = Sql<Data>.Create(db =>
    Select(new Asterisk()).
    From(db.tbl_staff).

    //条件指定
    Where(Like(db.tbl_staff.name, "%e%")));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);
}
SELECT *
FROM tbl_staff
WHERE (tbl_staff.name LIKE @p_0);
publicvoid Where8()
{
    var query = Sql<Data>.Create(db =>
    Select(new Asterisk()).
    From(db.tbl_remuneration).

    //条件指定
    Where(In(db.tbl_remuneration.money, 3000, 4000, 5000, 6000)));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT *
FROM tbl_remuneration
WHERE (tbl_remuneration.money IN(@p_0, @p_1, @p_2, @p_3));

なんと!サブクエリ使えます。

In句をサブクエリで書けます。実はLambdicSQLはサブクエリに対応していて、ここだけではなく色んなところでサブクエリが使えます。また別の回で紹介します。

publicvoid Where9()
{
    //サブクエリ作成
    var sub = Sql<Data>.Create(db =>
        Select(new
        {
            money = db.tbl_remuneration.money
        }).
        From(db.tbl_remuneration).
        Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id).
        Where(3000< db.tbl_remuneration.money && db.tbl_remuneration.money < 4000));

    //Inにサブクエリを入れる
    var query = Sql<Data>.Create(db =>
    Select(new Asterisk()).
    From(db.tbl_remuneration).

    //条件指定
    Where(In(db.tbl_remuneration.money, sub.Cast<int>())));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT *
FROM tbl_remuneration
WHERE tbl_remuneration.money IN(
	(SELECT
	tbl_remuneration.money AS money
	FROM tbl_remuneration
		JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)
	WHERE ((@p_0) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@p_1))))

コード修正

2016/07/09 SQL修正 プリペアド対応
2016/07/24 SelectFromに変更、ToExecutorの引数変更
2016/09/02 β版

LambdicSql - 機能紹介 - サブクエリ

$
0
0

GitHub - Codeer-Software/LambdicSql

前回も少し書きましたが、LambdicSqlはサブクエリを書けます。

今回の例もこんなDBが対象です。

//テーブルが二つあるDBpublicclass DB
{
    public Staff tbl_staff { get; set; }
    public Remuneration tbl_remuneration { get; set; }
}
//スタッフテーブルpublicclass Staff
{
    publicint id { get; set; }
    publicstring name { get; set; }
}
//報酬テーブルpublicclass Remuneration
{
    publicint id { get; set; }
    publicint staff_id { get; set; }
    public DateTime payment_date { get; set; }
    publicdecimal money { get; set; }
}

前回の例 Where句で利用

前回の例を再度書きます。でも、Cast&ltdecima&gt()ってなんやねんって感じですよね。これは、色んなところでサブクエリ書けるようにするための仕様なんですよ。

publicvoid WhereTest()
{
    //サブクエリ作成
    var sub = Sql<Data>.Create(db =>
        Select(new
        {
            money = db.tbl_remuneration.money
        }).
        From(db.tbl_remuneration).
        Join(db.tbl_staff, db => db.tbl_remuneration.staff_id == db.tbl_staff.id).
        Where(3000< db.tbl_remuneration.money && db.tbl_remuneration.money < 4000));

    //Inにサブクエリを入れる
    var query = Sql<Data>.Create(db =>
    Select(new Asterisk()).
    From(db.tbl_remuneration).

    //条件指定
    Where(In(db.tbl_remuneration.money, sub.Cast<int>()));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlConnection);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT *
FROM tbl_remuneration
WHERE tbl_remuneration.money IN(
	(SELECT
	tbl_remuneration.money AS money
	FROM tbl_remuneration
		JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)
	WHERE ((@p_0) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@p_1))))

Select句で利用

Select句での利用を見てもらえば納得してもらえるかも。サブクエリの結果をdecimalで受けたいわけなのですが、普通に受けると、queryの型になってしまいますよね。そこでキャストです。指定された型を戻り値にとるので、この例ではdecimalで変数に代入したかのようなコードを書くことができます。実際にはこのコードは動くわけではなく、式木が解析されてSQLの文字列が作成されるだけなのですよ。あ、キャストはExpression内で使わないと変換されませんよw

//moneyの全合計
var sub = Sql<DB>.Create(db=>
    Select(new
    {
        total = Sum(db.tbl_remuneration.money)
    }).
    From(db.tbl_remuneration));

//スタッフごとにグルーピングされたmoneyと全体の合計を同時に表示する
var query = Sql<DB>.Create(db =>
    Select(new
    {
        name = db.tbl_staff.name,
        personalTotal = Sum(db.tbl_remuneration.money),
        total = sub.Cast<decimal>()//★サブクエリ 代入したかのようなコードが書ける
    }).
    From(db.tbl_staff).
    Join(db.tbl_remuneration, db.tbl_remuneration.staff_id == db.tbl_staff.id).
    GroupBy(db.tbl_remuneration.staff_id, db.tbl_staff.name));

//文字列化
Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

//Dapperを使っているなら、以下のように実行できます
var datas = _connection.Query(query).ToList();
SELECT
tbl_staff.name AS name,
	SUM(tbl_remuneration.money) AS personalTotal,
	(SELECT
	SUM(tbl_remuneration.money) AS total
	FROM tbl_remuneration) AS total
FROM tbl_staff
	JOIN tbl_remuneration ON (tbl_remuneration.staff_id) = (tbl_staff.id)
GROUPBY tbl_remuneration.staff_id, tbl_staff.name

From句で利用

From句でも利用できます。ちょっと普通のSQLとは書き方異なってしまいます。

publicvoid SubQuery2()
{
    //サブクエリ作成
    var sub = Sql<DB>.Create(db =>
        Select(new
        {
            name = db.tbl_staff.name,
            payment_date = db.tbl_remuneration.payment_date,
            money = db.tbl_remuneration.money,
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id).
        Where(3000< db.tbl_remuneration.money && db.tbl_remuneration.money < 4000));

    var query = Sql<DB>.Create(db =>
        Select(new
        {
            name = sub.Body.name
        }).
        //From句でサブクエリのテーブル(的なもの)を指定//View的な使い方
        From(sub));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT
sub.name AS name
FROM 
	(SELECT
	tbl_staff.name AS name,
		tbl_remuneration.payment_date AS payment_date,
		tbl_remuneration.money AS money
	FROM tbl_remuneration
		JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)
	WHERE ((@p_2) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@p_3))) sub

この場合は、Viewを使うイメージで使ってもらうことになります。最初に利用するテーブルのようなイメージでQuery関数で指定してもらいます。この例では匿名クラスでやっていますが、もちろん普通の型でも同様のことができます。Generic指定なしのCast()を使うとそのクエリでSelectされている型になります。

他にもいろんなところで使える(はず

結局文字列置換です。他にも普通にサブクエリ書けるところでは使えると思います。

コード修正しました。

2016/07/09 SQL修正 プリペアド対応
2016/09/02 β版対応

LambdicSql - TODO 今後の改善ポイント

$
0
0

GitHub - Codeer-Software/LambdicSql

私のSQL力の低さを補うべく、SQLServerのMVPのおださんに、色々教えてもらいました。(えらい初心者向けのことから)いや、このシリーズの初回にも書きましたが、本当にSQLはほとんど書いたことがなかったのです。組み込みとか、それを制御するPCアプリがメインだったので・・・。

それで、達人から見たLambdicSqlの改善点などもご指摘いただきました。さらにヘッドハンティングにも成功し、開発メンバに加わっていただきました。今後も総監督的立場から色々ご指導いただきます。あわよくば開発もw

プリペアド使わなきゃ

2016/07/09 α0.0.12で対応完了
まず、何をおいてもこれは対応しなければってことでした。基本らしいです。すみません。例えば、文字列とかそのまま出してたんだけど、それをやると文字列にSQL構文いれられたときにヤバいことになる。これがかの有名なSQLインジェクションだったのか。

publicvoid Test()
{
    var target = "ishikawa";

    var query = Sql.Query<Using2>().Select().
    From(db => db.tbl_staff).

    //変数で来たものを条件に入れる
    Where(db => db.tbl_staff.name == target);

    //実行
    var datas = query.ToExecutor(TestEnvironment.Adapter).Read();
}
SELECT
	tbl_staff.id,
	tbl_staff.name
FROM
	tbl_staff
WHERE/*SQLにリテラルいれちゃダメよ*/
	((tbl_staff.name) = ('ishikawa'));

DISTINCTの構文が違和感あり

2016/07/24 α0.0.36で対応完了
いや、これは僕のDISTINCTの理解が間違ってたんです。サポートしたものの実際に使ったことはなかったのでw。DISTINCT指定した列だけを一意にしてくれるのかと思ってましたが、Selectしたすべての列が完全に同じものを省くというものだったんですね。

publicvoid Distinct()
{
    var datas = Sql.Query<DB>().
        Select(db => new
        {
            //この仕様は変
            id = Sql.Word.Distinct(db.tbl_remuneration.staff_id)
        }).
        From(db => db.tbl_remuneration).
        ToExecutor(new SqlServerAdapter(ConnectionString)).Read();
}
```sql
SELECTDISTINCT tbl_remuneration.staff_id AS"staff_id",
	/*これじつは↑だけじゃなくて↓にもかかっている*/
	tbl_remuneration.money AS"money"FROM
	tbl_remuneration;

SQL玄人はCase式を使う

2016/07/29 α0.0.39で対応完了
そうなんだー。むしろCaseは「データとってきてからアプリで加工すればいいかなー」とか思ってて、実装しない予定でした。でもそういうことなら頑張って実装しますよー。

Whereの組み立てをもっと便利に

2016/07/24 α0.0.38で対応完了
やっぱり、これ系のライブラリに期待することはWhereの書きやすさのようです。逆に言えば、それ以外はテキストでも問題ないかなーとのこと。了解です。そこをもっと改善します。

クエリを組み合わせやすくした方がいい

2016/07/31 α0.0.40で対応完了
今もある程度はできますが、ここをもっと自由に便利にできた方がいいとのこと。確かにSQL構文を部品化して型安全に組み立てることができたら便利ですよね!この辺の話など参考になるようです。
http://www.slideshare.net/kwatch/sqlor

コネクションは使いまわしたい

2016/0723 α0.0.34で対応完了。
今の仕様では、内部的にコネクションの開閉をやってるんですよね。それで便利な場合もありますが、トランザクションの場合などは一つのコネクションを使いまわしたいとのこと。なるほどねー。

ウィンドウ関数欲しい

おー、やっぱウィンドウ関数もいるんだ。了解っす。勉強して追加しておきます。

Dapperは速いよ

->高速化中。
ishikawa-tatsuya.hatenablog.com
そこに関してはLambdicSqlも、式木から生成メソッドをコンパイルしてキャッシュしているから、良い線いけるんじゃないかなー。まあ計測してみよっと。

ツール欲しい

クラス書くのメンドイ。まあ、大規模になればそうですよねー。DBのスキーマからクラス作るツールをVS拡張でつくろうかな。

ありがとうございました!

おださん、今日は長い時間ありがとうございました。今後ともよろしくっす!

LambdicSql - α 0.0.12リリース - プリペアド対応

$
0
0

GitHub - Codeer-Software/LambdicSql

前回のTODOのうちのプリペアドに対応しました。
変数や直値で渡した値は自動でプリペアドに変換されます。
Where句だけでなく他の句でも同様に置き換え処理が実施されます。
ふう、一安心。
今までブログで書いたサンプルのSQLも更新しました。

publicvoid StandardNoramlType()
{
    var max = 4000;

    var query = Sql<DB>.Create(db =>
        Select(new SelectedData()
        {
            Name = db.tbl_staff.name,
            PaymentDate = db.tbl_remuneration.payment_date,
            Money = db.tbl_remuneration.money,
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id).

        //直値や変数でパラメータを渡す。
        Where(3000< db.tbl_remuneration.money && db.tbl_remuneration.money < max).
        OrderBy(new Asc(db.tbl_staff.name)));


    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT
tbl_staff.name AS Name,
	tbl_remuneration.payment_date AS PaymentDate,
	tbl_remuneration.money AS Money
FROM tbl_remuneration
	JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)
WHERE ((@p_0) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@max))
ORDERBY
	tbl_staff.name ASC

履歴

2016/09/02 β版対応

LambdicSql - α 0.0.13リリース - IS NULL 対応

$
0
0

IS NULL 対応しました。
www.nuget.org

最初は IS NULL は何かキーワードを用意して特殊対応にしようと思ってました。int値の時とか面倒ですしね。でも、@yone64さんが、「昔にこの問題に対応したことがある」的なことを言っていたので、やっぱりnullの時に IS NULL に自動で変換した方が便利かなーって思って対応しました。

*あれ?最近のDBはNULLキーワードが主流なようですね。NULLキーワードで比較するようにしました。

SQL作成に使う型です。

//スタッフテーブルpublicclass Staff
{
    publicint id { get; set; }
    publicstring name { get; set; }
}
//報酬テーブルpublicclass Remuneration
{
    publicint id { get; set; }
    publicint staff_id { get; set; }
    public DateTime payment_date { get; set; }
    publicdecimal money { get; set; }
}
//二つのテーブル定義を持つpublicclass DB
{
    public Staff tbl_staff { get; set; }
    public Remuneration tbl_remuneration { get; set; }
}
//Select句で使う定義publicclass SelectData
{
    publicstring name { get; set; }
    public DateTime payment_date { get; set; }
    publicdecimal money { get; set; }
}

null比較すると、IS NULL になります。
値型の場合はNullable<>で比較するという仕様です。
この例ではmoneyをdecimal?のパラメータと比較することで値型でも IS NULL のクエリを生成できています。

publicvoid IsNull()
{
    decimal? val = null;
    var query = Sql<DB>.Create(db=>
        Select(new SelectedData()
        {
            Name = db.tbl_staff.name,
            PaymentDate = db.tbl_remuneration.payment_date,
            Money = db.tbl_remuneration.money,
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id).
        Where(db.tbl_staff.name == null || db.tbl_remuneration.money == val));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT
tbl_staff.name AS Name,
	tbl_remuneration.payment_date AS PaymentDate,
	tbl_remuneration.money AS Money
FROM tbl_remuneration
	JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)
WHERE ((tbl_staff.name) = (NULL)) OR ((tbl_remuneration.money) = (NULL))

!= で比較すると IS NOT NULL になります。

publicvoid IsNotNull()
{
    decimal? val = null;
    var query = Sql<DB>.Create(db=>
        Select(new SelectedData()
        {
            Name = db.tbl_staff.name,
            PaymentDate = db.tbl_remuneration.payment_date,
            Money = db.tbl_remuneration.money,
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id).
        Where(db.tbl_staff.name != null || db.tbl_remuneration.money != val));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT
tbl_staff.name AS Name,
	tbl_remuneration.payment_date AS PaymentDate,
	tbl_remuneration.money AS Money
FROM tbl_remuneration
	JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)
WHERE ((tbl_staff.name) <> (NULL)) OR ((tbl_remuneration.money) <> (NULL))

履歴

2016/09/02 β版対応

Viewing all 104 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>