Вопрос по entity-framework, c#-4.0 – EF 4 Code First - объединение видов и таблиц

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
)

Теперь я хотел бы использовать представления для загрузки & quot; общих & quot; поиск / расчетная информация. Каждый раз, когда мы отображаем сообщение на сайте, мы хотим знать имя человека, который создал сообщение и кто в последний раз изменил его. Это два поля, которые хранятся в отдельных таблицах из таблицы записей. Я мог бы легко использовать следующий синтаксис (при условии, что была применена отложенная загрузка) и CreatedBy был свойством типа Contact, основанным на CreatedByID): currentPost.CreatedBy.Name;

Проблема с этим подходом заключается в количестве вызовов Db, а также большой записи, извлеченной для контакта, но в этой ситуации мы используем только имя 99%. Я понимаю, что приведенная выше схема БД крошечная, но это всего лишь упрощенный пример, и в реальной таблице контактов содержится около 50 полей.

Чтобы справиться с ситуацией такого типа в прошлом (до использования EF), я обычно выкладывал «детализацию». представления для таблиц я буду использовать. & Quot; деталь & quot; Представления содержат общие поля поиска / вычисления, поэтому для эффективного получения всей необходимой информации требуется всего один вызов в БД (ПРИМЕЧАНИЕ. Мы также используем индексирование для наших представлений 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; }
}

Причина, по которой мне нравится этот подход, заключается в том, что он позволяет мне извлекать информацию из базы данных на основе таблиц или представлений. И если я загружаю представление, представление содержит все таблицы & quot; table & quot; информация, которая позволила бы мне загрузить из представления, но сохранить в таблицу. ИМО, это дает мне лучшее из обоих миров.

Я использовал этот подход в прошлом, но, как правило, я просто загружал информацию из БД, используя datarows или информацию из хранимых процедур, или даже использовал шаблон дозвуковой активной записи и сопоставленные поля после загрузки из БД. Я действительно надеюсь, что смогу сделать что-то в 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");
            });

    }

}

На этом этапе я попытался использовать представление, содержащее всю информацию из таблицы с расширением & quot; расширенным & quot; информация и когда я пытаюсь это я получаю страшную ошибку 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'.

Так что я немного поиграл с отключением MapInheritedProperties, но безуспешно. Я продолжаю получать похожую ошибку.

Есть ли у кого-нибудь предложение о том, как «продлить»? объект базы / таблицы и информация о загрузке из представления? Опять же, я считаю, что благодаря этому достигается большой прирост производительности. В статье, на которую я ссылался в начале этого вопроса, есть 2 возможных решения, но 1 требует слишком много обращений к БД (просто для получения некоторой общей информации о поиске), а другая требует дополнительного уровня абстракции (и я действительно хотел бы перейти непосредственно к мои POCO из БД, без записи каких-либо карт).

И, наконец,thank you всем, кто отвечает на эти вопросы. Я приветствую всех, кто внес свой вклад в ответы на протяжении многих лет. Я думаю, что слишком многие из нас, разработчиков, принимают эту информацию как должное!

Ваш Ответ

1   ответ
3

ботать с отображением кода - сущность Blog всегда будет загружаться из таблицы и сохраняться в таблицу, а сущность 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, После этого вы присоединяете его к контексту, устанавливаете его в измененное состояние и сохраняете изменения.

Спасибо за подробное объяснение. Я в конечном итоге сделал то, что вы предложили. Я использую 3 отдельных объекта (1 для просмотра и 1 для таблицы и 1 для модели «домен», которая содержит свойство). При загрузке я загружаю из вида карту в мою модель домена. При сохранении я сопоставляю свою модель домена с табличной моделью. Это почти активный шаблон записи, но он помещает все мои сопоставления в код, что облегчает отладку. Chad Capra

Похожие вопросы