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

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

ただいまの
回答率

87.59%

ASP.NET MVCのモデルのプロパティに、クラスを使用したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 3,890

score 35

フレームワーク: MVC Core v2.1.4
以下のようなコードで、クラス(Person)を使用したモデル(Member)を作成したいと思ったのですが

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

public class Member
{
    public int Id { get; set; }
    public Person Person { get; set; }
    public Person Child { get; set; }
}

スキャフォールディングをおこなうと、データベースにはMemberテーブルとPersonテーブルが作成されます。
以下の表のように、Memberテーブルのみ作成したい(Personテーブルは作成したくない)のですが、どのようにすればよろしいでしょうか?

Id Person.Name Person.Age Child.Name Child.Age
1 一郎 30 太郎 11
2 次郎 40 二郎 12

 追記

PersonのIdは不要です。Idをなしにするとスキャフォールディングがエラーになってしまうため追加しています。
スキャフォールディングは、ModelをベースにController / View のソースを自動生成するという意味で使っています。
(ソリューションエクスプローラー → 追加 → コントローラー → Entity Frameworkを使用したビューがあるMVCコントローラー からおこなっています)

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • haru853

    2018/01/29 16:45

    ModelをベースにController / View のソースを自動生成するという意味で使っています。ソリューションエクスプローラー → 追加 → コントローラー → Entity Frameworkを使用したビューがあるMVCコントローラー からおこなっています。

    キャンセル

  • SurferOnWww

    2018/01/29 16:48

    > 出来ればCoreで使用可能な方法を知りたいです。 ← 「出来れば」というのでは困ります。.NET Framework ベースで回答したのに、やっぱり Core ベースでないとダメと言われては(多分そうなると思います)、回答者の労力と時間が無駄になりますので。Core ベースの質問なら、タグは ASP.NET に変更して、質問文の一番最初に MVC Core であることとそのバージョンを明記してください。

    キャンセル

  • haru853

    2018/01/29 17:10

    失礼しました。アプリケーション全体の作成がCoreでは不可能だった場合は、.NET Frameworkで作成しようと考えている為、「出来れば」と記載してしまっていました。まぎらわしいのでCoreに限定させていただきました。

    キャンセル

回答 1

checkベストアンサー

+2

EF Code First で Model の Member, Person, Child クラスをベースに DB にテーブルを生成してそれらを使うのですよね?

Model の Member クラスにナビゲーションプロパティを定義して(今は定義されてないように見えます)、それをたどって Person.Name, Person.Age, Child.Name, Child.Age を取得することを検討されてはいかがでしょう?

【2018/1/31 12:00 追記】

下の私の 2018/01/31 10:50 のコメントで「コメント欄には書ききれないので回答欄に追記します」と書きましたが、それを以下に書きます。

質問者さんの DB の詳細は回答者にとっては不明ですので、誰でも入手できる Microsoft のサンプルデータベース Northwind をベースに話をしたいと思います。

Customers, Employees, Suppliers テーブルにある Address, City, Region, PostalCode, Country は各テーブル固有の情報(住所が個人個人で違うのは当たり前)なので、これらの情報を抜き出して別テーブルにまとめて正規化(?)するというようなことは無意味というよりやるべきではない・・・という認識は質問者さんも自分も一致していると理解しています。

で、質問者さんのやりたいことは、DB に別テーブルを作るという訳ではなく、

Address・City・Region・PostalCode・Country をクラスなどにまとめてスマートに記述する

ということですが、Customers, Employees, Suppliers テーブルのレベルで考えて、それらからデータを取得する場合は、そういう方法はないと思います。

全テーブルの Address, City, Region, PostalCode, Country データを List<T> 型のオブジェクトなどにまとめることは可能ですが、無理にやってもデメリットばかりで何の意味もない(少なくともスマートに利用できる方法はない)と思います。

意味があるのは Orders テーブルなどの一つ上のレベルのテーブルです。Orders テーブルは正規化されていて、EDM を作れば Customers, Employees, Suppliers クラスが自動生成されて利用できます。

VS2015 で .NET Framework ベースて作った EDM の Orders クラスは以下のようになります。Customers, Employees というナビゲーションプロパティが定義されているところに注目してください。上の私レスで提案したのはこれを使うことです。

namespace Mvc5App
{
    using System;
    using System.Collections.Generic;

    public partial class Orders
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public Orders()
        {
            this.Order_Details = new HashSet<Order_Details>();
        }

        public int OrderID { get; set; }
        public string CustomerID { get; set; }
        public Nullable<int> EmployeeID { get; set; }
        public Nullable<System.DateTime> OrderDate { get; set; }
        public Nullable<System.DateTime> RequiredDate { get; set; }
        public Nullable<System.DateTime> ShippedDate { get; set; }
        public Nullable<int> ShipVia { get; set; }
        public Nullable<decimal> Freight { get; set; }
        public string ShipName { get; set; }
        public string ShipAddress { get; set; }
        public string ShipCity { get; set; }
        public string ShipRegion { get; set; }
        public string ShipPostalCode { get; set; }
        public string ShipCountry { get; set; }

        public virtual Customers Customers { get; set; }
        public virtual Employees Employees { get; set; }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<Order_Details> Order_Details { get; set; }
        public virtual Shippers Shippers { get; set; }
    }
}

例えば、質問者さんの質問の例にならって、Orders の OrderID, Customers の City と Country, Employees の City と Country の一覧を取得したい場合は、Orders クラスには、Customers と Employees というナビゲーションプロパティが定義されているので、以下のようにできます。個人的には、これが Entity Framework と Ling to Entity の機能を最大限活用した最もスマートな例だと思います。

(注)Include() というメソッドは指定したプロパティを先読みするためのものです。無くても結果は同じですが、無いと遅延評価されるのでクエリを 2 度、3 度投げることになります。質問者さんのアップされたコードではナビゲーションプロパティに virtual が付与されないようなので、話が違うかも。(未検証・未確認です)

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

namespace ConsoleAppJoinByLinq
{

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

            var orders2 = db.Orders.Include(c => c.Customers).Include(e => e.Employees);

            foreach (var item in orders2)
            {
                Console.WriteLine("OrderID: {0}, CustomerCity: {1}, CustomerCountry: {2}, EmployeeCity: {3}, EmployeeCountry: {4}",
                    item.OrderID, item.Customers.City, item.Customers.Country, item.Employees.City, item.Employees.Country);
            }
        }
    }
}

ナビゲーションプロパティが定義されてない場合は以下のように join して同じ結果が得られます。

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

namespace ConsoleAppJoinByLinq
{
    public class OrderCustomerEmployee
    {
        public int OrderID { get; set; }
        public string CustomerCity { get; set; }
        public string CustomerDountry { get; set; }
        public string EmployeeCity { get; set; }
        public string EnployeeCountry { get; set; }
    }

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

            var orders = from o in db.Orders
                         join c in db.Customers
                         on o.CustomerID equals c.CustomerID
                         join e in db.Employees
                         on o.EmployeeID equals e.EmployeeID
                         select new OrderCustomerEmployee
                         {
                             OrderID = o.OrderID,
                             CustomerCity = c.City,
                             CustomerDountry = c.Country,
                             EmployeeCity = e.City,
                             EnployeeCountry = e.Country
                         };

            foreach (var item in orders)
            {
                Console.WriteLine("OrderID: {0}, CompanyName: {1}, ContactName: {2}, FirstName: {3}, LastName: {4}",
                    item.OrderID, item.CustomerCity, item.CustomerDountry, item.EmployeeCity, item.EnployeeCountry);
            }
        }
    }
}

ひょっとして、上のコードの OrderCustomerEmployee クラスが質問者さんの言うまとめるためのクラスなのでしょうか?

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/01/31 09:52

    EDMの作り方がわからなかった為、以下の手順でNorthwindからコードを生成してみました。
    https://docs.microsoft.com/en-us/ef/core/get-started/aspnetcore/existing-db
    こちらでもよろしいでしょうか?
    EDMでないとダメでしたら再度調べますのでおっしゃってください。

    public partial class Customers
    {
    public Customers()
    {
    CustomerCustomerDemo = new HashSet<CustomerCustomerDemo>();
    Orders = new HashSet<Orders>();
    }

    public string CustomerId { get; set; }
    public string CompanyName { get; set; }
    public string ContactName { get; set; }
    public string ContactTitle { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string Region { get; set; }
    public string PostalCode { get; set; }
    public string Country { get; set; }
    public string Phone { get; set; }
    public string Fax { get; set; }

    public ICollection<CustomerCustomerDemo> CustomerCustomerDemo { get; set; }
    public ICollection<Orders> Orders { get; set; }
    }

    public partial class Employees
    {
    public Employees()
    {
    EmployeeTerritories = new HashSet<EmployeeTerritories>();
    InverseReportsToNavigation = new HashSet<Employees>();
    Orders = new HashSet<Orders>();
    }

    public int EmployeeId { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public string Title { get; set; }
    public string TitleOfCourtesy { get; set; }
    public DateTime? BirthDate { get; set; }
    public DateTime? HireDate { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string Region { get; set; }
    public string PostalCode { get; set; }
    public string Country { get; set; }
    public string HomePhone { get; set; }
    public string Extension { get; set; }
    public byte[] Photo { get; set; }
    public string Notes { get; set; }
    public int? ReportsTo { get; set; }
    public string PhotoPath { get; set; }

    public Employees ReportsToNavigation { get; set; }
    public ICollection<EmployeeTerritories> EmployeeTerritories { get; set; }
    public ICollection<Employees> InverseReportsToNavigation { get; set; }
    public ICollection<Orders> Orders { get; set; }
    }

    public partial class Suppliers
    {
    public Suppliers()
    {
    Products = new HashSet<Products>();
    }

    public int SupplierId { get; set; }
    public string CompanyName { get; set; }
    public string ContactName { get; set; }
    public string ContactTitle { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string Region { get; set; }
    public string PostalCode { get; set; }
    public string Country { get; set; }
    public string Phone { get; set; }
    public string Fax { get; set; }
    public string HomePage { get; set; }

    public ICollection<Products> Products { get; set; }
    }

    public partial class NorthwindContext : DbContext
    {
    public virtual DbSet<Categories> Categories { get; set; }
    public virtual DbSet<CustomerCustomerDemo> CustomerCustomerDemo { get; set; }
    public virtual DbSet<CustomerDemographics> CustomerDemographics { get; set; }
    public virtual DbSet<Customers> Customers { get; set; }
    public virtual DbSet<Employees> Employees { get; set; }
    public virtual DbSet<EmployeeTerritories> EmployeeTerritories { get; set; }
    public virtual DbSet<OrderDetails> OrderDetails { get; set; }
    public virtual DbSet<Orders> Orders { get; set; }
    public virtual DbSet<Products> Products { get; set; }
    public virtual DbSet<Region> Region { get; set; }
    public virtual DbSet<Shippers> Shippers { get; set; }
    public virtual DbSet<Suppliers> Suppliers { get; set; }
    public virtual DbSet<Territories> Territories { get; set; }
    ...

    キャンセル

  • 2018/01/31 10:50

    > こちらでもよろしいでしょうか?

    すみませんが VS2017 は持っていないので確認できないです。

    でも、質問者さんがアップされた DbContext クラスや DB のテーブルを表すクラスは、VS2015 で .NET Framework ベースで作ったものと微妙に違うものの(ナビゲーションプロパティに virtual が付与されてないところ)ほぼ同じです。

    なので、機能的には同じ&基本的なところが違うということはまずありえないであろうと仮定してレスします。

    コメント欄には書ききれないので回答欄に追記します。

    キャンセル

  • 2018/01/31 14:02

    回答ありがとうございます。

    > で、質問者さんのやりたいことは、DB に別テーブルを作るという訳ではなく、
    > Address・City・Region・PostalCode・Country をクラスなどにまとめてスマートに記述する
    > ということですが、Customers, Employees, Suppliers テーブルのレベルで考えて、それらからデータを取得する場合は、そういう方法はないと思います。

    まさしくこれが質問でお聞きしたかった事です。

    > ひょっとして、上のコードの OrderCustomerEmployee クラスが質問者さんの言うまとめるためのクラスなのでしょうか?

    これは違います。(使うときではなく、EF Code Firstのモデルの一部のプロパティをまとめたかった)
    また、ナビゲーションプロパティについては、MVC5も使用した経験がありますので理解しています。
    virtualの有無については、現状のEF Coreは遅延ローディングがサポートされていませんので(議論はされているようですが、Microsoftの方針としてはサポートしそうもない?)付けても付けなくても変わらないのではないかと思います。

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

    キャンセル

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

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

関連した質問

同じタグがついた質問を見る