WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

Linq to Entities で型の初期化

by WebSurfer 2018年4月30日 16:31

以下のコードを実行すると foreach (var item in list2) のところで System.NotSupportedException がスローされ、

1 つの LINQ to Entities クエリに含まれる構造的に互換性のない 2 つの初期化に、型 'ConsoleAppJoinByLinq2.JoinedList' が指定されています。1 つの型を同じクエリ内の 2 つの場所で初期化することはできますが、両方の場所で同じプロパティが同じ順序で設定されている必要があります。

・・・というエラーメッセージが表示されます。その理由と解決策を書きます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleAppJoinByLinq2
{
  public class JoinedList
  {
    public int ProductID { set; get; }
    public string ProductName { get; set; }
    public int CategoryID { get; set; }
    public string CategoryName { set; get; }
  }

  class Program
  {
    static void Main(string[] args)
    {
      NORTHWINDEntities db = new NORTHWINDEntities();

      var list1 = from c in db.Categories
                  where c.CategoryID == 1
                  select new JoinedList
                  {
                    CategoryID = c.CategoryID,
                    CategoryName = c.CategoryName
                  };

      var list2 = from c in list1
                  join p in db.Products
                  on c.CategoryID equals p.CategoryID
                  select new JoinedList
                  {
                    ProductID = p.ProductID,
                    ProductName = p.ProductName,
                    CategoryName = c.CategoryName
                  };

      foreach (var item in list2)
      {
        Console.WriteLine("{0}, {1}, {2}",
          item.ProductID, item.ProductName, item.CategoryName);
      }
    }
  }
}

上のコードでは、SQL Server のサンプルデータベース Northwind をベースに Visual Studio Communirt 2015 のウィザードを使って作った Entity Data Model (EDM) を使っています。以下の画像を見てください。

Entity Data Model

NORTHWINDEntities は EDM を作ると一緒に自動生成される DbContext クラスを継承したコンテキストクラスです。

Categories と Products は NORTHWINDEntities コンテキストクラスのプロパティで、データベースの当該テーブルを表すエンティティのコレクションを取得・設定するものです。

そして、肝心の話の何故エラーになるかの理由ですが、エラーメッセージの「両方の場所で同じプロパティが同じ順序で設定」という条件が満たされてない、即ち、list1 と list2 のクエリで JoinedList を初期化する際のプロパティの設定が異なるからです。

その前のエラーメッセージの条件「1 つの型を同じクエリ内の 2 つの場所で初期化」には該当しないように見えますが、list1 と list2 のクエリは両方 foreach のところで遅延評価されて、結局「同じクエリ内」ということになるようです。

解決策は、

  1. list1 のクエリに ToList() を適用する(遅延評価されないように)、または、
  2. JoinedList を初期化する際のプロパティの設定を、並び順序を含めて list1 / list2 のクエリで同じになるようにする

・・・です。そうすれば、以下の通り期待した結果が得られます。

実行結果

Tags:

ADO.NET

SQL Server 列の照合順序

by WebSurfer 2018年4月26日 19:27

自分の持っている本「一目でわかる Microsoft SQL Server 2008」によると、SQL Server の照合順序はサーバー、データベース、列、式に対して指定することができるそうです。(テーブルレベルでは指定できないようです)

そして、照合順序は変更できるのですが、既存のデータベースの照合順序を変更しても、そのテーブルの中の列の照合順序は元のままで、変更されないということは知ってました?

実は、自分は知らなかったです。おかげで 1 ~ 2 時間ハマってしまいました。(汗) また時間を無駄にすることがないように備忘録を残しておきます。

(1) データベースの照合順序

データベースの照合順序

データベースの照合順序は SQL Server Management Studio (SSMS) を使って表示・変更することができます。Transact-SQL を使っても可能です。詳しくは Microsoft の文書「照合順序情報の表示」を見てください。

上の画像は、SSMS で既存のデータベース TestDatabase を右クリックして[データベースのプロパティ]ダイアログを開き、[ページの選択]で[オプション]を選択したものです。[照合順序(C)]ボックスに現在の照合順序が表示されています。

この画面で、[照合順序(C)]ボックスのドロップダウンリストから他の照合順序を選んで、変更することができます。

現在はサーバーレベルでのデフォルトの照合順序を継承して Japanese_CI_AS になっていますが、試しにそれを Japanese_BIN2 に変更してみます。

(2) 列の照合順序

列の照合順序

上の画像は、上記 (1) で既存のデータベース TestDatabase の照合順序を Japanese_CI_AS から Japanese_BIN2 に変更した後の、テーブル dbo.T_TRADE の 列 USER_ID (varchar(10), NOT NULL) のプロパティを SSMS で見たものです。

列 USER_ID の照合順序は Japanese_CI_AS のまま変わってないのが分かるでしょうか。

(3) 列の照合順序の変更

列の照合順序の変更

列の照合順序の変更は Transact-SQL を使って可能です。詳しい方法は Microsoft の文書「列の照合順序の設定または変更」を見てください。

上の画像は、SSMS 上で Transact-SQL を使って、テーブル dbo.T_TRADE の 列 USER_ID の照合順序を Japanese_BIN2 に変更したところです。

(4) 変更後の列の照合順序

変更後の列の照合順序

上の画像の通り、上記 (3) の手順を実行した結果、テーブル dbo.T_TRADE の 列 USER_ID の照合順序が Japanese_BIN2 に変更されています。


以上、既存のデーターベースの照合順序を変更しても意味はなくて、上記の操作をして列の照合順序を変えないとダメという話でした。

先の記事「SQL Server の Order By での濁音の扱い」は order by 句による並び順の話でしたが、主キーの場合は Japanese_CI_AS では濁音有り無しを区別しないことにより制約違反になることがあるということで、注意が必要です。

Tags:

SQL Server

ASP.NET 4.5 スクリプト マッピング

by WebSurfer 2018年4月24日 15:58

先の記事「ASP.NET 4.5 ScriptManager」で "テンプレートで「空」を選択した場合は、最低でも jQuery, MSAjax, WebForms の 3 つの NuGet パッケージはインストールして、ScriptManager に登録し、全てのページに ScriptManager を配置するのがよさそうです" と書きました。その手順を備忘録として残しておきます。

「空」のテンプレート

まず、「空」のテンプレートというのは何かですが、上の画像のように Visual Studio で Web アプリケーションを作成する際に「空」を選択して Web Forms にチェックを入れたものです。(画像は Visual Studio Community 2015 のもの)

上の設定で作ったアプリケーションには、先の記事で書いたような ScriptManager へのスクリプトマッピング等は一切含まれず、自力で NuGet パッケージをダウンロードして必要な設定することになります。

その手順は以下の通りです。

(1) jQuery, MSAjax, WebForms の NuGet パッケージ

jQuery, MSAjax, WebForms の NuGet

Visual Studio の[ツール(T)]⇒[NuGet パッケージマネージャー(N)]⇒[ソリューションの NuGet パッケージの管理(N)...]と進んで管理画面を開き、以下の 3 つの NuGet パッケージをインストールします。(もし Bootstrap 等も必要でしたら追加してください)

  • AspNet.ScriptManager.jQuery
  • Microsoft.AspNet.ScriptManager.MsAjax
  • Microsoft.AspNet.ScriptManager.WebForms

上の画像はインストール後のもので、バージョンはこの記事を書いた時点で最新のものです。

NuGet パッケージのインストールが完了すると、下の画像の通り JavaScript ファイルが Scripts フォルダ下にインストールされます。

スクリプトファイル

その他、ASP.NET 4.5 ScriptManager Improvements in WebForms に書いてありますが、スクリプトマッピングのためのコードが PreApplicationStart メソッドに追加されるそうです。

追加されているのを目で見て確認する方法は分かりませんが、ScriptManaget に以下の設定をすれば jQuery 3.3.1 のスクリプトファイルへの参照は正しくレンダリングされるようになりますので、間違いなく追加されていると思います。

<asp:ScriptManager runat="server">
  <Scripts>
    <asp:ScriptReference Name="jquery" />
  </Scripts>
</asp:ScriptManager>

ただし、MsAjax と WebForms の方は NuGet パーッケージのインストールだけでは不足で、Web.Optimization 関係の NuGet パッケージのインストール、バンドル定義の作成とそれの登録が追加で必要になります。

(2) Web.Optimization の NuGet パッケージ

 の NuGet

上記 (1) の手順と同様に Visual Studio で NuGet パッケージの管理管理画面を開き、以下の 2 つの NuGet パッケージをインストールします。上の画像はインストール後のもので、バージョンはこの記事を書いた時点で最新のものです。

  • Microsoft.AspNet.Web.Optimization
  • Microsoft.AspNet.Web.Optimization.WebForms

これらの NuGet パッケージのインストールはバンドル定義を行うために必要です。

(3) バンドル定義と登録

