Reputation: 1958
Overview
I am using entity framework 4.3 code first with the fluent interface to setup my DbContext. I have a base Item class with other types that inherit this such as an Event, BlogPost, ForumThread, WikiPage and so on.
These inherited types are mapped with what I think entity framework refers to as TPT inheritance. This works great when querying a single type like 'events' or 'blog posts' but constructs very complicated queries with horrible performance when trying to query across all types due to the joins required in order to achieve the polymorphic behaviour EF provides out of the box.
Problem Context
I am wanting to build a global search feature where I only need access to the base 'Item' entity and not the inherited instances. I'd like to be able to query across the base item class by name, tags and so on. Performing any sort of LINQ query, even when requesting the base item type still results in polymorphic behaviour which kills performance.
Code First Model
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Body { get; set; }
public DateTime Created { get; set; }
public int? CreatedBy { get; set; }
public int? LastModifiedBy { get; set; }
public DateTime? LastModified { get; set; }
public virtual User Author { get; set; }
public bool IsDeleted { get; set; }
public string ImageUri { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Event : Item
{
// Additional properties
}
public class BlogPost : Item
{
// Additional properties
}
What I would like to be able to do is map another POCO to the same base table so that when I construct queries on it, it doesn't involve the inheritence issues. EF doesn't seem to like this though. I don't have the error on hand at the moment but my attempts at a simple mapping failed.
Alternate Solutions?
I had thought about implementing an 'index' table which would look similar to the 'Item' table and inserting a record into that whenever a new item type is created. But this index data would then also need to be updated whenever the event, blog post data changes etc. This is further complicated by foreign keys such as tags. Whenever tags on say an event are changed I would have to make sure these changes are synchronised on the matching index table too. When considering all the different item types, this would become a bit of a nightmare to manage and frankly, doesn't seem like a very elegant solution.
Database triggers
My preferred solution here would be one that is in code and not database triggers / stored procs.
Is there a way to construct a query to force EF to only return the base type instead of the polymorphic type which results in too many joins and horrible performance? Or is there some other clever way around this?
Update
After updating to EntityFramework 5 via Nuget (targeting .Net 4.0) I have been able to query items by their tags and project into a new SearchItem which results in fairly clean SQL without joins to the TPT types.
var x = from item in repository.FindAll<Item>()
where item.Tags.Any(t => t.Name == "test")
select new SearchItem
{
Id = item.Id,
Name = item.Name,
Body = item.Body,
Created = item.Created,
CreatedBy = item.CreatedBy,
IsDeleted = item.IsDeleted,
ImageUri = item.ImageUri,
MembershipEntityId = item.MembershipEntityId,
//Tags = (from t in item.Tags
// select new Tag
// {
// Id = t.Id,
// Name = t.Name,
// MembershipEntityId = t.MembershipEntityId
// })
};
SQL
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Body] AS [Body],
[Extent1].[Created] AS [Created],
[Extent1].[CreatedBy] AS [CreatedBy],
[Extent1].[IsDeleted] AS [IsDeleted],
[Extent1].[ImageUri] AS [ImageUri],
[Extent1].[MembershipEntityId] AS [MembershipEntityId]
FROM [dbo].[Item] AS [Extent1]
WHERE EXISTS (SELECT
1 AS [C1]
FROM [dbo].[ItemTag] AS [Extent2]
INNER JOIN [dbo].[Tag] AS [Extent3] ON [Extent3].[Id] = [Extent2].[Tag_Id]
WHERE ([Extent1].[Id] = [Extent2].[Item_Id]) AND (N'test' = [Extent3].[Name])
)
This has solved half of my problem as I can now search across the base type by tag. I would however like to be able to return the tags with the new projection. Including that commented out bit of code results in a query that EF cannot translate though. Is there a workaround for this?
Upvotes: 4
Views: 1851
Reputation: 364369
Is there a way to construct a query to force EF to only return the base type instead of the polymorphic type which results in too many joins and horrible performance?
Generally no. You have mapped inheritance and if you want to return instances of Item
, EF must always return correct type => it needs those joins. EF also doesn't allow mapping the same table multiple times so you cannot have in the same mapping the Item
mapped again as another POCO.
In theory you should be able to query Items
and project to your non mapped POCO class only properties you want from the base class. Unfortunately this didn't work in .NET 4.0 - EF still performed joins. You can try this with .NET 4.5 and EF 5.0 where this issue should be solved.
Upvotes: 3