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 6 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
6 changes: 5 additions & 1 deletion src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1585,7 +1585,11 @@ public virtual bool IsKeyUnknown
{
get
{
var keyProperties = ((EntityType)EntityType).FindPrimaryKey().Properties;
var keyProperties = ((EntityType)EntityType).FindPrimaryKey()?.Properties;
if (keyProperties == null)
umitkavala marked this conversation as resolved.
Show resolved Hide resolved
{
throw new InvalidOperationException(CoreStrings.SeedKeylessEntity(EntityType.DisplayName()));
}
// ReSharper disable once ForCanBeConvertedToForeach
// ReSharper disable once LoopCanBeConvertedToQuery
for (var i = 0; i < keyProperties.Count; i++)
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.

3 changes: 3 additions & 0 deletions src/EFCore/Properties/CoreStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,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 to seed data or use this entity for query only.</value>
umitkavala marked this conversation as resolved.
Show resolved Hide resolved
</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
13 changes: 13 additions & 0 deletions test/EFCore.InMemory.FunctionalTests/SeedingInMemoryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,26 @@ public class SeedingInMemoryTest : SeedingTestBase
protected override SeedingContext CreateContextWithEmptyDatabase(string testId)
=> new SeedingInMemoryContext(testId);

protected override KeylessSeedingContext CreateKeylessContextWithEmptyDatabase(string testId)
=> new KeylessSeedingInMemoryContext(testId);
umitkavala marked this conversation as resolved.
Show resolved Hide resolved

protected class SeedingInMemoryContext : SeedingContext
{
public SeedingInMemoryContext(string testId)
: base(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($"Seeds{TestId}");
}
Expand Down
47 changes: 47 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 Down Expand Up @@ -31,8 +33,32 @@ 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");
var _ = async
? await context.Database.EnsureCreatedResilientlyAsync()
: context.Database.EnsureCreatedResiliently();

Assert.Empty(context.ChangeTracker.Entries());

var seeds = context.Set<KeylessSeed>().OrderBy(e => e.Species).ToList();

Assert.Empty(seeds);
umitkavala marked this conversation as resolved.
Show resolved Hide resolved
});
Assert.Equal(CoreStrings.SeedKeylessEntity(nameof(KeylessSeed)), exception.Message);
}

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 +80,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; }
}
}
}
23 changes: 23 additions & 0 deletions test/EFCore.SqlServer.FunctionalTests/SeedingSqlServerTest.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// 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
{
Expand All @@ -16,6 +19,15 @@ protected override SeedingContext CreateContextWithEmptyDatabase(string testId)
return context;
}

protected override KeylessSeedingContext CreateKeylessContextWithEmptyDatabase(string testId)
{
var context = new KeylessSeedingInMemoryContext(testId);

context.Database.EnsureClean();

return context;
}
umitkavala marked this conversation as resolved.
Show resolved Hide resolved

protected class SeedingSqlServerContext : SeedingContext
{
public SeedingSqlServerContext(string testId)
Expand All @@ -26,5 +38,16 @@ public SeedingSqlServerContext(string testId)
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(SqlServerTestStore.CreateConnectionString($"Seeds{TestId}"));
}
protected class KeylessSeedingInMemoryContext : KeylessSeedingContext
umitkavala marked this conversation as resolved.
Show resolved Hide resolved
umitkavala marked this conversation as resolved.
Show resolved Hide resolved
{
public KeylessSeedingInMemoryContext(string testId)
: base(testId)
{
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(SqlServerTestStore.CreateConnectionString($"Seeds{TestId}"));
umitkavala marked this conversation as resolved.
Show resolved Hide resolved
}

umitkavala marked this conversation as resolved.
Show resolved Hide resolved
}
}
19 changes: 19 additions & 0 deletions test/EFCore.Sqlite.FunctionalTests/SeedingSqliteTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,32 @@ protected override SeedingContext CreateContextWithEmptyDatabase(string testId)
return context;
}

protected override KeylessSeedingContext CreateKeylessContextWithEmptyDatabase(string testId)
{
var context = new KeylessSeedingInMemoryContext(testId);

context.Database.EnsureClean();

return context;
}

protected class SeedingSqliteContext : SeedingContext
{
public SeedingSqliteContext(string testId)
: base(testId)
{
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlite(($"Data Source = Seeds{TestId}.db"));
}
protected class KeylessSeedingInMemoryContext : KeylessSeedingContext
umitkavala marked this conversation as resolved.
Show resolved Hide resolved
umitkavala marked this conversation as resolved.
Show resolved Hide resolved
{
public KeylessSeedingInMemoryContext(string testId)
umitkavala marked this conversation as resolved.
Show resolved Hide resolved
: base(testId)
{
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlite(($"Data Source = Seeds{TestId}.db"));
}
Expand Down