「Web フォーム」のテンプレートを使って作成すると自動生成される App_Start フォルダの BundleConfig.cs のコードを参考にバンドル定義を作成します。

参考と言っても、そのままコピペして不要な部分を削除するだけですが。コードは以下のようになります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Optimization;
using System.Web.UI;

namespace WebFormsEmpty
{
  public class BundleConfig
  {
    public static void RegisterBundles(BundleCollection bundles)
    {
      bundles.Add(new ScriptBundle("~/bundles/WebFormsJs").
      Include("~/Scripts/WebForms/WebForms.js",
              "~/Scripts/WebForms/WebUIValidation.js",
              "~/Scripts/WebForms/MenuStandards.js",
              "~/Scripts/WebForms/Focus.js",
              "~/Scripts/WebForms/GridView.js",
              "~/Scripts/WebForms/DetailsView.js",
              "~/Scripts/WebForms/TreeView.js",
              "~/Scripts/WebForms/WebParts.js"));

      bundles.Add(new ScriptBundle("~/bundles/MsAjaxJs").
      Include(
      "~/Scripts/WebForms/MsAjax/MicrosoftAjax.js",
      "~/Scripts/WebForms/MsAjax/" + 
                      "MicrosoftAjaxApplicationServices.js",
      "~/Scripts/WebForms/MsAjax/MicrosoftAjaxTimer.js",
      "~/Scripts/WebForms/MsAjax/MicrosoftAjaxWebForms.js"));
    }
  }
}

上記のバンドル定義を Global.asax の Application_Start メソッドで登録します。コードは以下のようになります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
using System.Web.Optimization;

namespace WebFormsEmpty
{
  public class Global : System.Web.HttpApplication
  {
    protected void Application_Start(object sender, 
                                           EventArgs e)
    {
      BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
  }
}

(4) ScriptManager への登録

「Web フォーム」のテンプレートを使って作成すると自動生成されるマスターページ Site.Master の ScriptManager のコードを参考に、クライアントにレンダリングされるスクリプトを登録します。

そのままコピペして、今回は使わない bootstrap, respond の部分を削除するだけです。コードは以下のようになります。

<asp:ScriptManager runat="server">
  <Scripts>
    <asp:ScriptReference Name="MsAjaxBundle" />
    <asp:ScriptReference Name="jquery" />
    <asp:ScriptReference Name="WebForms.js" 
        Assembly="System.Web" 
        Path="~/Scripts/WebForms/WebForms.js" />
    <asp:ScriptReference Name="WebUIValidation.js" 
        Assembly="System.Web" 
        Path="~/Scripts/WebForms/WebUIValidation.js" />
    <asp:ScriptReference Name="MenuStandards.js" 
        Assembly="System.Web" 
        Path="~/Scripts/WebForms/MenuStandards.js" />
    <asp:ScriptReference Name="GridView.js" 
        Assembly="System.Web" 
        Path="~/Scripts/WebForms/GridView.js" />
    <asp:ScriptReference Name="DetailsView.js" 
        Assembly="System.Web" 
        Path="~/Scripts/WebForms/DetailsView.js" />
    <asp:ScriptReference Name="TreeView.js" 
        Assembly="System.Web" 
        Path="~/Scripts/WebForms/TreeView.js" />
    <asp:ScriptReference Name="WebParts.js" 
        Assembly="System.Web" 
        Path="~/Scripts/WebForms/WebParts.js" />
    <asp:ScriptReference Name="Focus.js" 
        Assembly="System.Web" 
        Path="~/Scripts/WebForms/Focus.js" />
    <asp:ScriptReference Name="WebFormsBundle" />
  </Scripts>
</asp:ScriptManager>

上のコードで Assembly と Path を指定した ScriptReference がありますが、それらは ASP.NET 4.5 ScriptManager Improvements in WebForms に書いてありありますように、deduping(重複排除)のための "special arrangement"だそうです。(どのような仕組みで排除されるのかは分かりませんが)

結果、上記 (3) のバンドル定義に従ってバンドルされたスクリプト定義への参照と、jQuery 3.3.1 のスクリプトファイルへの参照が、正しい順序でクライアントにレンダリングされます。

<script src="/bundles/MsAjaxJs?v=c42ygB2...></script>
<script src="Scripts/jquery-3.3.1.js" .....></script>
<script src="/bundles/WebFormsJs?v=AAyiA...></script>

Tags: ,

ASP.NET

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  2018年8月  >>
2930311234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar