質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.03%

GridViewへの表示 C# WPF Entity Framework

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 313

tappuri

score 1

前提・実現したいこと

C# Entiry FrameworkコードファーストでWPFアプリを作成しています。
DataGridにデータを表示する際、結合しているテーブルのデータがクラス名?で表示されます。
中身のデータが表示されるようにできませんか?

そもそもDBの設計時点、データの挿入時点でよくないところがあれば指摘してください。

発生している問題・エラーメッセージ

イメージ説明
イメージ説明

該当のソースコード

<Window x:Class="WpfAppTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfAppTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="306.505" Width="408.546">
    <Grid>
        <Button x:Name="btnSelect" Content="Select" HorizontalAlignment="Left" Margin="10,45,0,0" VerticalAlignment="Top" Width="75" Click="BtnSelect_Click"/>
        <Button x:Name="btnInsert" Content="Insert" HorizontalAlignment="Left" Margin="90,45,0,0" VerticalAlignment="Top" Width="75" Click="BtnInsert_Click"/>
        <DataGrid x:Name="dgv" HorizontalAlignment="Left" Height="186" Margin="10,70,0,0" VerticalAlignment="Top" Width="361"/>

    </Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using System.Data.Entity;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace WpfAppTest
{
    public class Product
    {
        [Key]
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Maker { get; set; }
        public ProductCategory Category { get; set; }
    }

    public class ProductCategory
    {
        [Key]
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }
    }
    public class StockContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
        public DbSet<ProductCategory> ProductCategories { get; set; }
    }

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void BtnSelect_Click(object sender, RoutedEventArgs e)
        {
            using (var context = new StockContext())
            {
                var query = context.Products
                    .Include(a => a.Category)
                    .ToArray();

                dgv.ItemsSource = query;
            }
        }

        private void BtnInsert_Click(object sender, RoutedEventArgs e)
        {
            using (var context = new StockContext())
            {
                context.Products.Add(new Product
                {
                    Name = "TestProduct",
                    Maker = "TestMaker",
                    Category = new ProductCategory { CategoryName = "TestCategory" }
                });
                context.SaveChanges();
            }
        }
    }
}

試したこと

下記サイトを参考に作成しています。
https://densan-labs.net/tech/codefirst/chapter4.html

補足情報(FW/ツールのバージョンなど)

Microsoft Visual Studio Community 2019
Entity Framework 6.2.0

8/3 16:10 追記

SurferOnWwwさんからご回答頂いた内容の通り、SelectedDataクラスを追加してBtnSelect_Clickの内容を修正したところ正常にDataGridへ値を読みだせました。

    public class SelectedData
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Maker { set; get; }
        public string CategoryName { set; get; }
    }
        private void BtnSelect_Click(object sender, RoutedEventArgs e)
        {
            using (var context = new StockContext())
            {
                var query = from p in context.Products.Include(a => a.Category)
                            select new SelectedData
                            {
                                ProductID = p.ProductID,
                                Name = p.Name,
                                Maker = p.Maker,
                                CategoryName = p.Category.CategoryName
                            };

                dgv.ItemsSource = query.ToArray();
            }
        }

また、Entity Framework 6 Power Tools Community Editionによるグラフ化、DB生成のSQL文の確認ができました。
イメージ説明

create table [dbo].[Products] (
    [ProductID] [int] not null identity,
    [Name] [nvarchar](max) null,
    [Maker] [nvarchar](max) null,
    [Category_CategoryID] [int] null,
    primary key ([ProductID])
);
create table [dbo].[ProductCategories] (
    [CategoryID] [int] not null identity,
    [CategoryName] [nvarchar](max) null,
    primary key ([CategoryID])
);
alter table [dbo].[Products] add constraint [Product_Category] foreign key ([Category_CategoryID]) references [dbo].[ProductCategories]([CategoryID]);


いつもSSMSのデザイナからテーブルへの変更を加えていたのでER図?やSQL文を見るのはほぼ初めてですが、私が見たところ想定通りのDBになっているように思います。

しかしまだ気になる部分があります。
1つ目はEntity Framework, コードファーストというかっこよさげなものを使っていながらInsertとSelectでクラスを使い分けないといけない事です。これは、public ProductCategory Category { get; set; }のような書き方をやめて[ForeignKey()]などで代用して設計しなおすことで解消できるのでしょうか?(そもそもデータベースファーストを使用しろという話なのでしょうか)

