Windows Azure Tips from Prospex

システム開発 × インフラ構築・運用 × グラフィックデザイン = プロスペックス

Expression を使って動的 OrElse

clock February 19, 2012 15:47 by author Ito

なますて。

とあるAzureプロジェクトで、パフォーマンスが超悪いものがあるので、それを何とかしている今日この頃です。 

 

さて、Azureでトレンディーなもののひとつに、Windows Azure Table があります。
こいつは、自動でスケールアウトされたり、データを大量に詰め込んでもほぼ定数時間で取得出来たり(PartitionKey, RowKey を指定したとき)と、
使いこなせれば非常に強力なものだと思います。

しかし、使い方を間違ったら…マジやばいです。

 

今回のプロジェクトでも、Azure Table を使用していますが、その使用している部分が非常に遅い…

具体的に何が遅いと言うと

・RDBMSのように、親テーブルがあり、詳細情報用に子テーブルがある。
・親テーブル項目をキーとして、子テーブルを Azure Table から一件ずつ取得している。 

実際には、親テーブルは Sql Azure にいて、詳細情報は Azure Table にいるという変則なのですが…

この、Azure Table から一件ずつ取得が超遅いのです。

速度を調べてみると、Azure Table から一件取得するのにかかる時間は大体50ms。
それを約300件分取得しようとするので…15秒!
(現在は苦肉の策で20件ずつのページング処理化してあります)

 

そんなの、一回で全部取得すればいいじゃないか

 

そこで考えたのが、取得するエンティティのPartitionKeyとRowKeyをOrElseでつなげる。

しかし、取得したいエンティティの件数は動的に変化するので、Expressionクラスを用いて動的にWhereメソッドに渡す式を作ってみます。

 

Expression exp = null;
ParameterExpression paramExp = Expression.Parameter(typeof(HogeEntity), "e");

// Expression : e.PartitionKey == pkey
string pkey = string.Format("prospex-hoge-{0:0000000000}", tenantID);
Expression pKeyExp = Expression.Property(paramExp, "PartitionKey");
Expression pkeyEqualExp = Expression.Equal(pKeyExp, Expression.Constant(pkey));

foreach ( var item in items) {
    // Expression : e.RowKey.CompareTo(rkey) >= 0
    string rkey = string.Format(
        "{0:0000000000}-{1:0000000000}-{2:0000000000}-{3:0000000000}-{4:000}-",
        item.TenantID,
        item.BoxID,
        item.ThreadID,
        item.MessageID,
        item.Language);
    Expression rKeyExp = Expression.Property(paramExp, "RowKey");
    System.Reflection.MethodInfo compareToType = typeof(string).GetMethod("CompareTo",new Type[] { typeof(string) });
    Expression rKeyCompareToExp = Expression.Call(rKeyExp, compareToType, Expression.Constant(where_RKey));
    Expression rkeyGteExp = Expression.GreaterThanOrEqual(rKeyCompareToExp, Expression.Constant(0));

    // OrElse結合
    if ( exp == null ) {
        exp = rkeyGteExp;
    } else {
        exp = Expression.OrElse(exp, rkeyGteExp);
    }
}
 
exp = Expression.AndAlso(pkeyEqualExp, exp);
 
TableServiceContext context = _tableClient.GetDataServiceContext();
IQueryable<HogeEntity> entitys = context.CreateQuery<HogeEntity>("HogeEntity");
var q = from t in entities
        select t;
q = q.Where(Expression.Lambda<Func<HogeEntity, bool>>(exp, paramExp));

 

そして実行…したら例外でました。内容は、

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">

  <code>InvalidInput</code>
  <message xml:lang="en-US">0:One of the request inputs is not valid.
RequestId:d54d290e-bc01-4fa7-9c77-30bde58fde6c
Time:2012-02-19T07:49:39.6028980Z</message>
</error> 

理由はたぶん、URLが長くなりすぎたと思われ。
(TableServiceContext? は、REST API のラッパーであるため、最終的には Azure Table のURLにリクエストを投げているだけ)

 

