1

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

将计算字段添加到模型

最终,我有同样的问题,但我希望有更好的解决方案。

考虑以下数据库表:

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 表不同的表中的字段。我可以轻松地使用以下语法(假设应用了延迟/急切加载,并且 CreatedBy 是基于 CreatedByID 的 Contact 类型的属性): currentPost.CreatedBy.Name;

这种方法的问题在于 Db 调用的数量以及为联系人检索到的大量记录,但在这种情况下我们只使用 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; }
}

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

我过去使用过这种方法,但通常,我只是使用数据行或存储过程中的信息从数据库加载信息,甚至在从数据库加载后使用亚音速活动记录模式和映射字段。我真的希望我可以在 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 错误(错误示例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 个需要太多的 DB 命中(只是为了获得一些常见的查找信息),另一个需要额外的抽象层(我真的很想直接去我的 POCO 来自数据库,没有写任何映射)。

最后,感谢所有回答此类问题的人。我为多年来为响应做出贡献的每个人鼓掌。我认为我们太多的开发人员认为这些信息是理所当然的!!

4

1 回答 1

3

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

您可以使用您的视图,并且可以像以前一样将其映射到类,但不能映射继承。原因在于继承的工作方式。继承表示实体要么是父级,要么是子级(可以充当父级)。永远不会有既可以是父母(我的意思是只有父母)也可以是孩子的数据库记录。这在 .NET 中甚至是不可能的,因为要支持这种情况,您需要两个实例——一个父类型实例和一个子类型实例。这两个实例不等价,因为纯父级不能强制转换为子级(它不是子级)。最大的问题来了。映射继承后,键在整个继承层次结构中必须是唯一的。因此,您永远不能有两个具有相同密钥的实例(一个用于父级,一个用于子级)。

作为一种解决方法,不要BlogDetail从映射实体 ( Blog) 派生。要么使用第三个未映射的类作为两者的父类,要么使用接口。也不要MapInheritedProperties用来使你的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
                          ...   
                      }; 

在这两种情况下,如果您需要保存Blog,您必须创建新实例并从BlogDetail. 之后将其附加到上下文,将其设置为修改状态并保存更改。

于 2012-04-11T08:46:38.470 回答