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

NullReferenceException when trying to seed a keyless entity #23030 #23562

Merged
merged 24 commits into from
Jan 11, 2021
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
73f5e79
NullReferenceException when trying to seed a keyless entity #23030
umitkavala Dec 2, 2020
3df299b
Test only for SqlServer
umitkavala Dec 2, 2020
4ff8f0d
Update SeedingSqliteTest.cs
umitkavala Dec 2, 2020
8072855
Update SeedingInMemoryTest.cs
umitkavala Dec 2, 2020
e05081d
Merge remote-tracking branch 'upstream/main' into seed-keyless-entity
umitkavala Dec 6, 2020
ac0de5c
Throw InvalidOperationException for keyless entity
umitkavala Dec 6, 2020
f6775b7
Update SeedingInMemoryTest.cs
umitkavala Dec 6, 2020
68cb50d
Move exception throwing to ModelValidator
umitkavala Dec 8, 2020
36e5611
Merge remote-tracking branch 'upstream/main' into seed-keyless-entity
umitkavala Dec 8, 2020
65c5cce
move validation to ValidateData function
umitkavala Dec 8, 2020
7187386
Update src/EFCore/Properties/CoreStrings.resx
umitkavala Dec 9, 2020
980331a
Update test/EFCore.InMemory.FunctionalTests/SeedingInMemoryTest.cs
umitkavala Dec 9, 2020
0d4b7ad
Update test/EFCore.Specification.Tests/SeedingTestBase.cs
umitkavala Dec 9, 2020
1194928
Update test/EFCore.SqlServer.FunctionalTests/SeedingSqlServerTest.cs
umitkavala Dec 9, 2020
900c11c
Update test/EFCore.Sqlite.FunctionalTests/SeedingSqliteTest.cs
umitkavala Dec 9, 2020
e67635a
Update test/EFCore.InMemory.FunctionalTests/SeedingInMemoryTest.cs
umitkavala Dec 9, 2020
ad3f39a
Update test/EFCore.SqlServer.FunctionalTests/SeedingSqlServerTest.cs
umitkavala Dec 9, 2020
cd951fe
Update test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs
umitkavala Dec 9, 2020
ee50650
correction of context names
umitkavala Dec 9, 2020
c947bed
Merge branch 'seed-keyless-entity' of https://github.com/umitkavala/e…
umitkavala Dec 9, 2020
47109fc
Move clean database to base
umitkavala Dec 10, 2020
546d546
Update SeedingTestBase.cs
umitkavala Dec 12, 2020
569b056
Merge remote-tracking branch 'upstream/main' into seed-keyless-entity
umitkavala Jan 9, 2021
adc26a2
Move KeylessSeedingContext initialization to the base
umitkavala Jan 9, 2021
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
4 changes: 4 additions & 0 deletions src/EFCore/Infrastructure/ModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,10 @@ protected virtual void ValidateData([NotNull] IModel model, [NotNull] IDiagnosti
var key = entityType.FindPrimaryKey();
if (key == null)
{
if (entityType.GetSeedData().Any())
{
throw new InvalidOperationException(CoreStrings.SeedKeylessEntity(entityType.DisplayName()));
}
continue;
}

Expand Down
8 changes: 8 additions & 0 deletions src/EFCore/Properties/CoreStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/EFCore/Properties/CoreStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,9 @@
<data name="SeedDatumSignedNumericValue" xml:space="preserve">
<value>The seed entity for entity type '{entityType}' cannot be added because a non-zero value is required for property '{property}'. Consider providing a negative value to avoid collisions with non-seed data.</value>
</data>
<data name="SeedKeylessEntity" xml:space="preserve">
<value>The seed entity for entity type '{entityType}' cannot be added because keyless entity types are not supported. Consider providing a key or removing the seed data.</value>
</data>
<data name="SelfReferencingNavigationWithInverseProperty" xml:space="preserve">
<value>The inverse for the navigation '{entityType}.{property}' cannot be the same navigation. Change the value in the [InverseProperty] attribute to a different navigation.</value>
</data>
Expand Down Expand Up @@ -1464,4 +1467,4 @@
<data name="WrongStateManager" xml:space="preserve">
<value>Cannot start tracking the entry for entity type '{entityType}' because it was created by a different StateManager instance.</value>
</data>
</root>
</root>
18 changes: 18 additions & 0 deletions test/EFCore.InMemory.FunctionalTests/SeedingInMemoryTest.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.EntityFrameworkCore.TestUtilities;

namespace Microsoft.EntityFrameworkCore
{
public class SeedingInMemoryTest : SeedingTestBase
{
protected override TestStore TestStore => InMemoryTestStore.Create("inMemoryTestStore");
umitkavala marked this conversation as resolved.
Show resolved Hide resolved

protected override SeedingContext CreateContextWithEmptyDatabase(string testId)
=> new SeedingInMemoryContext(testId);

protected override KeylessSeedingContext CreateKeylessContextWithEmptyDatabase(string testId)
=> new KeylessSeedingInMemoryContext(testId);

protected class SeedingInMemoryContext : SeedingContext
{
public SeedingInMemoryContext(string testId)
Expand All @@ -18,5 +25,16 @@ public SeedingInMemoryContext(string testId)
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseInMemoryDatabase($"Seeds{TestId}");
umitkavala marked this conversation as resolved.
Show resolved Hide resolved
}

protected class KeylessSeedingInMemoryContext : KeylessSeedingContext
umitkavala marked this conversation as resolved.
Show resolved Hide resolved
{
public KeylessSeedingInMemoryContext(string testId)
: base(testId)
{
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseInMemoryDatabase($"KeylessSeeds{TestId}");
umitkavala marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
45 changes: 45 additions & 0 deletions test/EFCore.Specification.Tests/SeedingTestBase.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;

Expand All @@ -17,6 +19,7 @@ public abstract class SeedingTestBase
public virtual async Task Seeding_does_not_leave_context_contaminated(bool async)
{
using var context = CreateContextWithEmptyDatabase(async ? "1A" : "1S");
TestStore.Clean(context);
var _ = async
? await context.Database.EnsureCreatedResilientlyAsync()
: context.Database.EnsureCreatedResiliently();
Expand All @@ -31,8 +34,29 @@ public virtual async Task Seeding_does_not_leave_context_contaminated(bool async
Assert.Equal("Orange", seeds[1].Species);
}

[ConditionalTheory]
[InlineData(false)]
[InlineData(true)]
public virtual async Task Seeding_keyless_entity_throws_exception(bool async)
{
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
async () =>
{
using var context = CreateKeylessContextWithEmptyDatabase(async ? "1A" : "1S");
TestStore.Clean(context);
var _ = async
? await context.Database.EnsureCreatedResilientlyAsync()
: context.Database.EnsureCreatedResiliently();
});
Assert.Equal(CoreStrings.SeedKeylessEntity(nameof(KeylessSeed)), exception.Message);
}

protected abstract TestStore TestStore { get; }

protected abstract SeedingContext CreateContextWithEmptyDatabase(string testId);

protected abstract KeylessSeedingContext CreateKeylessContextWithEmptyDatabase(string testId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
protected abstract KeylessSeedingContext CreateKeylessContextWithEmptyDatabase(string testId);
protected virtual KeylessSeedingContext CreateKeylessContextWithEmptyDatabase()
=> new KeylessSeedingContext(TestStore.AddProviderOptions(new DbContextOptionsBuilder()).Options)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KeylessSeedingContext won't be abstract anymore and we won't need KeylessSeedingContext implementations on other classes (KeylessSeedingSqlServerContext, KeylessSeedingSqliteContext, KeylessSeedingInMemoryContext)

public class KeylessSeedingContext : DbContext
        {
            public KeylessSeedingContext(DbContextOptions options)
                : base(options)
            {
            }


protected abstract class SeedingContext : DbContext
{
public string TestId { get; }
Expand All @@ -54,5 +78,26 @@ protected class Seed

public string Species { get; set; }
}

protected abstract class KeylessSeedingContext : DbContext
{
public string TestId { get; }

protected KeylessSeedingContext(string testId)
=> TestId = testId;
umitkavala marked this conversation as resolved.
Show resolved Hide resolved

protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<KeylessSeed>()
.HasNoKey()
.HasData(
new KeylessSeed { Species = "Apple" },
new KeylessSeed { Species = "Orange" }
);
}

protected class KeylessSeed
{
public string Species { get; set; }
}
}
}
24 changes: 18 additions & 6 deletions test/EFCore.SqlServer.FunctionalTests/SeedingSqlServerTest.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;

namespace Microsoft.EntityFrameworkCore
{
public class SeedingSqlServerTest : SeedingTestBase
{
protected override SeedingContext CreateContextWithEmptyDatabase(string testId)
{
var context = new SeedingSqlServerContext(testId);
protected override TestStore TestStore => SqlServerTestStore.Create("sqlServerTestStore");
umitkavala marked this conversation as resolved.
Show resolved Hide resolved

context.Database.EnsureClean();
protected override SeedingContext CreateContextWithEmptyDatabase(string testId)
=> new SeedingSqlServerContext(testId);

return context;
}
protected override KeylessSeedingContext CreateKeylessContextWithEmptyDatabase(string testId)
=> new KeylessSeedingSqlServerContext(testId);

protected class SeedingSqlServerContext : SeedingContext
{
Expand All @@ -26,5 +28,15 @@ public SeedingSqlServerContext(string testId)
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(SqlServerTestStore.CreateConnectionString($"Seeds{TestId}"));
}
protected class KeylessSeedingSqlServerContext : KeylessSeedingContext
{
public KeylessSeedingSqlServerContext(string testId)
: base(testId)
{
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(SqlServerTestStore.CreateConnectionString($"KeylessSeeds{TestId}"));
}
}
}
24 changes: 18 additions & 6 deletions test/EFCore.Sqlite.FunctionalTests/SeedingSqliteTest.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.EntityFrameworkCore.TestUtilities;

namespace Microsoft.EntityFrameworkCore
{
public class SeedingSqliteTest : SeedingTestBase
{
protected override TestStore TestStore => SqliteTestStore.Create("sqlliteTestStore");

protected override SeedingContext CreateContextWithEmptyDatabase(string testId)
{
var context = new SeedingSqliteContext(testId);
=> new SeedingSqliteContext(testId);

context.Database.EnsureClean();

return context;
}
protected override KeylessSeedingContext CreateKeylessContextWithEmptyDatabase(string testId)
=> new KeylessSeedingSqliteContext(testId);

protected class SeedingSqliteContext : SeedingContext
{
Expand All @@ -24,5 +25,16 @@ public SeedingSqliteContext(string testId)
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlite(($"Data Source = Seeds{TestId}.db"));
}

protected class KeylessSeedingSqliteContext : KeylessSeedingContext
{
public KeylessSeedingSqliteContext(string testId)
: base(testId)
{
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlite(($"Data Source = KeylessSeeds{TestId}.db"));
}
}
}
18 changes: 18 additions & 0 deletions test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,24 @@ public virtual void Shared_type_inheritance_throws()
VerifyError(CoreStrings.SharedTypeDerivedType("Shared2 (C)"), modelBuilder.Model);
}

[ConditionalFact]
public virtual void Seeding_keyless_entity_throws()
{
var modelBuilder = CreateConventionalModelBuilder();
modelBuilder.Entity<KeylessSeed>(
e =>
{
e.HasNoKey();
e.HasData(
new KeylessSeed
{
Species = "Apple"
});
});

VerifyError(CoreStrings.SeedKeylessEntity(nameof(KeylessSeed)), modelBuilder.Model);
}

// INotify interfaces not really implemented; just marking the classes to test metadata construction
private class FullNotificationEntity : INotifyPropertyChanging, INotifyPropertyChanged
{
Expand Down
5 changes: 5 additions & 0 deletions test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,11 @@ protected class Product
public virtual ICollection<Order> Orders { get; set; }
}

protected class KeylessSeed
umitkavala marked this conversation as resolved.
Show resolved Hide resolved
{
public string Species { get; set; }
}

protected ModelValidatorTestBase()
=> LoggerFactory = new ListLoggerFactory(l => l == DbLoggerCategory.Model.Validation.Name || l == DbLoggerCategory.Model.Name);

Expand Down