そこで、とあるサイトで「バッチリクエスト(エンティティグループトランザクション)」で送る必要があるようなことが書かれていたため、試してみた。

 

foreach ( QueryOperationResponse res in context.ExecuteBatch((DataServiceQuery<MessageBodyEntity>)q) ) {
    foreach ( HogeEntity entity in res) {
        data = entity;
    }
}


これで実行…だが結果は同じであった。


とりあえず調べてみて分かった Azure Table の制限。

・長いURLはつかえない。(ブチザッキさんに詳しく書かれている)
・バッチリクエスト(ExecuteBatch)では、$filter は使えない。(上の方法だと、最終的に$filterを使うURLになる)


だめじゃん…


Azure Table はちゃんと設計しないとハマります。




Access2010からSQLAzureにODBC接続

clock February 7, 2012 13:18 by author Kawana

Access2010からSQLAzureにODBC接続(リンクテーブル)を行いました。
この機能を使えば、社内でSQL Serverの運用をすることなく、Access 2010から大規模なリレーショナルデータベースの機能を利用することができるようになります


■SQLAzureにテーブル作成

CREATE TABLE [dbo].[T_TEST](
 [T_TEST_ID] [int] IDENTITY(1,1) NOT NULL,
 [CharField] [char](3) NOT NULL,
 [IntField] [int] NOT NULL,
 [DateTimeField] [datetime] NULL,
 [NvarcharField] [nvarchar](1000) NULL,
 [NtextField] [ntext] NULL,
 [RG_DT_FST] [datetime] NULL,
 CONSTRAINT [PK_T_TEST] PRIMARY KEY CLUSTERED
(
 [T_TEST_ID] ASC
)
)

GO

ALTER TABLE [dbo].[T_TEST] ADD  CONSTRAINT [DF_T_TEST_RG_DT_FST]  DEFAULT (getdate()) FOR [RG_DT_FST]
GO


■Access2010からSQL Azureに接続
 Access2010を開いて「外部データ」タブの「ODBCデータベース」をクリック。
 「外部データの取り込み」ウイザードが起動します。
 "リンクテーブルを作成してソースデータにリンクする"」を選択して次へ

 

 

 

 「データソースの選択」ダイアログで"新規作成"をクリック。
 「データソースの新規作成」ダイアログで"SQL Server Native Client 10.0"を選択して次へ

 

 


 データソース名を適当に指定して次へ 

 

 

 「Create a New Data Source toSQL Server」ダイアログが開くので
 "Description"を適当に、"Server"にはSQL Azureのサーバー名を入力して次へ

 


 "With SQL Server authentification using a login ID and password entered by the user"を選択してSQL AzureにログインできるIDとPWを入力して次へ
 ※Login IDは"ユーザー名@SQL Azureサーバー名"としてください。

 


 "Change the default database to"を選択してSQL Azureのデータベースを選択して次へ

 


 "Change the language of SQL Server system messages to"に"Japanese"を選択
 "Use regional settings when outputting currency,numbers,dates and times"にチェックを入れて完了
 ※これはSQL Azureでは意味が無いかもしれません。 

 

 

 作成完了画面が表示されます。 "Test data source"をクリックすれば作成したデータソースのテストもできます。

 


 「データソースの選択」ダイアログで作成されたデータソースがあるので、それを選択してOK

 

 「テーブルのリンク」ダイアログが開くのでSTEP1で作成したテーブルを選択してOK

 

 

 リンクテーブルを開いて新規レコードを追加してみました。
 簡単にできるので試してみてください。

 

 

 



Microsoft Translator を使ってみました

clock January 31, 2012 21:30 by author fujii

「Microsoft Translator」を使ってみました。

詳細は下記のページです。

http://www.microsofttranslator.com/Tools/

image

上記のページから下記の「Windows Azure Marketplace」の「Microsoft Translator」のページにたどり着いたので、「¥0.00」の「2000 tx/month」を購入しました。

https://datamarket.azure.com/dataset/1899a118-d202-492c-aa16-ba21c33c06cb

