1

私はこの質問を何日も調査しましたが、私が満足している選択肢を見つけることができないようです。ただし、非常によく似た質問へのリンクは次のとおりです。

計算フィールドをモデルに追加

最終的には同じ質問がありますが、より良い解決策を望んでいます。

次のDBテーブルを検討してください。

CREATE TABLE [Contact](
[ContactID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[ContactName] [varchar](80) NOT NULL,
[Email] [varchar](80) NOT NULL,
[Title] [varchar](120) NOT NULL,
[Address1] [varchar](80) NOT NULL,
[Address2] [varchar](80) NOT NULL,
[City] [varchar](80) NOT NULL,
[State_Province] [varchar](50) NOT NULL,
[ZIP_PostalCode] [varchar](30) NOT NULL,
[Country] [varchar](50) NOT NULL,
[OfficePhone] [varchar](30) NOT NULL,
[MobilePhone] [varchar](30) NOT NULL)

CREATE TABLE [Blog](
[BlogID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[BlogName] [varchar](80) NOT NULL,
    [CreatedByID] [int] NOT NULL,  -- FK to ContactTable
    [ModifiedByID] [int] NOT NULL  -- FK to ContactTable
)

CREATE TABLE [Post](
[PostID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
    [BlogID] [int] NOT NULL, -- FK to BlogTable
[Entry] [varchar](8000) NOT NULL,
    [CreatedByID] [int] NOT NULL,  -- FK to ContactTable
    [ModifiedByID] [int] NOT NULL  -- FK to ContactTable
)

ここで、「一般的な」ルックアップ/計算された情報をロードするためにビューを使用したいと思います。サイトに投稿を表示するたびに、投稿を作成した人と最後に変更した人の名前を知りたいと思います。これらは、postテーブルとは別のテーブルに格納される2つのフィールドです。次の構文を簡単に使用できます(遅延/熱心な読み込みが適用され、CreatedByがCreatedByIDに基づくContactタイプのプロパティであると仮定):currentPost.CreatedBy.Name;

このアプローチの問題は、Db呼び出しの数と、連絡のために取得された大きなレコードですが、この状況ではName 99%しか使用していません。上記のDBスキーマは小さいと思いますが、これは単純化された例であり、実際の連絡先テーブルには約50のフィールドがあります。

過去(EFを使用する前)にこのタイプの状況を管理するために、私は通常、使用するテーブルの「詳細」ビューを作成しました。「詳細」ビューには一般的なルックアップ/計算フィールドが含まれているため、DBを1回呼び出すだけで、必要なすべての情報を効率的に取得できます(注:SQLビューのインデックスを使用して、これを非常に効率的に読み取ることができます)。私が一般的に使用するビューのリスト(関連するテーブルからの「ルックアップ」フィールドが含まれるため):

ALTER VIEW [icoprod].[BlogDetail]
AS
SELECT  B.[BlogID], 
    B.[BlogName], 
    B.[BlogDescription],
    B.[CreatedByID], 
    B.[ModifiedByID],
    CREATEDBY.[ContactName] AS CreatedByName, 
    MODIFIEDBY.[ContactName] AS ModifiedByName,
    (SELECT COUNT(*) FROM Post P WHERE P.BlogID = B.BlogID) AS PostCount
FROM    Blog AS B 
JOIN Contact AS CREATEDBY ON B.CreatedByID = CREATEDBY.ContactID 
JOIN Contact AS MODIFIEDBY ON B.ModifiedByID = MODIFIEDBY.ContactID

ALTER VIEW [icoprod].[PostDetail]
AS
SELECT  P.[PostID], 
    P.[BlogID],
    P.[Entry], 
    P.[CreatedByID], 
    P.[ModifiedByID],
    CREATEDBY.[ContactName] AS CreatedByName, 
    MODIFIEDBY.[ContactName] AS ModifiedByName,
    B.Name AS BlogName
FROM    Post AS P
JOIN Contact AS CREATEDBY ON P.CreatedByID = CREATEDBY.ContactID 
JOIN Contact AS MODIFIEDBY ON P.ModifiedByID = MODIFIEDBY.ContactID
JOIN Blog AS B ON B.BlogID = P.BlogID

これが私の「POCO」オブジェクトの概要です。

public class Blog
{
    public int ID { get; set; }
    public string Name { get; set; }

    public int CreatedByID { get; set; }
    public DateTime ModifiedByID { get; set; }
}

public class Post
{
    public int ID { get; set; }
    public string Name { get; set; }

    public int CreatedByID { get; set; }
    public DateTime ModifiedByID { get; set; }
}

public class Contact
{
    public int ID { get; set; }
    public string Name { get; set; }

    public string Email { get; set; }
    public string Title { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string MobilePhone { get; set; }
}

public class BlogDetails : Blog
{
    public string CreatedByName { get; set; }
    public string ModifiedByName { get; set; }
    public int PostsCount { get; set; }
}

public class PostDetails : Post
{
    public string CreatedByName { get; set; }
    public string ModifiedByName { get; set; }
    public string BlogName { get; set; }
}

このアプローチが好きな理由は、テーブルまたはビューに基づいてデータベースから情報を取得できることと、ビューをロードすると、ビューにすべての「テーブル」情報が含まれているため、ビューからロードして保存することができるためです。テーブル。IMO、これは私に両方の長所を与えてくれます。

私は過去にこのアプローチを使用しましたが、通常は、データ行またはストアドプロシージャからの情報を使用して、DBから情報をロードするか、DBからロードした後に亜音速のactiverecordパターンとマップされたフィールドを使用しました。EFで何かを実行して、別の抽象化レイヤーを作成せずにこれらのオブジェクトをロードできるようになることを本当に望んでいます。

これが私が構成に使用しようとしたものです(Fluent APIとコードファーストEFを使用):

public class PostConfiguration : EntityTypeConfiguration<Post>
{
    public PostConfiguration()
        : base()
    {
        HasKey(obj => obj.ID);

        Property(obj => obj.ID).
            HasColumnName("PostID").
            HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).
            IsRequired();

        Map(m =>
            {
                m.ToTable("Post");
            });
    }
}

public class BlogConfiguration : EntityTypeConfiguration<Blog>
{
    public BlogConfiguration()
        : base()
    {
        HasKey(obj => obj.ID);

        Property(obj => obj.ID).
            HasColumnName("BlogID").
            HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).
            IsRequired();

        Map(m =>
            {
                m.ToTable("Blog");
            });
    }
}

public class ContactConfiguration : EntityTypeConfiguration<Contact>
{
    public ContactConfiguration()
        : base()
    {
        HasKey(obj => obj.ID);

        Property(obj => obj.ID).
            HasColumnName("ContactID").
            HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).
            IsRequired();

        Map(m =>
            {
                m.ToTable("Contact");
            });
    }
}

public class PostDetailsConfiguration : EntityTypeConfiguration<PostDetails>
{

    public PostDetailsConfiguration()
        : base()
    {

        Map(m =>
            {
                m.MapInheritedProperties();
                m.ToTable("icoprod.PostDetails");
            });

    }

}

public class BlogDetailsConfiguration : EntityTypeConfiguration<BlogDetails>
{

    public BlogDetailsConfiguration()
        : base()
    {

        Map(m =>
            {
                m.MapInheritedProperties();  
                m.ToTable("icoprod.BlogDetails");
            });

    }

}

この時点で、「拡張」情報を含むテーブルのすべての情報を含むビューを使用しようとしましたが、これを実行すると、恐ろしい3032エラーが発生します(エラーサンプルはこちら)。次に、ビューにテーブルの主キーと「拡張」プロパティのみを含めるようにしました(たとえば、[エントリ]はPostDetailsビューにありません)。これを試してみると、次のエラーが発生します。

All objects in the EntitySet 'DBContext.Post' must have unique primary keys. However, an instance of type 'PostDetails' and an instance of type 'Post' both have the same primary key value, 'EntitySet=Post;ID=1'.

だから私はMapAliExpressPropertiesを少し省略して遊んだことがありますが、運がありません。同様のエラーが引き続き発生します。

ベース/テーブルオブジェクトを「拡張」してビューから情報をロードする方法について誰かが提案を持っていますか?繰り返しになりますが、これを行うことでパフォーマンスが大幅に向上すると思います。この質問の冒頭で参照した記事には2つの解決策がありますが、1つは(一般的なルックアップ情報を取得するためだけに)DBヒットが多すぎる必要があり、もう1つは抽象化の追加レイヤーが必要です(そして私は本当に直接行きたいですマッピングを記述せずに、DBからのPOCO)。

最後に、このような質問に答えてくださった皆様、ありがとうございました。長年にわたって対応に貢献してくださった皆様に拍手を送ります。私たち開発者の多くは、この情報を当然のことと思っていると思います!!

4

1 に答える 1

3

ビューからレコードをロードしてテーブルに保存しても、コードマッピングでは機能しません-ブログエンティティは常にテーブルからロードされてテーブルに保存され、BlogDetailエンティティは常にビューからロードされてビューに保存されます-したがって、更新可能なビューが必要です。このシナリオをサポートするためのトリガーの。EDMXを使用する場合は、挿入、更新、および削除のために実行されるカスタムSQL /ストアドプロシージャをマップして、テーブルに強制的に保存することもできますが、この機能はコードマッピングでは使用できません。とにかくそれはあなたの最大の問題ではありません。

ビューを使用して、行ったようにクラスにマップできますが、継承をマップしてはなりません。その理由は、継承がどのように機能するかです。継承は、エンティティが親または子(親として機能できる)のいずれかであることを示します。親(つまり、親のみ)または子の両方になることができるデータベースレコードは存在できません。このシナリオをサポートするには、親タイプと子タイプの2つのインスタンスが必要であるため、.NETでは不可能ですらあります。純粋な親を子にキャストできない(子ではない)ため、これら2つのインスタンスは同等ではありません。そして、ここに最大の問題があります。継承をマップしたら、キーは継承階層全体で一意である必要があります。したがって、同じキーを持つ2つのインスタンス(1つは親用、もう1つは子用)を持つことはできません。

BlogDetail回避策として、マップされたエンティティ()から派生しないでくださいBlog。マップされていない3番目のクラスを両方またはインターフェイスの親として使用します。また、を完全に無関係にMapInheritedPropertiesするために使用しないでください。BlogDetailBlog

もう1つの回避策は、BlogDetailをまったくマッピングしないことです。このような場合、コードをそのまま使用でき、ビューを使用する代わりに、プロジェクションを使用して単純な再利用可能なクエリを作成できます。

var blogDetails = from b in context.Blogs
                  where ... 
                  select new BlogDetail
                      {
                          Name = b.Name,
                          CreatedByID = b.CreatedByID,
                          ...
                          CreatedByName = b.CreatedBy.Name // You need navigation property
                          ...   
                      }; 

どちらの場合も、保存Blogする必要がある場合は、新しいインスタンスを作成し、から入力する必要がありますBlogDetail。その後、コンテキストにアタッチし、変更された状態に設定して変更を保存します。

于 2012-04-11T08:46:38.470 に答える