2012-04-10 60 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