Entity Framework Pitfalls: Queries Over Navigation Properties When There Are Foreign Key Properties Are Ignored

Long title, I know! Smile

Another one for Entity Framework Code First. The problem is: imagine you have an entity with a relation to another entity (one-to-one, many-to-one) where you have both the navigation property and the foreign key property:

public class Source
{
public int Id { get; set; }

[ForeignKey("TargetId")]
public virtual Target Target { get; set; }

public int? TargetId { get; set; }
}

public class Target
{
public int Id { get; set; }
public virtual ICollection<Source> Sources { get; set; }
}

You may want to query all Sources that have a Target set:

var sourcesWithTarget = ctx
.Sources
.Where(source => source.Target != null)
.ToList();

However, you will get this SQL instead:

SELECT [Extent1].[Id] AS [Id], [Extent1].[TargetId] AS [TargetId], 
FROM [dbo].[Source] AS [Extent1]

See? No WHERE clause!

If, instead, we query on the foreign key property:

var sourcesWithTarget = ctx
.Sources
.Where(source => source.TargetId != 0)
.ToList();

Then we get what we want:

SELECT [Extent1].[Id] AS [Id], [Extent1].[TargetId] AS [TargetId], 
FROM [dbo].[Source] AS [Extent1]
WHERE 0 <> [Extent1].[TargetId]

Weird, huh?

Stay tuned for more!

                             

13 Comments

  • Whoa... weird, indeed. Is this for EF6.0 or .NET Core? Might be worth making sure you differentiate going forward using tag, title, or both. :)

  • Hi, Mike!
    I said this is for Entity Framework Code First! :-)
    I'll try to make it clearer in the future!

  • Code First is the mapping strategy. Mike was asking which version of EF you are using. The current EF 6.x? Or the EF Core 1.0 reboot?

  • InsaneTestPilot: no, Entity Framework Code First has been the name of EF since version 4.1. If I wanted to say Core, I would have said so.

  • I don't have the ability to test this now, but I suspect this could be a lazy/eager loading issue.

    If you do this, I'm betting it will work:
    var sourcesWithTarget = ctx.Sources.Include(source => source.Target)
    .Where(source => source.TargetId != 0)
    .ToList();

    Removing the virtual keyword from "public virtual Target Target { get; set; }" may also change this behavior.

  • Copied the wrong line...

    Anyway, this still may work

    var sourcesWithTarget = ctx.Sources.Include(source => source.Target)
    .Where(source => source.Target != null)
    .ToList();

  • Hi, Dansor!
    Thanks, but none of these work. Please try them! :-)

  • AH! Again, I'm not in front of a computer with VS, so I can't test, and therefore I'm more throwing out suggestions.
    BUT! It looks like at some point they decided to intentionally make null != null, but added an option
    https://henrylu.me/2014/07/05/null-value-handling-in-entity-framework/

    By setting DbContext .ContextOptions .UseCSharpNullComparisonBehavior to true, that should resolve it.
    But, again, I can't actually test at the moment, so I apologize if I'm leading you down another incorrect path.

  • Hi!
    It is actually an option of the ObjectContext, not DbContext (https://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.contextoptions(v=vs.110).aspx).
    Anyway, I tried it, and it doesn't work:

    (ctx as IObjectContextAdapter).ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior = true;

  • I am with Dansor on this. EF lazy loading is the real culprit here. In order for EF to test for a null condition on a POCO object, it would be forced to load each object into memory first. However, since your Target object property is defined as virtual, you are telling EF to lazy load this object when you need to use a property on the object. Your query doesn't need to access a property on the object.

    Weird issue. Thanks for bringing it to our attention. I'll make sure my developers are aware of this.

  • Delcov: not, it isn't, this has nothing to do with lazy loading at all. A LINQ query is translated to SQL, nothing to do with laziness here.

  • I'm aware that it's translating the LINQ query to SQL but EF is using your class libraries and DataContext to understand how to map your queryable into understandable SQL code.

    You're using your POCO object class properties in your Where queryable's lambda condition when that property ("Target") is a lazy-loaded navigation property for ease-of-use OO purposes. The only reasons I could see for using it in the WHERE clause would be type checking your object for child types or to navigate to other non-POCO properties on your navigated object necessary for your condition check.

  • Delcov: those are the only reasons YOU see. In general, I *never* use foreign key properties, so I always use the reference properties in my LINQ queries. Again, this problem has nothing to do with laziness, this happens if we disable lazy loading or do an eager fetch using Include.

Add a Comment

As it will appear on the website

Not displayed

Your website