Joshua Hayes
Joshua Hayes

Reputation: 1958

How to avoid polymorphic behaviour in Entity Framework TPT Inheritance in order to query base type efficiently

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?

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

Answers (1)

Ladislav Mrnka
Ladislav Mrnka

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

Related Questions