2012-04-10 61 views
1

我研究了這個問題幾天,似乎無法找到我感覺良好的選項;然而,這裏是一個鏈接到一個非常類似的問題:EF 4代碼優先 - 組合視圖和表

Add Calculated field to Model

最終,我也有同樣的問題,但我希望有一個更好的解決方案。

考慮下面的數據庫表:

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 
) 

我現在想使用視圖用於裝載「共同」查找/計算的信息。每次我們在網站上顯示帖子時,我們都想知道創建帖子的人的姓名以及上次修改人的姓名。這些是存儲在帖子表的不同表格中的兩個字段。我可以很容易地使用下面的語法(假設應用了Lazy/eager加載,並且CreatedBy是基於CreatedByID的Contact類型的屬性):currentPost.CreatedBy.Name;

該方法的問題在於撥打電話的次數,也是爲聯繫人檢索到的大記錄,但在這種情況下我們只使用名稱99%。我意識到上面的數據庫模式很小,但這只是一個簡單的例子,真正的聯繫表有大約50個字段。

爲了管理過去這種類型的情況(在使用EF之前),我通常爲我將使用的表格構建出「細節」視圖。 「詳細信息」視圖包含常見的查找/計算字段,因此它只需要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; } 
} 

的我喜歡這種方法的原因是,它允許我根據表或視圖從數據庫檢索信息,如果我加載視圖,則視圖包含所有「表」信息w這將允許我從視圖加載,但保存到表格。國際海事組織,這給了我兩全其美。

我過去曾經使用過這種方法,但通常我只是使用來自存儲過程的數據行或信息從DB加載信息,甚至在從數據庫加載後使用亞音速活動記錄模式和映射字段。我真的希望我可以在EF中做一些事情,讓我可以加載這些對象而無需創建另一個抽象層。

這是我曾嘗試使用用於配置(使用流利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錯誤(error sample here)。然後我嘗試讓視圖只包含表的主鍵和「擴展」屬性(例如[Entry]不在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'. 

所以我有離開過MapInheritedProperties位出場,但沒有運氣。我仍然有類似的錯誤。

有沒有人有關於如何「擴展」基本/表對象和從視圖加載信息的建議?再次,我相信這樣做會帶來巨大的性能提升。我在這個問題開始時引用的文章有兩個潛在的解決方案,但1需要太多的數據庫命中(只是爲了獲得一些常見的查找信息),另一個需要額外的抽象層(我真的想直接去我的POCO來自DB,沒有寫任何映射)。

最後,謝謝給所有回答這些類型問題的人。我讚揚多年來爲迴應做出貢獻的每一個人。我想我們很多開發人員都會將這些信息視爲理所當然!

回答

3

從視圖中載入記錄並將其保存到表格中將無法使用代碼映射 - 博客實體將始終從表格中加載並保存到表格中,BlogDetail實體將始終從視圖中加載並保存爲視圖 - 所以您必須擁有可更新視圖或代替觸發器來支持此場景。如果您使用EDMX,您還可以映射爲插入,更新和刪除而執行的自定義SQL /存儲過程以強制保存到表格,但此功能在代碼映射中不可用。無論如何,這不是你最大的問題。

你可以使用你的視圖,你可以將它映射到類,但你不能映射繼承。原因是繼承的工作方式。繼承說實體是父母或孩子(可以作爲父母)。永遠不會有數據庫記錄可以是父母(我的意思是隻有父母)或孩子。在.NET中甚至是不可能的,因爲爲了支持這種情況,你需要兩個實例 - 父類型和子類型之一。這兩個實例並不等同,因爲純粹的父對象不能轉換爲子對象(它不是子對象)。這是最大的問題。一旦映射繼承,鍵在整個繼承層次中必須是唯一的。所以你永遠不能有兩個實例(一個用於父母,一個用於孩子)使用相同的密鑰。

解決方法不是從映射實體(Blog)派生BlogDetail。使用第三個未映射的類作爲父類或接口。也請勿使用MapInheritedProperties使您的BlogDetailBlog完全無關。

另一個解決方法是根本不映射BlogDetail。如果您需要保存Blog您必須創建新實例,並從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 
          ... 
         }; 

在兩種情況下:在這種情況下,你可以使用你的代碼是和而不是使用一個視圖中創建簡單的可重用的查詢與投影。之後,將其附加到上下文,將其設置爲已修改狀態並保存更改。

+0

感謝您的詳細解釋。我最終做了類似於你的建議。我使用3個獨立的對象(1個用於查看,1個用於表格,1個包含屬性的「域」模型)。在加載時,我從視圖加載,映射到我的域模型。在保存時,我從我的域模型映射到表模型。這幾乎是一種有效的記錄模式,但它將我所有的映射放在代碼中,這使得它更易於調試。 – 2012-10-09 15:52:12