image

下記のページに使用法があったので、使ってみました。

http://msdn.microsoft.com/en-us/library/hh454950.aspx

コンソールアプリケーションを作成し、SOAP web service を追加。

http://api.microsofttranslator.com/V2/Soap.svc

Oimage

image

「Access Token」を取得し、メソッドを並べてみました。

using System;
using System.IO;
using System.Net;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;
using System.Web;
using System.Data;
using System.Linq;
 
namespace TranslationTest
{
    class Program
    {
        static void Main(string[] args)
        {
            string uri = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13";
            string clientId = "??????????";         //get it from https://datamarket.azure.com/developer/applications
            string clientSecret = "??????????";  //get it from https://datamarket.azure.com/developer/applications
            string headerValue = "";
            string request = string.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&scope=http://api.microsofttranslator.com", clientId, HttpUtility.UrlEncode(clientSecret));
 
            // Get an Access Token
            try
            {
                MTAccessToken mtToken = HttpPost(uri, request);
 
                // Create a header with only the access_token property of the returned token
                headerValue = "Bearer " + mtToken.access_token;
                Console.WriteLine(headerValue + "\r\n");
            }
            catch (Exception ex)
            {
                Console.WriteLine( ex.Message);
            }
 
            // Use the Access Token in a Translate() call
            try
            {
                // Add the http header
                HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
                httpRequestProperty.Method = "POST";
                httpRequestProperty.Headers.Add("Authorization", headerValue);
 
                // Call Translator with the header
                using (MTService.LanguageServiceClient myClient = new MTService.LanguageServiceClient())
                {
                    using (OperationContextScope scope = new OperationContextScope(myClient.InnerChannel))
                    {
                        OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
                        
                        // AddTranslation Test(辞書に単語や文章を追加します)
                        Console.WriteLine("AddTranslation Test:Press Enter");
                        Console.ReadLine();
                        
                        myClient.AddTranslation("","translate","übersetzen","en","de",1,"text/plain","general","username",null);
                        myClient.AddTranslation("", "prospex", "プロスペックス", "en", "ja", 1, "text/plain", "general", "username", null);
                        myClient.AddTranslation("", "prospex", "株式会社プロスペックス", "en", "ja", 1, "text/plain", "general", "username", null);
                        Console.WriteLine("Your translation has been added");
                        
                        Console.WriteLine("\n");
                        
                        // BreakSentences Test(文章の一文ごとの文字数を数えます)
                        Console.WriteLine("BreakSentences Test:Press Enter");
                        Console.ReadLine();
 
                        try
                        {
                            string text = "Please break up this string into sentences. I would like this string to be broken into its respective sentences. Is this possible?";
                            string from = "en";
                            int[] sentenceLengths = myClient.BreakSentences("", text, from);
                            var breakSentencesResult = "BreakSentences broke up the above sentence into " + sentenceLengths.Length + " sentences.";
                            var lengths = String.Join(",", sentenceLengths.Select(x => x.ToString()).ToArray());
                            
                            breakSentencesResult += "The lengths of the sentences are " + lengths + " characters respectively.";
                            Console.WriteLine(breakSentencesResult);
                        }
                        catch (Exception)
                        {
                            Console.WriteLine("Something unexpected has occurred. Try again later.");
                            Console.ReadLine();
                        }
 
                        Console.WriteLine("\n");
                
                // Detect Test(文章から文字コードを取得します)
                        Console.WriteLine("Detect Test:Press Enter");
                        Console.ReadLine();
 
                        string detectResult;
                        detectResult = myClient.Detect("", "I have no idea what this language may be");
                        Console.WriteLine("The detected language friendly code is: " + detectResult);
                        
                        Console.WriteLine("\n");
                
                        // GetLanguageNames Test(文字コードの名称を指定した言語で取得します)
                        Console.WriteLine("GetLanguageNames Test:Press Enter");
                        Console.ReadLine();
 
                        string[] languageCodes = { "de", "en", "fr", "ko" };
                        string[] languageNames = myClient.GetLanguageNames("", "ja", languageCodes);
 
                        for (int i = 0; i < languageNames.Length; i++)
                        {
                            Console.WriteLine("{0} = {1}", languageCodes[i], languageNames[i]);
                        }
 
                        Console.WriteLine("\n");
       
                        // GetLanguagesForTranslate Test(使用できる言語コードの配列を取得します)
                        Console.WriteLine("GetLanguageForTranslate Test:Press Enter");
                        Console.ReadLine();
 
                        string[] languagesForTranslate = myClient.GetLanguagesForTranslate("");
                        var availableLanguages = String.Join(",", languagesForTranslate.Select(x => x.ToString()).ToArray());
                        Console.WriteLine("The languages available for translation are: " + availableLanguages);
 
                        Console.WriteLine("\n");
 
                        // GetTranslations Test(AddTranslationsで保存した単語や文章を取り出します)
                        Console.WriteLine("GetTranslations Test:Press Enter");
                        Console.ReadLine();
 
                        MTService.TranslateOptions options = new MTService.TranslateOptions(); // Use the default options
 
                        options.User = "username"; // default is all
                        MTService.GetTranslationsResponse translations = myClient.GetTranslations("", "prospex", "en", "ja", 5, options);
                        string matches = "The matches are: ";
 
                        foreach (MTService.TranslationMatch translationMatch in translations.Translations)
                        {
                            matches += "\"" + translationMatch.TranslatedText + "\"";
                        }
                        Console.WriteLine(matches);
                        
                        Console.WriteLine("\n");
 
                        // Translate Test
                        Console.WriteLine("Translate Test:Press Enter");
                        Console.ReadLine();
 
                        string input;
                        
                        while(true)
                        {
                            Console.WriteLine("Please input Japanese(or press Enter):");
                            input = Console.ReadLine();
                            if (input == "") break;
                            Console.WriteLine(myClient.Translate("", input, "ja", "en", "text/html", null) + "(" + input.Length + ")");
                            Console.WriteLine("\n");
                        }
 
                        Console.WriteLine("\n");
 
                        // TranslateArray Test
                        Console.WriteLine("TranslateArray Test:Press Enter");
                        Console.ReadLine();
 
                        string[] texts = new string[2];
 
                        options = new MTService.TranslateOptions(); // Use the default options
 
                        while (true)
                        {
                            Console.WriteLine("Please input Japanese subject(or press Enter):");
                            texts[0] = Console.ReadLine();
                            if (texts[0] == "") break;
                            Console.WriteLine("Please input Japanese body(or press Enter):");
                            texts[1] = Console.ReadLine();
                            if (texts[1] == "") break;
 
                            MTService.TranslateArrayResponse[] translatedTexts = myClient.TranslateArray("", texts, "ja", "en", options);
 
                            Console.WriteLine("\n");
                            Console.WriteLine("Subject:" + translatedTexts[0].TranslatedText + "(" + texts[0].Length + ")");
                            Console.WriteLine("Body:" + translatedTexts[1].TranslatedText + "(" + texts[1].Length + ")");
                            Console.WriteLine("\n");
                        }
 
                        Console.WriteLine("\n");
                        
                        Console.WriteLine("End:Press Enter");
                        Console.ReadLine();
                    }
                }   
            }
            catch (Exception ex)
            {
                Console.WriteLine( ex.StackTrace);
                Console.WriteLine( ex.Message);
                Console.ReadLine();
            }            
        }
 
