Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partially nullable composite Foreign Key is not supported #1772

Open
lybax opened this issue Feb 25, 2019 · 1 comment
Open

Partially nullable composite Foreign Key is not supported #1772

lybax opened this issue Feb 25, 2019 · 1 comment

Comments

@lybax
Copy link

lybax commented Feb 25, 2019

OData returns an incomplete (chunked) JSON response when expanding a navigation property referenced by two foreign keys, of which one is nullable and actually being null.

Assemblies affected

Microsoft.AspNetCore.OData, Version=7.1.0.21120

Reproduce steps

Model

public class SourceEntity
{
   public int ForeignKey1 { get; set; }
   public int? ForeignKey2 { get; set; }
   public virtual TargetEntity NavigationProperty { get; set; }
}
public class TargetEntity
{
   public int Key1 { get; set; }
   public int Key2 { get; set; }
}

ODataConventionalBuilder
builder.EntitySet<SourceEntity>("EntitySet").EntityType.HasOptional(x => x.NavigationProperty, (c, r) => c.ForeignKey1 == r.Key1 && c.ForeignKey2 == r.Key2);

OData query

Expected result

Complete JSON response (HTTP Status 200 OK)
{"@odata.context":"http://localhost/odata/$metadata#EntitySet(ForeignKey1,ForeignKey2,NavigationProperty)","value":[{"ForeignKey1":1234,"ForeignKey2":null,"NavigationProperty":null}]}

Actual result

Incomplete (chunked) JSON response (HTTP Status 200 OK)
{"@odata.context":"http://localhost/odata/$metadata#EntitySet(ForeignKey1,ForeignKey2,NavigationProperty)","value":[{"ForeignKey1":1234,"ForeignKey2":null

Additional detail

A similar query in Entity Framework Core (EF Core) works correctly:

var test = DbSet<SourceEntity>().Where(x => x.ForeignKey2 == null).Take(1).Select(x => x.NavigationProperty).ToList();

By definition,

The value of a composite foreign key is null if any component of the value is null.

@lybax
Copy link
Author

lybax commented Feb 26, 2019

The issue comes from the generated SQL query.

With this query (which does not work):
http://localhost/odata/EntitySet?$top=1&$select=ForeignKey1,ForeignKey2&$filter=ForeignKey2%20eq%20null&$expand=NavigationProperty

We get:

exec sp_executesql N'SELECT TOP(@__TypedProperty_0) 
[$it.NavigationProperty].[ForeignKey1], 
[$it.NavigationProperty].[ForeignKey2],
[$it.NavigationProperty].[OtherProperty1], 
[$it.NavigationProperty].[OtherProperty2],
[$it].[ForeignKey1] AS [Value], 
[$it].[ForeignKey2] AS [Value0], 
CASE
    WHEN [$it].[ForeignKey1] IS NULL AND [$it].[ForeignKey2] IS NULL
    THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END AS [IsNull]
FROM [secured].[SourceEntities] AS [$it]
LEFT JOIN [secured].[TargetEntities] AS [$it.NavigationProperty] ON ([$it].[ForeignKey1] = [$it.NavigationProperty].[ForeignKey1]) AND ([$it].[ForeignKey2] = [$it.NavigationProperty].[ForeignKey2])
WHERE [$it].[ForeignKey2] IS NULL
ORDER BY [Value]',N'@__TypedProperty_0 int',@__TypedProperty_0=1

where [IsNull] is 0, because it compares both foreign keys with AND statement instead of OR, which is incorrect according to this statement:

The value of a composite foreign key is null if any component of the value is null.

Now using this query (which works):
http://localhost/odata/EntitySet?$top=1&$select=ForeignKey1,ForeignKey2&$filter=ForeignKey2%20eq%20null&$expand=NavigationProperty($select=Key1)

We get:

exec sp_executesql N'SELECT TOP(@__TypedProperty_0) 
[$it.NavigationProperty].[ForeignKey1] AS [Value0], 
[$it.NavigationProperty].[ForeignKey2] AS [Value], 
[$it].[ForeignKey1] AS [Value1], 
[$it].[ForeignKey2] AS [Value2], 
CASE
    WHEN [$it.NavigationProperty].[ForeignKey1] IS NULL
    THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END & CASE
    WHEN [$it.NavigationProperty].[ForeignKey2] IS NULL
    THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END AS [IsNull]
FROM [secured].[SourceEntities] AS [$it]
LEFT JOIN [secured].[TargetEntities] AS [$it.NavigationProperty] ON ([$it].[ForeignKey1] = [$it.NavigationProperty].[ForeignKey1]) AND ([$it].[ForeignKey2] = [$it.NavigationProperty].[ForeignKey2])
WHERE [$it].[ForeignKey2] IS NULL
ORDER BY [Value1]',N'@__TypedProperty_0 int',@__TypedProperty_0=1

where [IsNull] is 1, because it compares 2 fields from the joined table, which are null.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants