Я исследовал этот вопрос в течение нескольких дней и, похоже, не могу найти вариант, который мне нравится; однако, здесь есть ссылка на очень похожий вопрос:
Добавить расчетное поле в модель
В конечном счете, у меня есть тот же вопрос, но я надеюсь на лучшее решение.
Рассмотрим следующие таблицы DB:
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 loading, а BuiltBy - свойство типа Contact, на основе CreatedByID): currentPost.CreatedBy.Name;
Проблема с этим подходом - количество вызовов Db, а также большая запись, полученная для контакта, но мы используем только имя 99% в этой ситуации. Я понимаю, что схема DB выше крошечная, но это просто упрощенный пример, а реальная таблица контактов имеет около 50 полей.
Чтобы управлять этим типом ситуации в прошлом (до использования EF), я обычно создавал "подробные" представления для таблиц, которые я буду использовать. Представления "подробно" содержат общие поля поиска/расчета, так что для эффективного получения всей необходимой информации требуется только один вызов в БД (ПРИМЕЧАНИЕ. Мы также используем индексирование в наших представлениях 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; }
}
Причина, по которой мне нравится этот подход, заключается в том, что он позволяет мне извлекать информацию из базы данных на основе таблиц или представлений. И если я загружаю представление, представление содержит всю "таблицу", которая позволяет мне загружаться из представления а сохранить в таблицу. ИМО, это дает мне лучшее из обоих миров.
Я использовал этот подход в прошлом, но, как правило, я просто загружал информацию из БД, используя datarows или информацию из сохраненных процессов или даже используемую дозвуковую схему activerecord и отображаемые поля после загрузки из БД. Я действительно надеюсь, что смогу сделать что-то в 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 (пример ошибки здесь). Затем я попытался иметь представление ТОЛЬКО содержать главный ключ таблицы и "расширенные" свойства (например, [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 немного, но без везения. Я по-прежнему получаю аналогичную ошибку.
Есть ли у кого-нибудь предложение о том, как "расширить" объект base/table и загрузить информацию из представления? Опять же, я считаю, что при этом получается большая производительность. В статье, на которую я ссылался в начале этого вопроса, есть 2 потенциальных решения, но 1 требует слишком большого количества удалений БД (просто чтобы получить некоторую общую информацию о поиске), а другой требует дополнительного слоя абстракции (и мне бы очень хотелось перейти непосредственно к мой POCO из БД, не записывая никакого сопоставления).
Наконец, благодарю вас всем, кто отвечает на эти вопросы. Я приветствую всех, кто внес свой вклад в ответы на протяжении многих лет. Я думаю, что многие из нас разработчики принимают эту информацию как должное!!