Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@ public static string GetDefaultColumnName(this IReadOnlyProperty property)
else if (StoreObjectIdentifier.Create(property.DeclaringType, currentStoreObject.StoreObjectType) == currentStoreObject
|| property.DeclaringType.GetMappingFragments(storeObject.StoreObjectType)
.Any(f => f.StoreObject == currentStoreObject)
|| (property.IsPrimaryKey()
|| ((property.IsPrimaryKey()
|| property.DeclaringType.ContainingEntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy)
&& property.DeclaringType.ContainingEntityType.GetDerivedTypes()
.Any(e => StoreObjectIdentifier.Create(e, currentStoreObject.StoreObjectType) == currentStoreObject)))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -865,7 +865,8 @@ public virtual SqlExpression Case(
return left;
// a == b ? null : a -> NULLIF(a, b)
case SqlConstantExpression { Value: null }:
return Function("NULLIF", [left, right], nullable: true, Statics.TrueFalse, left.Type, left.TypeMapping);
// NULLIF is also NULL when a == b, so its nullability doesn't follow from its arguments: never propagate.
return Function("NULLIF", [left, right], nullable: true, Statics.FalseArrays[2], left.Type, left.TypeMapping);
}
}

Expand All @@ -879,7 +880,7 @@ public virtual SqlExpression Case(
return right;
// a == b ? null : b -> NULLIF(b, a)
case SqlConstantExpression { Value: null }:
return Function("NULLIF", [right, left], nullable: true, Statics.TrueFalse, right.Type, right.TypeMapping);
return Function("NULLIF", [right, left], nullable: true, Statics.FalseArrays[2], right.Type, right.TypeMapping);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ public JsonCollectionOfNullableStructsReaderWriter(JsonValueReaderWriter<TElemen
case JsonTokenType.Number:
case JsonTokenType.True:
case JsonTokenType.False:
collection.Add(_elementReaderWriter.FromJsonTyped(ref manager));
collection.Add(
_elementReaderWriter.GetType() == typeof(JsonCastValueReaderWriter<TElement>)
? (TElement?)((ICompositeJsonValueReaderWriter)_elementReaderWriter).InnerReaderWriter.FromJson(ref manager)
: _elementReaderWriter.FromJsonTyped(ref manager));
break;
case JsonTokenType.Null:
collection.Add(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,16 @@ public override Task Can_read_write_collection_of_nullable_int_with_converter_JS
// Cosmos provider cannot map collections of elements with converters. See Issue #34026.
=> Assert.ThrowsAsync<InvalidOperationException>(base.Can_read_write_collection_of_nullable_int_with_converter_JSON_values);

public override void Can_read_write_collection_of_nullable_int_with_null_returning_converter(bool existingCollection)
// Cosmos provider cannot map collections of elements with converters. See Issue #34026.
=> Assert.Throws<InvalidOperationException>(
() => base.Can_read_write_collection_of_nullable_int_with_null_returning_converter(existingCollection));

public override void Can_read_write_collection_of_nullable_struct_with_null_returning_converter(bool existingCollection)
// Cosmos provider cannot map collections of elements with converters. See Issue #34026.
=> Assert.Throws<InvalidOperationException>(
() => base.Can_read_write_collection_of_nullable_struct_with_null_returning_converter(existingCollection));

public override Task Can_read_write_collection_of_nullable_IP_address_JSON_values()
// Cosmos provider cannot map collections of elements with converters. See Issue #34026.
=> Assert.ThrowsAsync<InvalidOperationException>(base.Can_read_write_collection_of_nullable_IP_address_JSON_values);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,42 @@ FROM root c
""");
}

public override async Task Conditional_uncoalesce_is_null()
{
await base.Conditional_uncoalesce_is_null();

AssertSql(
"""
SELECT VALUE c
FROM root c
WHERE ((c["Int"] = 8) OR (c["Int"] = null))
""");
}

public override async Task Conditional_uncoalesce_is_not_null_with_nullable_column()
{
await base.Conditional_uncoalesce_is_not_null_with_nullable_column();

AssertSql(
"""
SELECT VALUE c
FROM root c
WHERE (((c["Int"] = 8) ? null : c["Int"]) != null)
""");
}

public override async Task Conditional_uncoalesce_with_coalesce()
{
await base.Conditional_uncoalesce_with_coalesce();

AssertSql(
"""
SELECT VALUE c
FROM root c
WHERE (((((c["Int"] = 0) ? null : c["Int"]) != null) ? ((c["Int"] = 0) ? null : c["Int"]) : -1) < 0)
""");
}

public override async Task Coalesce()
{
await base.Coalesce();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,11 +546,14 @@ private static void AssertViews(IRelationalModel model, Mapping mapping)

Assert.Null(customerView.EntityTypeMappings.Single().IsSharedTablePrincipal);
Assert.Null(customerView.EntityTypeMappings.Single().IsSplitEntityTypePrincipal);
Assert.Equal(5, customerView.Columns.Count());
Assert.Equal(6, customerView.Columns.Count());
Assert.Same(
abstractBaseType.FindComplexProperty(nameof(AbstractBase.Tag))!.ComplexType.FindProperty(nameof(Tag.Name)),
Assert.Single(customerView.FindColumn("Tag_Name")!.PropertyMappings).Property);

Assert.Single(specialCustomerView.EntityTypeMappings);

Assert.Equal(9, specialCustomerView.Columns.Count());
Assert.Equal(10, specialCustomerView.Columns.Count());

Assert.False(specialtyColumn.IsNullable);

Expand All @@ -560,7 +563,7 @@ private static void AssertViews(IRelationalModel model, Mapping mapping)
Assert.False(extraSpecialCustomerView.EntityTypeMappings.Last().IsSharedTablePrincipal);
Assert.Null(extraSpecialCustomerView.EntityTypeMappings.Last().IsSplitEntityTypePrincipal);

Assert.Equal(11, extraSpecialCustomerView.Columns.Count());
Assert.Equal(12, extraSpecialCustomerView.Columns.Count());
}
}
}
Expand Down Expand Up @@ -788,6 +791,12 @@ private static void AssertTables(IRelationalModel model, Mapping mapping)

var complexType = abstractBaseType.GetComplexProperties().Single().ComplexType;

foreach (var columnMapping in complexType.GetTableMappings().SelectMany(m => m.ColumnMappings))
{
Assert.Equal("Tag_Name", columnMapping.Column.Name);
Assert.Same(complexType.FindProperty(nameof(Tag.Name)), columnMapping.Property);
}

if (mapping == Mapping.TPT)
{
var baseTable = abstractBaseType.GetTableMappings().Single().Table;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8594,6 +8594,50 @@ public void Change_TPT_to_TPC_with_FKs_and_seed_data()
Assert.Equal(ReferentialAction.Cascade, operation.OnDelete);
}));

[Fact]
public void Add_second_inherited_complex_property_in_TPC_does_not_reassign_existing_columns()
=> Execute(
common =>
{
common.Entity("Vessel", b =>
{
b.UseTpcMappingStrategy();
b.Property<int>("Id");
b.ComplexProperty<Address>("Shipping", c =>
{
c.IsRequired();
c.Ignore(a => a.AddressLine2);
});
});
common.Entity("Ferry").HasBaseType("Vessel");
},
_ => { },
target => target.Entity("Vessel").ComplexProperty<Address>("Billing", c =>
{
c.IsRequired();
c.Ignore(a => a.AddressLine2);
}),
upOperations =>
{
Assert.Equal(2, upOperations.Count);
Assert.Equal(["Ferry", "Vessel"], upOperations.Cast<AddColumnOperation>().Select(o => o.Table).Order());
foreach (var operation in upOperations)
{
var addColumn = Assert.IsType<AddColumnOperation>(operation);
Assert.Equal("Billing_AddressLine1", addColumn.Name);
}
},
downOperations =>
{
Assert.Equal(2, downOperations.Count);
Assert.Equal(["Ferry", "Vessel"], downOperations.Cast<DropColumnOperation>().Select(o => o.Table).Order());
foreach (var operation in downOperations)
{
var dropColumn = Assert.IsType<DropColumnOperation>(operation);
Assert.Equal("Billing_AddressLine1", dropColumn.Name);
}
});

[Fact]
public void Change_TPT_to_TPC_with_excluded_base()
=> Execute(
Expand Down
62 changes: 62 additions & 0 deletions src/efcore/test/EFCore.Specification.Tests/JsonTypesTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3080,6 +3080,68 @@ protected class NullableDddIdCollectionType
public IList<DddId?> DddId { get; set; } = null!;
}

[Theory, InlineData(false), InlineData(true)]
public virtual void Can_read_write_collection_of_nullable_int_with_null_returning_converter(bool existingCollection)
{
var contextFactory = CreateContextFactory<DbContext>(
b => b.Entity<NullableInt32CollectionType>().HasNoKey().PrimitiveCollection(e => e.Int32).ElementType()
.HasConversion(new ValueConverter<int?, int?>(
v => v == 0 ? null : v,
v => v == 0 ? null : v)));
using var context = contextFactory.CreateDbContext();
var readerWriter = context.Model.FindEntityType(typeof(NullableInt32CollectionType))!
.GetProperty(nameof(NullableInt32CollectionType.Int32)).GetTypeMapping().JsonValueReaderWriter!;

Assert.Equal("[5,null,null,7]", readerWriter.ToJsonString(new List<int?> { 5, 0, null, 7 }));

var existing = existingCollection ? new List<int?> { 99 } : null;
var result = readerWriter.FromJsonString("[5,0,null,7]", existing);
Assert.Equal(new int?[] { 5, null, null, 7 }, Assert.IsAssignableFrom<IEnumerable<int?>>(result));
if (existingCollection)
{
Assert.Same(existing, result);
}

#pragma warning disable EF9100 // Experimental API.
var compiledReaderWriter = Expression.Lambda<Func<JsonValueReaderWriter>>(readerWriter.ConstructorExpression).Compile()();
#pragma warning restore EF9100
Assert.Equal(
new int?[] { 5, null, null, 7 },
Assert.IsAssignableFrom<IEnumerable<int?>>(compiledReaderWriter.FromJsonString("[5,0,null,7]")));
}

[Theory, InlineData(false), InlineData(true)]
public virtual void Can_read_write_collection_of_nullable_struct_with_null_returning_converter(bool existingCollection)
{
var contextFactory = CreateContextFactory<DbContext>(
b => b.Entity<NullableDddIdCollectionType>().HasNoKey().PrimitiveCollection(e => e.DddId).ElementType()
.HasConversion(new ValueConverter<DddId?, int?>(
v => v!.Value.Id == 0 ? null : v.Value.Id,
v => v == 0 ? null : new DddId { Id = v!.Value })));
using var context = contextFactory.CreateDbContext();
var readerWriter = context.Model.FindEntityType(typeof(NullableDddIdCollectionType))!
.GetProperty(nameof(NullableDddIdCollectionType.DddId)).GetTypeMapping().JsonValueReaderWriter!;
var five = new DddId { Id = 5 };
var seven = new DddId { Id = 7 };

Assert.Equal("[5,null,null,7]", readerWriter.ToJsonString(new List<DddId?> { five, new DddId { Id = 0 }, null, seven }));

var existing = existingCollection ? new List<DddId?> { new DddId { Id = 99 } } : null;
var result = readerWriter.FromJsonString("[5,0,null,7]", existing);
Assert.Equal(new DddId?[] { five, null, null, seven }, Assert.IsAssignableFrom<IEnumerable<DddId?>>(result));
if (existingCollection)
{
Assert.Same(existing, result);
}

#pragma warning disable EF9100 // Experimental API.
var compiledReaderWriter = Expression.Lambda<Func<JsonValueReaderWriter>>(readerWriter.ConstructorExpression).Compile()();
#pragma warning restore EF9100
Assert.Equal(
new DddId?[] { five, null, null, seven },
Assert.IsAssignableFrom<IEnumerable<DddId?>>(compiledReaderWriter.FromJsonString("[5,0,null,7]")));
}

[Fact]
public virtual Task Can_read_write_binary_as_collection()
=> Can_read_and_write_JSON_collection_value<BinaryAsJsonType, byte[]>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ public virtual async Task Conditional_uncoalesce_with_inequality_right()
public virtual async Task Conditional_uncoalesce_with_string()
=> await AssertQuery(ss => ss.Set<BasicTypesEntity>().Where(x => (x.String == "Seattle" ? null : x.String) == "London"));

[Fact]
public virtual async Task Conditional_uncoalesce_is_null()
=> await AssertQuery(ss => ss.Set<BasicTypesEntity>().Where(x => (x.Int == 8 ? null : (int?)x.Int) == null));

[Fact]
public virtual async Task Conditional_uncoalesce_is_not_null_with_nullable_column()
=> await AssertQuery(ss => ss.Set<NullableBasicTypesEntity>().Where(x => (x.Int == 8 ? null : x.Int) != null));

[Fact]
public virtual async Task Conditional_uncoalesce_with_coalesce()
=> await AssertQuery(ss => ss.Set<BasicTypesEntity>().Where(x => ((x.Int == 0 ? null : (int?)x.Int) ?? -1) < 0));

[Fact]
public virtual async Task Coalesce()
=> await AssertQuery(ss => ss.Set<NullableBasicTypesEntity>().Where(b => (b.String ?? "Unknown") == "Seattle"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1309,10 +1309,10 @@ public override async Task Select_TPC_base_with_ComplexType()

AssertSql(
"""
SELECT [t].[Id], [t].[ChildProperty], NULL AS [ChildProperty1], [t].[PropertyInsideComplexThing], [t].[ChildComplexProperty_PropertyInsideComplexThing], NULL AS [ChildComplexProperty_PropertyInsideComplexThing1], N'TpcChild1' AS [Discriminator]
SELECT [t].[Id], [t].[ChildProperty], NULL AS [ChildProperty1], [t].[BaseComplexProperty_PropertyInsideComplexThing], [t].[ChildComplexProperty_PropertyInsideComplexThing], NULL AS [ChildComplexProperty_PropertyInsideComplexThing1], N'TpcChild1' AS [Discriminator]
FROM [TpcChild1] AS [t]
UNION ALL
SELECT [t0].[Id], NULL AS [ChildProperty], [t0].[ChildProperty] AS [ChildProperty1], [t0].[PropertyInsideComplexThing], NULL AS [ChildComplexProperty_PropertyInsideComplexThing], [t0].[ChildComplexProperty_PropertyInsideComplexThing] AS [ChildComplexProperty_PropertyInsideComplexThing1], N'TpcChild2' AS [Discriminator]
SELECT [t0].[Id], NULL AS [ChildProperty], [t0].[ChildProperty] AS [ChildProperty1], [t0].[BaseComplexProperty_PropertyInsideComplexThing], NULL AS [ChildComplexProperty_PropertyInsideComplexThing], [t0].[ChildComplexProperty_PropertyInsideComplexThing] AS [ChildComplexProperty_PropertyInsideComplexThing1], N'TpcChild2' AS [Discriminator]
FROM [TpcChild2] AS [t0]
""");
}
Expand Down
Loading
Loading