Skip to content

Commit

Permalink
Use the new .NET api to check for nullable reference types (#7827)
Browse files Browse the repository at this point in the history
  • Loading branch information
huysentruitw authored Dec 16, 2024
1 parent 2a8f951 commit 109b60e
Show file tree
Hide file tree
Showing 11 changed files with 102 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace HotChocolate.Execution.Projections;

internal sealed class SelectionExpressionBuilder
{
private static readonly NullabilityInfoContext _nullabilityInfoContext = new();

public Expression<Func<TRoot, TRoot>> BuildExpression<TRoot>(ISelection selection)
{
var rootType = typeof(TRoot);
Expand Down Expand Up @@ -291,14 +293,9 @@ private static bool IsNullableType(PropertyInfo propertyInfo)
return Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
}

var nullableAttribute = propertyInfo.GetCustomAttribute<NullableAttribute>();

if (nullableAttribute != null)
{
return nullableAttribute.NullableFlags[0] == 2;
}
var nullabilityInfo = _nullabilityInfoContext.Create(propertyInfo);

return false;
return nullabilityInfo.WriteState == NullabilityState.Nullable;
}

private readonly record struct Context(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,39 @@ public async Task Project_Key_To_Collection_Expression_Integration()
.MatchMarkdownSnapshot();
}

[Fact]
public async Task Product_With_Nullable_Reference_Property_Set_To_Null()
{
// Arrange
var queries = new List<string>();
var connectionString = CreateConnectionString();
await CatalogContext.SeedAsync(connectionString);

// Act
var result = await new ServiceCollection()
.AddScoped(_ => queries)
.AddTransient(_ => new CatalogContext(connectionString))
.AddGraphQLServer()
.AddQueryType<ProductsWithNullPropertyQuery>()
.ExecuteRequestAsync(
"""
{
productById(id: 1) {
name
type {
name
}
}
}
""");

// Assert
Snapshot.Create()
.AddSql(queries)
.Add(result, "Result")
.MatchMarkdownSnapshot();
}

public class Query
{
public async Task<Brand?> GetBrandByIdAsync(
Expand Down Expand Up @@ -836,6 +869,39 @@ public async Task<IEnumerable<Brand>> GetBrandByIdAsync(
.ToListAsync(cancellationToken);
}

public class ProductsWithNullPropertyQuery
{
public async Task<ProductProjection?> GetProductByIdAsync(
int id,
List<string> queries,
ISelection selection,
CatalogContext context,
CancellationToken cancellationToken)
{
var query = context.Products
.Where(p => p.Id == id)
.Select(p => new ProductProjection // Cast to an object with nullable Type property
{
Name = p.Name,
})
.Select(selection.AsSelector<ProductProjection>());

lock (queries)
{
queries.Add(query.ToQueryString());
}

return await query.SingleOrDefaultAsync(cancellationToken);
}

public class ProductProjection
{
public string Name { get; set; } = default!;

public ProductType? Type { get; set; }
}
}

[ExtendObjectType<Brand>]
public class BrandExtensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```text
-- @__keys_0={ '1', '2' } (DbType = Object)
SELECT p."Name", b."Name", p."Id"
SELECT p."Name", FALSE, b."Name", p."Id"
FROM "Products" AS p
INNER JOIN "Brands" AS b ON p."BrandId" = b."Id"
WHERE p."Id" = ANY (@__keys_0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

```text
-- @__keys_0={ '1' } (DbType = Object)
SELECT p."Name", b."Name", p."Id"
SELECT p."Name", FALSE, b."Name", p."Id"
FROM "Products" AS p
INNER JOIN "Brands" AS b ON p."BrandId" = b."Id"
WHERE p."Id" = ANY (@__keys_0)
-- @__keys_0={ '1' } (DbType = Object)
SELECT p."Id", b."Id"
SELECT p."Id", FALSE, b."Id"
FROM "Products" AS p
INNER JOIN "Brands" AS b ON p."BrandId" = b."Id"
WHERE p."Id" = ANY (@__keys_0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```text
-- @__keys_0={ '1' } (DbType = Object)
SELECT p."Name", b."Name", p."Id"
SELECT p."Name", FALSE, b."Name", p."Id"
FROM "Products" AS p
INNER JOIN "Brands" AS b ON p."BrandId" = b."Id"
WHERE p."Id" = ANY (@__keys_0)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Product_With_Nullable_Reference_Property_Set_To_Null

## SQL

```text
-- @__id_0='1'
SELECT p."Name"
FROM "Products" AS p
WHERE p."Id" = @__id_0
```

## Result

```json
{
"data": {
"productById": {
"name": "Product 0-0",
"type": null
}
}
}
```

Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ public class Product
// Maximum number of units that can be in-stock at any time (due to physicial/logistical constraints in warehouses)
public int MaxStockThreshold { get; set; }

/// <summary>Optional embedding for the catalog item's description.</summary>
// [JsonIgnore]
// public Vector Embedding { get; set; }

/// <summary>
/// True if item is on reorder
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ORDER BY t."Name", t."Id"
## Expression 0

```text
[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Select(root => new Foo() {Id = root.Id, Name = root.Name, Bar = IIF((root.Bar == null), null, new Bar() {Id = root.Bar.Id, Description = root.Bar.Description})}).Take(11)
[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Select(root => new Foo() {Id = root.Id, Name = root.Name, Bar = IIF((root.Bar == null), null, new Bar() {Id = root.Bar.Id, Description = IIF((root.Bar.Description == null), null, root.Bar.Description)})}).Take(11)
```

## Result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ORDER BY t."Name", t."Id"
## Expression 0

```text
[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Select(root => new Foo() {Id = root.Id, Name = root.Name, Bar = IIF((root.Bar == null), null, new Bar() {Id = root.Bar.Id, Description = root.Bar.Description, SomeField1 = root.Bar.SomeField1, SomeField2 = root.Bar.SomeField2})}).Take(11)
[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Select(root => new Foo() {Id = root.Id, Name = root.Name, Bar = IIF((root.Bar == null), null, new Bar() {Id = root.Bar.Id, Description = IIF((root.Bar.Description == null), null, root.Bar.Description), SomeField1 = root.Bar.SomeField1, SomeField2 = IIF((root.Bar.SomeField2 == null), null, root.Bar.SomeField2)})}).Take(11)
```

## Result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ORDER BY f0."Name", f0."Id"
## Expression 0

```text
[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Select(root => new Foo() {Id = root.Id, Name = root.Name, Bar = IIF((root.Bar == null), null, new Bar() {Id = root.Bar.Id, Description = root.Bar.Description, SomeField1 = root.Bar.SomeField1, SomeField2 = root.Bar.SomeField2})}).Take(11)
[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Select(root => new Foo() {Id = root.Id, Name = root.Name, Bar = IIF((root.Bar == null), null, new Bar() {Id = root.Bar.Id, Description = IIF((root.Bar.Description == null), null, root.Bar.Description), SomeField1 = root.Bar.SomeField1, SomeField2 = IIF((root.Bar.SomeField2 == null), null, root.Bar.SomeField2)})}).Take(11)
```

## Result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ORDER BY f0."Name", f0."Id"
## Expression 0

```text
[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Select(root => new Foo() {Id = root.Id, Name = root.Name, Bar = IIF((root.Bar == null), null, new Bar() {Id = root.Bar.Id, Description = root.Bar.Description})}).Take(11)
[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Select(root => new Foo() {Id = root.Id, Name = root.Name, Bar = IIF((root.Bar == null), null, new Bar() {Id = root.Bar.Id, Description = IIF((root.Bar.Description == null), null, root.Bar.Description)})}).Take(11)
```

## Result
Expand Down

0 comments on commit 109b60e

Please sign in to comment.