        [DataContract]
        public class MTAccessToken
        {
            [DataMember]
            public string access_token { get; set; }
 
            [DataMember]
            public string token_type { get; set; }
 
            [DataMember]
            public string expires_in { get; set; }
 
            [DataMember]
            public string scope { get; set; }
        }
 
        private static MTAccessToken HttpPost(string uri, string parameters)
        {
            WebRequest webRequest = WebRequest.Create(uri);
            webRequest.ContentType = "application/x-www-form-urlencoded";
            webRequest.Method = "POST";
 
            byte[] bytes = Encoding.ASCII.GetBytes(parameters);
            Stream os = null;
 
            try
            { 
                webRequest.ContentLength = bytes.Length;
                os = webRequest.GetRequestStream();
                os.Write(bytes, 0, bytes.Length);         
            }
            catch (WebException ex)
            {
                Console.WriteLine(ex.Message, "HttpPost: Request error");
                Console.ReadLine();
            }
            finally
            {
                if (os != null)
                {
                    os.Close();
                }
            }
 
            try
            { 
                // get the response
                WebResponse webResponse = webRequest.GetResponse();
 
                if (webResponse == null)return null;
 
                DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(MTAccessToken));
                MTAccessToken token = (MTAccessToken)serializer.ReadObject(webResponse.GetResponseStream());
 
                return token;
            }
            catch (WebException ex)
            {
                Console.WriteLine(ex.Message, "HttpPost: Response error");
                Console.ReadLine();
            }
            return null;
        }
    }
}
 

上記の「clientId」と「clientSecret」は、下記のページで登録する必要があるようなので、登録しました。(コードの「??????????」の部分です)

https://datamarket.azure.com/developer/applications

image 

あと、各メソッドの第一引数は空文字にしておくそうです。(Bing デベロッパーセンターでAppIDを登録した場合に使用するそうです)

できました。

image

「私は犬っぽい人です。」と入力すると、「I'm like a dog person.」と翻訳してくれました。

あと、個人的にはSpeakメソッドが気になるんだが……今日はここまで。



SQL Azureへの接続について

clock January 26, 2012 11:19 by author tanaka




SQL AzureはSQL Serverと違って、負荷分散されているため、
データベースがサーバー間を移動することがあるので、
DBへのコネクションは再接続を考慮して作成しなければいけないようだ。

コネクションリトライを組み込んだコードは以下で紹介されています。
http://blogs.msdn.com/b/bartr/archive/2010/06/18/sql-azure-connection-retry.aspx



ローカルストレージを利用する

clock January 21, 2012 17:21 by author Nakajima

 

今回は、Windows Azureのローカルストレージを利用してみました。

(1) ローカルストレージの領域を確保する設定を行います。

image

 

(2) 次にローカルストレージにテキストファイルを保存するコーディング例を記載します。

using System;
using System.IO;
using System.Text;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace WebRole1
{
    public partial class PsxTest : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
     
            LocalResource psxtext = RoleEnvironment.GetLocalResource("Psx");

            var path = Path.Combine(psxtext.RootPath,"psx.txt");

            using (var writer = new StreamWriter(path, true))
            {
            var builder = new StringBuilder();
            builder.Append("おはようございます。");

            writer.Write(builder.ToString());
            }

        }
    }
}

 

※ポインンとは、赤字部分です。

Microsoft.WindowsAzure.ServiceRuntime名前空間を設定します。

GetLocalResourceメソッドで、ローカルストレージのオブジェクトを取得します。

 

(3) 実際に実行し、テキストファイルが作成されているか確認してみましょう。

Windows Azure  Compute Emulator を開き、ツリーの「0」で右クリックし「Open local sore」を選択します。

image

directory¥psxを選択するとpsx.txtが作成されていることがわかります。

image



Storage Table の設計パターン(絞り込みを考える)

clock January 14, 2012 19:05 by author Sekine

Storage Table を利用する場合、

PartitionKeyとRowKeyをどのように決めるのか重要なポイントとなります。

あまり気にせずに作ってしまうと、テーブル内のデータ量が多くなって来た時に、

抽出時のパフォーマンスが落ち、致命的な欠陥につながります。

特にRowKeyの考え方が重要で(PartitionKeyもそうですが)

まず、Storage Tableでは、RowKeyに対して昇順のインデックス付けがされます。

Table Storage の トランザクション – EGT

それを念頭に、実際にRowKeyをどのように組み立てれば良いかという事になるのですが、

例えば、PartitionKey、RowKey 以外の項目に where をかけるとすると、

対象テーブルの全てに走査・抽出が行われ、

データ量(レコード量)が多い場合には結果的に遅くなり、

さらにタイムアウトにより後方に存在するデータは抽出されないという事になります。

 

そのような事にならないように、

抽出するキーとなるものは、RowKeyに含め、RowKeyに対して where をかける

というのが原則となります。

RowKeyは、文字列型ですが、64KBの長さではなく、260文字しか有効ではありません。
(これは、PartitionKeyでも同じです)

 

データを表すキー項目が一つであれば、そのままRowKeyを使用するだけですが、

そんな単純なデータは多くはなく、複数のキーが有るのが普通でしょう。

そのような場合は、各キーを固定長の文字にして、連結してRowKeyとします。

例えば、店舗名、従業員名のキーが在るとしましょう。その場合、

RowKey = 店舗名20文字_従業員名20文字

のようにします。

数値が必要なら、string.Format(“0:10D”, 設定値);

日付なら、Ticks をうまく使うなどして、各項目を固定長に整形して連結します。

文字列が長い場合には、MD5ハッシュなどすれば、32文字になります。
(その場合、大文字小文字でハッシュ値が違うので注意してください。常に小文字にするなど)

 

このようにRowKeyを決める事で、

上の例で言えば、ある店舗名の従業員一覧が欲しい場合、

var query = from t in {TableServiceContextの従業員データ}
            where
            t.PartitionKey == partitionKey &&
            t.RowKey.CompareTo({調べたい店舗名20char}) >= 0 &&
            t.RowKey.CompareTo({調べたい店舗名20char} + "\uffff") < 0
            select t;

とすることで、その店舗名で始まるRowKeyを抽出できます。

t.RowKey.CompareTo({other}) >= 0 &&
t.RowKey.CompareTo({other} + "\uffff") < 0

上の、この構文は、StartsWith 相当です。

t.RowKey.StartsWith({other}) と同等になりますが、

Storage Table (TableServiceContext) の場合、Queryプロバイダが

StartsWithに対応していませんが、上記の書き方によって同等となります。

さらに言えば、部分条件抽出は、上記 StartsWith 相当しか無いと思ったほうがよさそうです。

SubString、EndWith、(他、部分的比較命令)は使用できません。

また、それらに相当する書き方も実際うまくいかないのが現状です。

この辺は、開発ストレージで動いたとしても、実際に動かなかったりしますので

十分注意してください。

(個人的には、開発ストレージで開発しないほうが良いとも思っています)

 

上記のことから、RowKeyは先頭一致の抽出が可能な事と、

それを利用して、キー項目を固定長で連結することが大事です。

固定長にするのは、where の文字列を作るときに任意な長さでは曖昧になるため。

また、RowKeyの値は変更できません。

 

以前、論理削除の意で、

0_DATA
削除すると
1_DATA

という、先頭1charを削除フラグとして試したことがあります。

UpdateChangesは失敗し、更新できません。

これは、仕様ですので当然なのですが、

思いついた時の歓喜と、その後の落胆が大きかったです・・



セッションごにょごにょしたくなった

clock January 9, 2012 23:53 by author Ito

こんばんは。

今夜は Azure のセッションについて調べてみた。
調べていて、良いまとめサイトがあったので紹介します。

ブチザッキ - {2011/08/23}   Windows AzureのWeb Roleでセッションを共有する
http://buchizo.wordpress.com/2011/08/23/windows-azure%E3%81%AEweb-role%E3%81%A7%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E5%85%B1%E6%9C%89%E3%81%99%E3%82%8B/

では今夜はこのへんで…

 

ごめんなさい。

以上の記事に書かれていることで、だいたいのセッションの問題は解決すると思いうけど、
あるものを使うだけでは面白くないため、今回から自前でセッション管理してみようとおもいます。

まず、簡単なカスタムセッションを作ってみます。
SessionStateStoreProviderBase クラスを継承して作っていくようです。
http://msdn.microsoft.com/ja-jp/library/ms178587%28v=VS.90%29.aspx

サンプルはMSDNにもあるのでそれをみつつ…
http://msdn.microsoft.com/ja-jp/library/ms178588%28v=VS.90%29.aspx
http://msdn.microsoft.com/ja-jp/library/ms178589%28v=VS.90%29.aspx

public class TestSessionStateProvider : System.Web.SessionState.SessionStateStoreProviderBase {

        private int Timeout {
            get { return 20; }
        }

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) {
            base.Initialize(name, config);
        }
        public override void InitializeRequest(HttpContext context) {
        }

        public override void EndRequest(HttpContext context) {
        }

        public override void Dispose() {
        }

        public override System.Web.SessionState.SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out System.Web.SessionState.SessionStateActions actions) {
            return GetSessionData(true, context, id, out locked, out lockAge, out lockId, out actions);
        }

        public override System.Web.SessionState.SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out System.Web.SessionState.SessionStateActions actions) {
           return GetSessionData(false, context, id, out locked, out lockAge, out lockId, out actions);
        }

        public override void SetAndReleaseItemExclusive(HttpContext context, string id, System.Web.SessionState.SessionStateStoreData item, object lockId, bool newItem) {
            UpdateSessionData(context, id, item, lockId, newItem);
        }

        public override void ReleaseItemExclusive(HttpContext context, string id, object lockId) {
            // 実装は後ほど
        }

        public override void RemoveItem(HttpContext context, string id, object lockId, System.Web.SessionState.SessionStateStoreData item) {
            // 実装は後ほど
        }

        public override void CreateUninitializedItem(HttpContext context, string id, int timeout) {
            // 実装は後ほど
        }

        public override System.Web.SessionState.SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout) {
            return new SessionStateStoreData(new SessionStateItemCollection(),
                                             SessionStateUtility.GetSessionStaticObjects(context),
                                             timeout);
        }

        public override bool SetItemExpireCallback(System.Web.SessionState.SessionStateItemExpireCallback expireCallback) {
            // 実装は後ほど
            return false;
        }

        public override void ResetItemTimeout(HttpContext context, string id) {
            // 実装は後ほど
        }

        private SessionStateStoreData GetSessionData(bool lockSession, HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out System.Web.SessionState.SessionStateActions actions) {
            // 今回は、HttpContext.Application にセッション情報を保存する

            SessionStateStoreData sessionState;

            Dictionary<string, byte[]> sessionData
                = (Dictionary<string, byte[]>)HttpContext.Current.Application["session"];

            // id のセッションがあるか
            if ( sessionData != null && sessionData.ContainsKey(id) ) {
                // セッションを復元

                using ( var stream = new MemoryStream(sessionData[id]) ) {
                    using ( var reader = new BinaryReader(stream) ) {

                        sessionState = new SessionStateStoreData(SessionStateItemCollection.Deserialize(reader),
                                                         SessionStateUtility.GetSessionStaticObjects(context),
                                                         this.Timeout);
                    }
                }

            } else {
                // 新規にセッションを作成
                sessionState = CreateNewStoreData(context, this.Timeout);
            }
           
            locked = false;
            lockId = null;
            lockAge = new TimeSpan(0, this.Timeout, 0);
            actions = SessionStateActions.None;

            return sessionState;
        }

        private void UpdateSessionData(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem) {
            // HttpContext.Application に保存されているセッション情報を復元する

            Dictionary<string, byte[]> sessionData
                = (Dictionary<string, byte[]>)HttpContext.Current.Application["session"];
            if ( sessionData == null ) {
                sessionData = new Dictionary<string, byte[]>();
            }

            using ( var stream = new MemoryStream() ) {
                using ( var writer = new BinaryWriter(stream) ) {
                    var sessionItem = (SessionStateItemCollection)item.Items;
                    sessionItem.Serialize(writer);
                }

                sessionData[id] = stream.GetBuffer();
            }

            HttpContext.Current.Application["session"] = sessionData;
        }

    }

ただ HttpContext.Application に保存するだけのものです。
(InProc セッションの超劣化版)

これから少しずつまともに使えるものにしていこう。