2つ目はInsertしたときの挙動です。先程、想定通りのDBになっていると言ったもののProductCategiesテーブルをSSMSから確認するとCategoryNameに重複した値が書き込まれてしまっています。Entity Frameworkが勝手に判断してくれるということは無く、SQL同様に親テーブルにデータを入れておき、子テーブルに書き込むときに親テーブルのIDを使用するしかないでしょうか?

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • hihijiji

    2019/08/03 11:10

    Entiry Frameworkはバージョンによって動作が大きく異なるので、バージョンは明記してください。

    キャンセル

  • tappuri

    2019/08/03 13:45

    SurferOnWwwさん
    コメントありがとうございます。
    仰る通り1枚目の画像はBtnInsert_Clickを押した結果をSSMSから確認したものです。
    外部結合で値「TestCategory」が取得できたのでこれをDateGridに表示できれば成功と思ったのです。

    >表示したいのは ProductCategory.CategoryName だと思いますが、であればそれを取得しないと期待通りにはなりません。

    Insertには成功したがSelectに失敗しているという認識でよろしいでしょうか?

    virtual、.includeに関しては遅延読み込みなどを理解せずに、とりあえず書き込めて読み込みたからOKという考えで進めていたため意識できていませんでした。.IncludeはSQLでいうLEFT JOINと同等の感覚で使用していました。
    また、回答の方にも情報を頂きありがとうございます。
    これから頂いた内容を試してみます。

    hihijijiさん
    コメントありがとうございます。
    Enriry Frameworkのバージョンは6.2.0です。

    キャンセル

  • SurferOnWww

    2019/08/03 16:55

    > しかしまだ気になる部分があります

    その話は、元々のこのスレッドの主題とはズレてきますので、別に新しいスレッドを立てて質問するようにしてください。後で検索などでここを訪れた人にとっては、一つの主題に対する Q&A にとどめておくのが、ここのようなノウハウの蓄積も目的としているサイトとしてあるべき姿と思いますので。

    ちょっとだけ書くと、そもそも Linq では SELECT しかできません。DB の編集を行うために Entity Framework 上で必要な操作は、対象となるレコードのエンティティオブジェクトの状態を、INSERT するなら Added、UPDATE するなら Modified、DEETE するなら Deleted としてマークし、DbContext.SaveChanges メソッドを適用する他ないです。(質問者さんのコードもそうしています)

    エンティティの状態の操作
    https://docs.microsoft.com/ja-jp/ef/ef6/saving/change-tracking/entity-state

    上記を読んでもまだ疑問があれば、別に新しいスレッドを立てて質問するようにしてください。このスレッドはクローズしてください。

    キャンセル

回答 1

checkベストアンサー

+2

上の質問に対するコメントに書いたことですが、具体的な修正案も追加して回答欄に書いておきます。

まず、一枚目の画像は BtnInsert_Click の結果 LocalDB にデータベースとテーブルが生成され、それから画像のクエリでデータを取得した結果と理解します。

その理解が正しければ、BtnSelect_Click のクエリがダメということになるはずです。.Include したのは ProductCatrgory オブジェクトなので 2 枚目の画像にはそれが表示されているということでしょう。(例外が出ないのがちょっと不思議ですが)

表示したいのは ProductCategory.CategoryName だと思いますが、であればそれを取得しないと期待通りにはなりません。以下のようにしてみてください。

まず、クエリの結果を格納するため以下のようなクラスクラスとプロパティ定義を追加する。プロパティの型は想像で書いていますので、実際と違ったら直してください。

public class SelectedData
{
    public int ProductID { get; set; }
    public string Name { get; set; }
    public string Maker { set; get; }
    public string CategoryName { set; get; }
}

BtnSelect_Click メソッドのクエリ式は以下のようにする。

var query = from p in context.Products.Include(a => a.Category)
            select new SelectedData
            {
                ProductID = p.ProductID,
                Name = p.Name,
                Maker = p.Maker,
                CategoryName = p.Category.CategoryName
            };

お試しください。

ナビゲーションプロパティへの virtual を付与する件ですが、遅延ローディングが必要(普通は必要)なら virtual を付与する必要があります。

遅延ローディングされては無駄なクエリが複数回投げられるという場合、今回のケースのように .Include を使うということになります。

詳しくは以下の記事の "Learn how to load related data" を読んでください。

Tutorial: Read related data with EF in an ASP.NET MVC app
https://docs.microsoft.com/ja-jp/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application

【追加情報】

今回の問題とは関係ないオマケ的な話ですが・・・

DB First で .edmx を作った時と同様に、Code First の DbContextから EDM をグラフィカルに表示するツールが利用できます。大変有用なので入手してインストールすることをお勧めします。

Entity Framework 6 Power Tools Community Edition
https://marketplace.visualstudio.com/items?itemName=ErikEJ.EntityFramework6PowerToolsCommunityEdition

イメージ説明

回答欄の「8/3 16:10 追記」の件

しかしまだ気になる部分があります

その話は、元々のこのスレッドの主題とはズレてきますので、別に新しいスレッドを立てて質問するようにしてください。後で検索などでここを訪れた人にとっては、一つの主題に対する Q&A にとどめておくのが、ここのようなノウハウの蓄積も目的としているサイトとしてあるべき姿と思いますので。

ちょっとだけ書くと、そもそも Linq では SELECT しかできません。DB の編集を行うために Entity Framework 上で必要な操作は、対象となるレコードのエンティティオブジェクトの状態を、INSERT するなら Added、UPDATE するなら Modified、DELETE するなら Deleted としてマークし、DbContext.SaveChanges メソッドを適用する他ないです。(質問者さんのコードもそうしています)

エンティティの状態の操作
https://docs.microsoft.com/ja-jp/ef/ef6/saving/change-tracking/entity-state

上記を読んでもまだ疑問があれば、別に新しいスレッドを立てて質問するようにしてください。このスレッドはクローズしてください。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/08/03 17:58

    更に回答ありがとうございます。
    この質問はSurferOnWwwさんの回答をもって解決、クローズと致します。
    LINQやその構文については別途質問する予定です。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.03%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる