EF 4 Code First - 结合视图和表格

时间:2012-04-10 23:09:24

标签: entity-framework c#-4.0 views ef-code-first

我研究了这个问题已经好几天了,似乎找不到我感觉良好的选择;但是,这里有一个非常相似的问题的链接:

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
)

我现在想使用视图加载“常见”查找/计算信息。每次我们在网站上显示帖子时,我们都想知道创建帖子的人的姓名以及最后修改过帖子的人。这两个字段存储在post表的不同表中。我可以轻松地使用以下语法(假设应用了Lazy / eager加载,并且CreatedBy是属于Contact的类型的属性,基于CreatedByID):currentPost.CreatedBy.Name;

该方法的问题是Db调用的数量以及为联系检索的大记录,但在这种情况下我们仅使用Name 99%。我意识到上面的DB模式很小,但这只是一个简化的例子,真正的联系表有大约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; }
}

我喜欢这种方法的原因是它允许我根据表或视图从数据库中检索信息。如果我加载视图,该视图包含允许我从视图加载的所有“表”信息但保存到一张桌子。国际海事组织,这给了我两全其美。

我过去曾使用过这种方法,但通常情况下,我只是使用数据行或来自存储过程的信息从数据库中加载信息,甚至在从数据库加载后使用亚音速主动记录模式和映射字段。我真的希望我能在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错误(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,但没有运气。我继续得到类似的错误。

有没有人建议如何“扩展”基础/表格对象并从视图加载信息?同样,我相信通过这样做会有很大的性能提升。我在这个问题的开头引用的文章有2个可能的解决方案,但是1需要太多的数据库命中(只是为了获得一些常见的查找信息)而另一个需要额外的抽象层(我真的想直接去我的POCO来自数据库,没有写任何映射)。

最后,谢谢给所有回答这些问题的人。我赞赏多年来为回应作出贡献的所有人。我想我们太多的开发人员认为这些信息是理所当然的!!

1 个答案:

答案 0 :(得分:3)

从视图加载记录并将其保存到表将无法使用代码映射 - 博客实体将始终从表中加载并保存到表中,并且BlogDetail实体将始终从视图加载并保存到视图中 - 因此您必须具有可更新查看或代替触发器来支持此方案。如果使用EDMX,还可以映射为插入,更新和删除执行的自定义SQL /存储过程以强制保存到表,但此功能在代码映射中不可用。无论如何,这不是你最大的问题。

您可以使用您的视图,您可以将其映射到类,但不能映射继承。原因是继承的工作方式。继承说实体是父母或子女(可以作为父母)。永远不会有数据库记录,既可以是父级(我的意思是仅父级),也可以是子级。在.NET中甚至不可能,因为要支持这种情况,您需要两个实例 - 父类型和子类型之一。这两个实例不等效,因为纯父不能转换为子(不是子)。这是最大的问题。映射继承后,密钥在整个继承层次结构中必须是唯一的。因此,您永远不会有两个具有相同密钥的实例(一个用于父实例,一个用于子代)。

作为一种解决方法,请勿从映射实体(BlogDetail)派生Blog。使用第三个未映射的类作为父级或接口的父级。另请注意,请勿使用MapInheritedProperties使BlogDetailBlog完全无关。

另一种解决方法是根本不映射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填写。之后,将其附加到上下文,将其设置为修改状态并保存更改。