Access2000からSQLAzureには接続できない

clock January 4, 2012 09:20 by author Kawana

ある提案をするために
Access2000からSQLAzureには接続できないか調査しました。

結果:Access2000(おそらくOffice2000で)では接続できないようです。


Accessの公式ブログに以下の文章があります。
http://blogs.office.com/b/microsoft-access/archive/2010/06/07/access-2010-and-sql-azure.aspx
Access 2010 and SQL Azure
• ODBC connections are not supported in Access versions prior to Access 2010


またSQLOLEDBを使ったADOでの接続もできないようです。

Data Access Technologies Road Map
http://msdn.microsoft.com/en-us/library/ms810810.aspx
SQL Azure connectivity through the WDAC ODBC driver (SQLODBC) and the OLE DB provider (SQLOLEDB) is not supported.
WDAC is based on the tabular data stream (TDS) protocol client version 7.1, which was used in SQL Server 2000.
SQL Azure databases support TDS protocol client version 7.3 or later.
Use SQL Server Native Client instead of WDAC to connect to SQL Azure. See
General Guidelines and Limitations (SQL Azure Database) for more information.



Azure上でWAPPSCmdletsを利用2

clock December 21, 2011 16:14 by author tanaka

 

今回も前回同様Azure上でWAPPSCmdletsネタ

PowershellでWAPPSCmdletsを利用する場合
まず、WAPPSCmdletsをインストールしてあげないといけない

※インストール方法は、以下のブログを参考にさせていただきました。
http://blogs.gine.jp/kusa/archives/1645

それで、今回はAzure上でWAPPSCmdletsを利用できる
ようにするわけなので、Azure上の仮想サーバーにもインストールする
必要がある。

私の場合以下の方法で対応

WAPPSCmdletsはインストール時に、SnapinかModuleか選択できるから、
Moduleを選択

以下のDLLができる
Microsoft.WindowsAzure.Samples.ManagementTools.PowerShell.dll

これをデプロイ予定のAzureのプロジェクトで参照する

参照したら、プロパティでローカルコピーをTrueに
しておく、

image


binフォルダの下に出力されるようになるので、
AzureのStartupタスクを利用してこのdllを.NetFrameWorkに追加してあげる
(Azure上で、プロジェクトがデプロイされる箇所は、E:\approot下)

IF EXIST %SystemRoot%\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe %SystemRoot%\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe E:\approot\bin\Microsoft.WindowsAzure.Samples.ManagementTools.PowerShell.dll
IF EXIST %SystemRoot%\Microsoft.NET\Framework64\v2.0.50727\InstallUtil.exe %SystemRoot%\Microsoft.NET\Framework64\v2.0.50727\InstallUtil.exe E:\approot\bin\Microsoft.WindowsAzure.Samples.ManagementTools.PowerShell.dll

すると、Startupタスクで呼び出すようにしたPowershellのスクリプトに
Add-PsSnapin WAPPSCmdletsと記述すれば、Azure上で利用可能となった。

おわり



Azure上でWAPPSCmdletsを利用する。

clock December 16, 2011 20:30 by author tanaka

 

AzureをPowershellで管理する際に利用するWAPPSCmdlets

Azure上で利用する場合どうするのか?


コマンドレットを利用する場合は、以下の情報が必要
1.サービス名 → ホステッドサービスの名前
2.サブスクリプションID
3.サービス証明書

※サービス証明書は、対象ホステッドサービスの証明書を右クリックして追加

image 

$serviceName = "chargemeter" 
$subscriptionId = "サブスクリプションID" 
$mgmtCert = Get-Item cert:\CurrentUser\MY\<サムプリント>

※サムプリントは、ポータルサイトの管理証明書で確認できる。


Azure上でサービス証明書を取得する方法に悩んでいた。

ローカルで、コマンドレットを利用する場合
こんな風に取得していた。
Get-Item cert:\CurrentUser\MY\*


Azure上では取得できない、


正解は、
Get-Item cert:\LocalMachine\My\*

これで、Azure上の証明書を取得できました。



Sign In