From af2409541389a7aed6c1ca1a5b4e7d236df93239 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 19 May 2017 09:17:21 -0700 Subject: [PATCH] Collapse and unify property value behaviors Part of issue #7913 This change obsoletes ReadOnlyBeforeSave, ReadOnlyAferSave, and StoreGeneratedAlways into two unified facets--one for before-save behavior (Added objects) and one for after-save behavior. In each case the property value can be: - Ignore: value never sent to database. (Equivalent to StoreGeneratedAlways, but can apply to any property and can be set differently for before and after save.) - Throw: any non-CLR default value set or property marked as modified causes exception. (Equivalent to previous read-only behaviors.) - UseValue: the value is used if it has been set (to non-CLR default for store-generated) or marked as modified. Also covers issue #7914 - makes Ignore the default for before and after save when using AddOrUpdate (computed) properties. --- .../RelationalEntityTypeBuilderAnnotations.cs | 4 +- .../Update/ModificationCommand.cs | 9 +- .../StoreGeneratedTestBase.cs | 739 +++++++++++++++--- .../TestUtilities/Extensions.cs | 4 +- .../TestUtilities/PropertyComparer.cs | 4 +- .../Internal/InternalEntityEntry.cs | 13 +- src/EFCore/Metadata/IMutableProperty.cs | 63 +- src/EFCore/Metadata/IProperty.cs | 53 +- .../Metadata/Internal/EntityTypeExtensions.cs | 3 +- .../Internal/InternalPropertyBuilder.cs | 61 +- src/EFCore/Metadata/Internal/Property.cs | 242 +++--- .../Metadata/Internal/PropertyExtensions.cs | 13 +- src/EFCore/Metadata/PropertyValueBehavior.cs | 27 + src/EFCore/breakingchanges.netcore.json | 30 + src/EFCore/breakingchanges.netframework.json | 30 + .../StoreGeneratedInMemoryTest.cs | 197 ----- .../SqlServerValueGenerationScenariosTest.cs | 19 +- .../StoreGeneratedSqlServerTest.cs | 78 +- .../StoreGeneratedSqliteTest.cs | 103 +-- .../Internal/InternalEntityEntryTestBase.cs | 4 + .../Metadata/Internal/EntityTypeTest.cs | 30 +- .../Internal/InternalPropertyBuilderTest.cs | 62 +- .../Metadata/Internal/PropertyTest.cs | 20 +- 23 files changed, 1144 insertions(+), 664 deletions(-) create mode 100644 src/EFCore/Metadata/PropertyValueBehavior.cs delete mode 100644 test/EFCore.InMemory.FunctionalTests/StoreGeneratedInMemoryTest.cs diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeBuilderAnnotations.cs b/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeBuilderAnnotations.cs index 2c143a456b6..288afe64be9 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeBuilderAnnotations.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeBuilderAnnotations.cs @@ -243,9 +243,7 @@ private DiscriminatorBuilder DiscriminatorBuilder( } propertyBuilder.IsRequired(true, configurationSource); - // TODO: #2132 - //propertyBuilder.ReadOnlyBeforeSave(true, configurationSource); - propertyBuilder.ReadOnlyAfterSave(true, configurationSource); + propertyBuilder.AfterSave(PropertyValueBehavior.Throw, configurationSource); propertyBuilder.HasValueGenerator( (property, entityType) => new DiscriminatorValueGenerator(GetAnnotations(entityType).DiscriminatorValue), configurationSource); diff --git a/src/EFCore.Relational/Update/ModificationCommand.cs b/src/EFCore.Relational/Update/ModificationCommand.cs index 0a33d368f12..9deef46e52a 100644 --- a/src/EFCore.Relational/Update/ModificationCommand.cs +++ b/src/EFCore.Relational/Update/ModificationCommand.cs @@ -6,6 +6,7 @@ using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Update.Internal; @@ -137,7 +138,13 @@ private IReadOnlyList GenerateColumnModifications() var isConcurrencyToken = property.IsConcurrencyToken; var isCondition = !adding && (isKey || isConcurrencyToken); var readValue = entry.IsStoreGenerated(property); - var writeValue = !readValue && (adding || entry.IsModified(property)); + + var writeValue = !readValue + && ((adding + && property.BeforeSaveBehavior == PropertyValueBehavior.UseValue) + || (!adding + && property.AfterSaveBehavior == PropertyValueBehavior.UseValue + && entry.IsModified(property))); if (readValue || writeValue diff --git a/src/EFCore.Specification.Tests/StoreGeneratedTestBase.cs b/src/EFCore.Specification.Tests/StoreGeneratedTestBase.cs index 33401c7f81c..ce6b69750ef 100644 --- a/src/EFCore.Specification.Tests/StoreGeneratedTestBase.cs +++ b/src/EFCore.Specification.Tests/StoreGeneratedTestBase.cs @@ -3,8 +3,10 @@ using System; using System.Linq; +using System.Reflection; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage; using Xunit; @@ -20,17 +22,382 @@ protected StoreGeneratedTestBase(TFixture fixture) TestStore = Fixture.CreateTestStore(); } + [Theory] + [InlineData("NeverThrowBeforeUseAfter")] + [InlineData("NeverThrowBeforeIgnoreAfter")] + [InlineData("NeverThrowBeforeThrowAfter")] + [InlineData("OnAddThrowBeforeUseAfter")] + [InlineData("OnAddThrowBeforeIgnoreAfter")] + [InlineData("OnAddThrowBeforeThrowAfter")] + [InlineData("OnAddOrUpdateThrowBeforeUseAfter")] + [InlineData("OnAddOrUpdateThrowBeforeIgnoreAfter")] + [InlineData("OnAddOrUpdateThrowBeforeThrowAfter")] + public virtual void Before_save_throw_always_throws_if_value_set(string propertyName) + { + ExecuteWithStrategyInTransaction( + context => + { + context.Add(WithValue(propertyName)); + + Assert.Equal( + CoreStrings.PropertyReadOnlyBeforeSave(propertyName, "Anais"), + Assert.Throws(() => context.SaveChanges()).Message); + }); + } + + [Theory] + [InlineData("NeverThrowBeforeUseAfter", null)] + [InlineData("NeverThrowBeforeIgnoreAfter", null)] + [InlineData("NeverThrowBeforeThrowAfter", null)] + [InlineData("OnAddThrowBeforeUseAfter", "Rabbit")] + [InlineData("OnAddThrowBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddThrowBeforeThrowAfter", "Rabbit")] + [InlineData("OnAddOrUpdateThrowBeforeUseAfter", "Rabbit")] + [InlineData("OnAddOrUpdateThrowBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddOrUpdateThrowBeforeThrowAfter", "Rabbit")] + public virtual void Before_save_throw_ignores_value_if_not_set(string propertyName, string expectedValue) + { + var id = 0; + ExecuteWithStrategyInTransaction( + context => + { + var entity = context.Add(new Anais()).Entity; + + context.SaveChanges(); + + id = entity.Id; + }, + context => { Assert.Equal(expectedValue, GetValue(context.Anaises.Find(id), propertyName)); }); + } + + [Theory] + [InlineData("Never")] + [InlineData("OnAdd")] + [InlineData("NeverUseBeforeUseAfter")] + [InlineData("NeverUseBeforeIgnoreAfter")] + [InlineData("NeverUseBeforeThrowAfter")] + [InlineData("OnAddUseBeforeUseAfter")] + [InlineData("OnAddUseBeforeIgnoreAfter")] + [InlineData("OnAddUseBeforeThrowAfter")] + [InlineData("OnAddOrUpdateUseBeforeUseAfter")] + [InlineData("OnAddOrUpdateUseBeforeIgnoreAfter")] + [InlineData("OnAddOrUpdateUseBeforeThrowAfter")] + public virtual void Before_save_use_always_uses_value_if_set(string propertyName) + { + var id = 0; + ExecuteWithStrategyInTransaction( + context => + { + var entity = context.Add(WithValue(propertyName)).Entity; + + context.SaveChanges(); + + id = entity.Id; + }, + context => { Assert.Equal("Pink", GetValue(context.Anaises.Find(id), propertyName)); }); + } + + [Theory] + [InlineData("Never", null)] + [InlineData("OnAdd", "Rabbit")] + [InlineData("NeverUseBeforeUseAfter", null)] + [InlineData("NeverUseBeforeIgnoreAfter", null)] + [InlineData("NeverUseBeforeThrowAfter", null)] + [InlineData("OnAddUseBeforeUseAfter", "Rabbit")] + [InlineData("OnAddUseBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddUseBeforeThrowAfter", "Rabbit")] + [InlineData("OnAddOrUpdateUseBeforeUseAfter", "Rabbit")] + [InlineData("OnAddOrUpdateUseBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddOrUpdateUseBeforeThrowAfter", "Rabbit")] + public virtual void Before_save_use_ignores_value_if_not_set(string propertyName, string expectedValue) + { + var id = 0; + ExecuteWithStrategyInTransaction( + context => + { + var entity = context.Add(new Anais()).Entity; + + context.SaveChanges(); + + id = entity.Id; + }, + context => { Assert.Equal(expectedValue, GetValue(context.Anaises.Find(id), propertyName)); }); + } + + [Theory] + [InlineData("OnAddOrUpdate", "Rabbit")] + [InlineData("NeverIgnoreBeforeUseAfter", null)] + [InlineData("NeverIgnoreBeforeIgnoreAfter", null)] + [InlineData("NeverIgnoreBeforeThrowAfter", null)] + [InlineData("OnAddIgnoreBeforeUseAfter", "Rabbit")] + [InlineData("OnAddIgnoreBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddIgnoreBeforeThrowAfter", "Rabbit")] + [InlineData("OnAddOrUpdateIgnoreBeforeUseAfter", "Rabbit")] + [InlineData("OnAddOrUpdateIgnoreBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddOrUpdateIgnoreBeforeThrowAfter", "Rabbit")] + public virtual void Before_save_ignore_ignores_value_if_not_set(string propertyName, string expectedValue) + { + var id = 0; + ExecuteWithStrategyInTransaction( + context => + { + var entity = context.Add(new Anais()).Entity; + + context.SaveChanges(); + + id = entity.Id; + }, + context => { Assert.Equal(expectedValue, GetValue(context.Anaises.Find(id), propertyName)); }); + } + + [Theory] + [InlineData("OnAddOrUpdate", "Rabbit")] + [InlineData("NeverIgnoreBeforeUseAfter", null)] + [InlineData("NeverIgnoreBeforeIgnoreAfter", null)] + [InlineData("NeverIgnoreBeforeThrowAfter", null)] + [InlineData("OnAddIgnoreBeforeUseAfter", "Rabbit")] + [InlineData("OnAddIgnoreBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddIgnoreBeforeThrowAfter", "Rabbit")] + [InlineData("OnAddOrUpdateIgnoreBeforeUseAfter", "Rabbit")] + [InlineData("OnAddOrUpdateIgnoreBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddOrUpdateIgnoreBeforeThrowAfter", "Rabbit")] + public virtual void Before_save_ignore_ignores_value_even_if_set(string propertyName, string expectedValue) + { + var id = 0; + ExecuteWithStrategyInTransaction( + context => + { + var entity = context.Add(WithValue(propertyName)).Entity; + + context.SaveChanges(); + + id = entity.Id; + }, + context => { Assert.Equal(expectedValue, GetValue(context.Anaises.Find(id), propertyName)); }); + } + + [Theory] + [InlineData("NeverUseBeforeThrowAfter")] + [InlineData("NeverIgnoreBeforeThrowAfter")] + [InlineData("NeverThrowBeforeThrowAfter")] + [InlineData("OnAddUseBeforeThrowAfter")] + [InlineData("OnAddIgnoreBeforeThrowAfter")] + [InlineData("OnAddThrowBeforeThrowAfter")] + [InlineData("OnAddOrUpdateUseBeforeThrowAfter")] + [InlineData("OnAddOrUpdateIgnoreBeforeThrowAfter")] + [InlineData("OnAddOrUpdateThrowBeforeThrowAfter")] + public virtual void After_save_throw_always_throws_if_value_modified(string propertyName) + { + ExecuteWithStrategyInTransaction( + context => + { + context.Attach(WithValue(propertyName, 1)).Property(propertyName).IsModified = true; + + Assert.Equal( + CoreStrings.PropertyReadOnlyAfterSave(propertyName, "Anais"), + Assert.Throws(() => context.SaveChanges()).Message); + }); + } + + [Theory] + [InlineData("NeverUseBeforeThrowAfter", null)] + [InlineData("NeverIgnoreBeforeThrowAfter", null)] + [InlineData("NeverThrowBeforeThrowAfter", null)] + [InlineData("OnAddUseBeforeThrowAfter", "Rabbit")] + [InlineData("OnAddIgnoreBeforeThrowAfter", "Rabbit")] + [InlineData("OnAddThrowBeforeThrowAfter", "Rabbit")] + [InlineData("OnAddOrUpdateUseBeforeThrowAfter", "Rabbit")] + [InlineData("OnAddOrUpdateIgnoreBeforeThrowAfter", "Rabbit")] + [InlineData("OnAddOrUpdateThrowBeforeThrowAfter", "Rabbit")] + public virtual void After_save_throw_ignores_value_if_not_modified(string propertyName, string expectedValue) + { + var id = 0; + ExecuteWithStrategyInTransaction( + context => + { + var entity = context.Add(new Anais()).Entity; + + context.SaveChanges(); + + id = entity.Id; + }, + context => + { + var entry = context.Entry(context.Anaises.Find(id)); + entry.State = EntityState.Modified; + entry.Property(propertyName).CurrentValue = "Daisy"; + entry.Property(propertyName).IsModified = false; + + context.SaveChanges(); + }, + context => { Assert.Equal(expectedValue, GetValue(context.Anaises.Find(id), propertyName)); }); + } + + [Theory] + [InlineData("OnAddOrUpdate", "Rabbit")] + [InlineData("NeverUseBeforeIgnoreAfter", null)] + [InlineData("NeverIgnoreBeforeIgnoreAfter", null)] + [InlineData("NeverThrowBeforeIgnoreAfter", null)] + [InlineData("OnAddUseBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddIgnoreBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddThrowBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddOrUpdateUseBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddOrUpdateIgnoreBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddOrUpdateThrowBeforeIgnoreAfter", "Rabbit")] + public virtual void After_save_ignore_ignores_value_if_not_modified(string propertyName, string expectedValue) + { + var id = 0; + ExecuteWithStrategyInTransaction( + context => + { + var entity = context.Add(new Anais()).Entity; + + context.SaveChanges(); + + id = entity.Id; + }, + context => + { + var entry = context.Entry(context.Anaises.Find(id)); + entry.State = EntityState.Modified; + entry.Property(propertyName).CurrentValue = "Daisy"; + entry.Property(propertyName).IsModified = false; + + context.SaveChanges(); + }, + context => { Assert.Equal(expectedValue, GetValue(context.Anaises.Find(id), propertyName)); }); + } + + [Theory] + [InlineData("OnAddOrUpdate", "Rabbit")] + [InlineData("NeverUseBeforeIgnoreAfter", null)] + [InlineData("NeverIgnoreBeforeIgnoreAfter", null)] + [InlineData("NeverThrowBeforeIgnoreAfter", null)] + [InlineData("OnAddUseBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddIgnoreBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddThrowBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddOrUpdateUseBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddOrUpdateIgnoreBeforeIgnoreAfter", "Rabbit")] + [InlineData("OnAddOrUpdateThrowBeforeIgnoreAfter", "Rabbit")] + public virtual void After_save_ignore_ignores_value_even_if_modified(string propertyName, string expectedValue) + { + var id = 0; + ExecuteWithStrategyInTransaction( + context => + { + var entity = context.Add(new Anais()).Entity; + + context.SaveChanges(); + + id = entity.Id; + }, + context => + { + var entry = context.Entry(context.Anaises.Find(id)); + entry.State = EntityState.Modified; + entry.Property(propertyName).CurrentValue = "Daisy"; + entry.Property(propertyName).IsModified = true; + + context.SaveChanges(); + }, + context => { Assert.Equal(expectedValue, GetValue(context.Anaises.Find(id), propertyName)); }); + } + + [Theory] + [InlineData("Never", null)] + [InlineData("OnAdd", "Rabbit")] + [InlineData("NeverUseBeforeUseAfter", null)] + [InlineData("NeverIgnoreBeforeUseAfter", null)] + [InlineData("NeverThrowBeforeUseAfter", null)] + [InlineData("OnAddUseBeforeUseAfter", "Rabbit")] + [InlineData("OnAddIgnoreBeforeUseAfter", "Rabbit")] + [InlineData("OnAddThrowBeforeUseAfter", "Rabbit")] + [InlineData("OnAddOrUpdateUseBeforeUseAfter", "Rabbit")] + [InlineData("OnAddOrUpdateIgnoreBeforeUseAfter", "Rabbit")] + [InlineData("OnAddOrUpdateThrowBeforeUseAfter", "Rabbit")] + public virtual void After_save_use_ignores_value_if_not_modified(string propertyName, string expectedValue) + { + var id = 0; + ExecuteWithStrategyInTransaction( + context => + { + var entity = context.Add(new Anais()).Entity; + + context.SaveChanges(); + + id = entity.Id; + }, + context => + { + var entry = context.Entry(context.Anaises.Find(id)); + entry.State = EntityState.Modified; + entry.Property(propertyName).CurrentValue = "Daisy"; + entry.Property(propertyName).IsModified = false; + + context.SaveChanges(); + }, + context => { Assert.Equal(expectedValue, GetValue(context.Anaises.Find(id), propertyName)); }); + } + + [Theory] + [InlineData("Never", "Daisy")] + [InlineData("OnAdd", "Daisy")] + [InlineData("NeverUseBeforeUseAfter", "Daisy")] + [InlineData("NeverIgnoreBeforeUseAfter", "Daisy")] + [InlineData("NeverThrowBeforeUseAfter", "Daisy")] + [InlineData("OnAddUseBeforeUseAfter", "Daisy")] + [InlineData("OnAddIgnoreBeforeUseAfter", "Daisy")] + [InlineData("OnAddThrowBeforeUseAfter", "Daisy")] + [InlineData("OnAddOrUpdateUseBeforeUseAfter", "Daisy")] + [InlineData("OnAddOrUpdateIgnoreBeforeUseAfter", "Daisy")] + [InlineData("OnAddOrUpdateThrowBeforeUseAfter", "Daisy")] + public virtual void After_save_use_uses_value_if_modified(string propertyName, string expectedValue) + { + var id = 0; + ExecuteWithStrategyInTransaction( + context => + { + var entity = context.Add(new Anais()).Entity; + + context.SaveChanges(); + + id = entity.Id; + }, + context => + { + var entry = context.Entry(context.Anaises.Find(id)); + entry.State = EntityState.Modified; + entry.Property(propertyName).CurrentValue = "Daisy"; + + context.SaveChanges(); + }, + context => { Assert.Equal(expectedValue, GetValue(context.Anaises.Find(id), propertyName)); }); + } + + private static Anais WithValue(string propertyName, int id = 0) + => SetValue(new Anais { Id = id }, propertyName); + + private static Anais SetValue(Anais entity, string propertyName) + { + entity.GetType().GetTypeInfo().GetDeclaredProperty(propertyName).SetValue(entity, "Pink"); + return entity; + } + + private static string GetValue(Anais entity, string propertyName) + => (string)entity.GetType().GetTypeInfo().GetDeclaredProperty(propertyName).GetValue(entity); + [Fact] public virtual void Identity_key_with_read_only_before_save_throws_if_explicit_values_set() { - ExecuteWithStrategyInTransaction(context => - { - context.Add(new Gumball { Id = 1 }); + ExecuteWithStrategyInTransaction( + context => + { + context.Add(new Gumball { Id = 1 }); - Assert.Equal( - CoreStrings.PropertyReadOnlyBeforeSave("Id", "Gumball"), - Assert.Throws(() => context.SaveChanges()).Message); - }); + Assert.Equal( + CoreStrings.PropertyReadOnlyBeforeSave("Id", "Gumball"), + Assert.Throws(() => context.SaveChanges()).Message); + }); } [Fact] @@ -73,14 +440,15 @@ public virtual void Identity_property_on_Added_entity_with_default_value_gets_va [Fact] public virtual void Identity_property_on_Added_entity_with_read_only_before_save_throws_if_explicit_values_set() { - ExecuteWithStrategyInTransaction(context => - { - context.Add(new Gumball { IdentityReadOnlyBeforeSave = "Masami" }); + ExecuteWithStrategyInTransaction( + context => + { + context.Add(new Gumball { IdentityReadOnlyBeforeSave = "Masami" }); - Assert.Equal( - CoreStrings.PropertyReadOnlyBeforeSave("IdentityReadOnlyBeforeSave", "Gumball"), - Assert.Throws(() => context.SaveChanges()).Message); - }); + Assert.Equal( + CoreStrings.PropertyReadOnlyBeforeSave("IdentityReadOnlyBeforeSave", "Gumball"), + Assert.Throws(() => context.SaveChanges()).Message); + }); } [Fact] @@ -230,14 +598,15 @@ public virtual void Always_identity_property_on_Added_entity_with_default_value_ [Fact] public virtual void Always_identity_property_on_Added_entity_with_read_only_before_save_throws_if_explicit_values_set() { - ExecuteWithStrategyInTransaction(context => - { - context.Add(new Gumball { AlwaysIdentityReadOnlyBeforeSave = "Masami" }); + ExecuteWithStrategyInTransaction( + context => + { + context.Add(new Gumball { AlwaysIdentityReadOnlyBeforeSave = "Masami" }); - Assert.Equal( - CoreStrings.PropertyReadOnlyBeforeSave("AlwaysIdentityReadOnlyBeforeSave", "Gumball"), - Assert.Throws(() => context.SaveChanges()).Message); - }); + Assert.Equal( + CoreStrings.PropertyReadOnlyBeforeSave("AlwaysIdentityReadOnlyBeforeSave", "Gumball"), + Assert.Throws(() => context.SaveChanges()).Message); + }); } [Fact] @@ -312,7 +681,7 @@ public virtual void Always_identity_property_on_Modified_entity_is_not_included_ Assert.Equal("Masami", gumball.AlwaysIdentity); }, - context => { Assert.Equal("Masami", context.Gumballs.Single(e => e.Id == id).AlwaysIdentity); }); + context => { Assert.Equal("Banana Joe", context.Gumballs.Single(e => e.Id == id).AlwaysIdentity); }); } [Fact] @@ -386,14 +755,15 @@ public virtual void Computed_property_on_Added_entity_with_default_value_gets_va [Fact] public virtual void Computed_property_on_Added_entity_with_read_only_before_save_throws_if_explicit_values_set() { - ExecuteWithStrategyInTransaction(context => - { - context.Add(new Gumball { ComputedReadOnlyBeforeSave = "Masami" }); + ExecuteWithStrategyInTransaction( + context => + { + context.Add(new Gumball { ComputedReadOnlyBeforeSave = "Masami" }); - Assert.Equal( - CoreStrings.PropertyReadOnlyBeforeSave("ComputedReadOnlyBeforeSave", "Gumball"), - Assert.Throws(() => context.SaveChanges()).Message); - }); + Assert.Equal( + CoreStrings.PropertyReadOnlyBeforeSave("ComputedReadOnlyBeforeSave", "Gumball"), + Assert.Throws(() => context.SaveChanges()).Message); + }); } [Fact] @@ -543,14 +913,15 @@ public virtual void Always_computed_property_on_Added_entity_with_default_value_ [Fact] public virtual void Always_computed_property_on_Added_entity_with_read_only_before_save_throws_if_explicit_values_set() { - ExecuteWithStrategyInTransaction(context => - { - context.Add(new Gumball { AlwaysComputedReadOnlyBeforeSave = "Masami" }); + ExecuteWithStrategyInTransaction( + context => + { + context.Add(new Gumball { AlwaysComputedReadOnlyBeforeSave = "Masami" }); - Assert.Equal( - CoreStrings.PropertyReadOnlyBeforeSave("AlwaysComputedReadOnlyBeforeSave", "Gumball"), - Assert.Throws(() => context.SaveChanges()).Message); - }); + Assert.Equal( + CoreStrings.PropertyReadOnlyBeforeSave("AlwaysComputedReadOnlyBeforeSave", "Gumball"), + Assert.Throws(() => context.SaveChanges()).Message); + }); } [Fact] @@ -633,13 +1004,14 @@ public virtual void Always_computed_property_on_Modified_entity_is_read_from_sto { var id = 0; - ExecuteWithStrategyInTransaction(context => - { - var entity = context.Add(new Gumball()).Entity; + ExecuteWithStrategyInTransaction( + context => + { + var entity = context.Add(new Gumball()).Entity; - context.SaveChanges(); - id = entity.Id; - }, + context.SaveChanges(); + id = entity.Id; + }, context => { var gumball = context.Gumballs.Single(e => e.Id == id); @@ -687,6 +1059,43 @@ protected class Gumball public string AlwaysComputedReadOnlyAfterSave { get; set; } } + protected class Anais + { + public int Id { get; set; } + public string Never { get; set; } + public string NeverUseBeforeUseAfter { get; set; } + public string NeverIgnoreBeforeUseAfter { get; set; } + public string NeverThrowBeforeUseAfter { get; set; } + public string NeverUseBeforeIgnoreAfter { get; set; } + public string NeverIgnoreBeforeIgnoreAfter { get; set; } + public string NeverThrowBeforeIgnoreAfter { get; set; } + public string NeverUseBeforeThrowAfter { get; set; } + public string NeverIgnoreBeforeThrowAfter { get; set; } + public string NeverThrowBeforeThrowAfter { get; set; } + + public string OnAdd { get; set; } + public string OnAddUseBeforeUseAfter { get; set; } + public string OnAddIgnoreBeforeUseAfter { get; set; } + public string OnAddThrowBeforeUseAfter { get; set; } + public string OnAddUseBeforeIgnoreAfter { get; set; } + public string OnAddIgnoreBeforeIgnoreAfter { get; set; } + public string OnAddThrowBeforeIgnoreAfter { get; set; } + public string OnAddUseBeforeThrowAfter { get; set; } + public string OnAddIgnoreBeforeThrowAfter { get; set; } + public string OnAddThrowBeforeThrowAfter { get; set; } + + public string OnAddOrUpdate { get; set; } + public string OnAddOrUpdateUseBeforeUseAfter { get; set; } + public string OnAddOrUpdateIgnoreBeforeUseAfter { get; set; } + public string OnAddOrUpdateThrowBeforeUseAfter { get; set; } + public string OnAddOrUpdateUseBeforeIgnoreAfter { get; set; } + public string OnAddOrUpdateIgnoreBeforeIgnoreAfter { get; set; } + public string OnAddOrUpdateThrowBeforeIgnoreAfter { get; set; } + public string OnAddOrUpdateUseBeforeThrowAfter { get; set; } + public string OnAddOrUpdateIgnoreBeforeThrowAfter { get; set; } + public string OnAddOrUpdateThrowBeforeThrowAfter { get; set; } + } + protected class StoreGeneratedContext : DbContext { public StoreGeneratedContext(DbContextOptions options) @@ -696,13 +1105,15 @@ public StoreGeneratedContext(DbContextOptions options) public DbSet Gumballs { get; set; } public DbSet Darwins { get; set; } + public DbSet Anaises { get; set; } } protected virtual void ExecuteWithStrategyInTransaction( Action testOperation, Action nestedTestOperation1 = null, Action nestedTestOperation2 = null) - => DbContextHelpers.ExecuteWithStrategyInTransaction(CreateContext, UseTransaction, + => DbContextHelpers.ExecuteWithStrategyInTransaction( + CreateContext, UseTransaction, testOperation, nestedTestOperation1, nestedTestOperation2); protected virtual void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) @@ -727,66 +1138,188 @@ public abstract class StoreGeneratedFixtureBase protected virtual void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity(b => - { - var property = b.Property(e => e.Id).ValueGeneratedOnAdd().Metadata; - property.IsReadOnlyAfterSave = true; - property.IsReadOnlyBeforeSave = true; - - property = b.Property(e => e.Identity).ValueGeneratedOnAdd().Metadata; - property.IsReadOnlyAfterSave = false; - property.IsReadOnlyBeforeSave = false; - - property = b.Property(e => e.IdentityReadOnlyBeforeSave).ValueGeneratedOnAdd().Metadata; - property.IsReadOnlyAfterSave = false; - property.IsReadOnlyBeforeSave = true; - - property = b.Property(e => e.IdentityReadOnlyAfterSave).ValueGeneratedOnAdd().Metadata; - property.IsReadOnlyAfterSave = true; - property.IsReadOnlyBeforeSave = false; - - property = b.Property(e => e.AlwaysIdentity).ValueGeneratedOnAdd().Metadata; - property.IsStoreGeneratedAlways = true; - property.IsReadOnlyAfterSave = false; - property.IsReadOnlyBeforeSave = false; - - property = b.Property(e => e.AlwaysIdentityReadOnlyBeforeSave).ValueGeneratedOnAdd().Metadata; - property.IsStoreGeneratedAlways = true; - property.IsReadOnlyAfterSave = false; - property.IsReadOnlyBeforeSave = true; - - property = b.Property(e => e.AlwaysIdentityReadOnlyAfterSave).ValueGeneratedOnAdd().Metadata; - property.IsStoreGeneratedAlways = true; - property.IsReadOnlyAfterSave = true; - property.IsReadOnlyBeforeSave = false; - - property = b.Property(e => e.Computed).ValueGeneratedOnAddOrUpdate().Metadata; - property.IsReadOnlyAfterSave = false; - property.IsReadOnlyBeforeSave = false; - - property = b.Property(e => e.ComputedReadOnlyBeforeSave).ValueGeneratedOnAddOrUpdate().Metadata; - property.IsReadOnlyAfterSave = false; - property.IsReadOnlyBeforeSave = true; - - property = b.Property(e => e.ComputedReadOnlyAfterSave).ValueGeneratedOnAddOrUpdate().Metadata; - property.IsReadOnlyAfterSave = true; - property.IsReadOnlyBeforeSave = false; - - property = b.Property(e => e.AlwaysComputed).ValueGeneratedOnAddOrUpdate().Metadata; - property.IsStoreGeneratedAlways = true; - property.IsReadOnlyAfterSave = false; - property.IsReadOnlyBeforeSave = false; - - property = b.Property(e => e.AlwaysComputedReadOnlyBeforeSave).ValueGeneratedOnAddOrUpdate().Metadata; - property.IsStoreGeneratedAlways = true; - property.IsReadOnlyAfterSave = false; - property.IsReadOnlyBeforeSave = true; - - property = b.Property(e => e.AlwaysComputedReadOnlyAfterSave).ValueGeneratedOnAddOrUpdate().Metadata; - property.IsStoreGeneratedAlways = true; - property.IsReadOnlyAfterSave = true; - property.IsReadOnlyBeforeSave = false; - }); + modelBuilder.Entity( + b => + { + var property = b.Property(e => e.Id).ValueGeneratedOnAdd().Metadata; +#pragma warning disable 618 + property.IsReadOnlyAfterSave = true; + property.IsReadOnlyBeforeSave = true; + + property = b.Property(e => e.Identity).ValueGeneratedOnAdd().Metadata; + property.IsReadOnlyAfterSave = false; + property.IsReadOnlyBeforeSave = false; + + property = b.Property(e => e.IdentityReadOnlyBeforeSave).ValueGeneratedOnAdd().Metadata; + property.IsReadOnlyAfterSave = false; + property.IsReadOnlyBeforeSave = true; + + property = b.Property(e => e.IdentityReadOnlyAfterSave).ValueGeneratedOnAdd().Metadata; + property.IsReadOnlyAfterSave = true; + property.IsReadOnlyBeforeSave = false; + + property = b.Property(e => e.AlwaysIdentity).ValueGeneratedOnAdd().Metadata; + property.IsStoreGeneratedAlways = true; + property.IsReadOnlyAfterSave = false; + property.IsReadOnlyBeforeSave = false; + + property = b.Property(e => e.AlwaysIdentityReadOnlyBeforeSave).ValueGeneratedOnAdd().Metadata; + property.IsStoreGeneratedAlways = true; + property.IsReadOnlyAfterSave = false; + property.IsReadOnlyBeforeSave = true; + + property = b.Property(e => e.AlwaysIdentityReadOnlyAfterSave).ValueGeneratedOnAdd().Metadata; + property.IsStoreGeneratedAlways = true; + property.IsReadOnlyAfterSave = true; + property.IsReadOnlyBeforeSave = false; + + property = b.Property(e => e.Computed).ValueGeneratedOnAddOrUpdate().Metadata; + property.IsStoreGeneratedAlways = false; + property.IsReadOnlyAfterSave = false; + property.IsReadOnlyBeforeSave = false; + + property = b.Property(e => e.ComputedReadOnlyBeforeSave).ValueGeneratedOnAddOrUpdate().Metadata; + property.IsReadOnlyAfterSave = false; + property.IsReadOnlyBeforeSave = true; + + property = b.Property(e => e.ComputedReadOnlyAfterSave).ValueGeneratedOnAddOrUpdate().Metadata; + property.IsReadOnlyAfterSave = true; + property.IsReadOnlyBeforeSave = false; + + property = b.Property(e => e.AlwaysComputed).ValueGeneratedOnAddOrUpdate().Metadata; + property.IsStoreGeneratedAlways = true; + property.IsReadOnlyAfterSave = false; + property.IsReadOnlyBeforeSave = false; + + property = b.Property(e => e.AlwaysComputedReadOnlyBeforeSave).ValueGeneratedOnAddOrUpdate().Metadata; + property.IsStoreGeneratedAlways = true; + property.IsReadOnlyAfterSave = false; + property.IsReadOnlyBeforeSave = true; + + property = b.Property(e => e.AlwaysComputedReadOnlyAfterSave).ValueGeneratedOnAddOrUpdate().Metadata; + property.IsStoreGeneratedAlways = true; + property.IsReadOnlyAfterSave = true; + property.IsReadOnlyBeforeSave = false; +#pragma warning restore 618 + }); + + modelBuilder.Entity( + b => + { + b.Property(e => e.Never).ValueGeneratedNever(); + + var property = b.Property(e => e.NeverUseBeforeUseAfter).ValueGeneratedNever().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.UseValue; + property.AfterSaveBehavior = PropertyValueBehavior.UseValue; + + property = b.Property(e => e.NeverIgnoreBeforeUseAfter).ValueGeneratedNever().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Ignore; + property.AfterSaveBehavior = PropertyValueBehavior.UseValue; + + property = b.Property(e => e.NeverThrowBeforeUseAfter).ValueGeneratedNever().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Throw; + property.AfterSaveBehavior = PropertyValueBehavior.UseValue; + + property = b.Property(e => e.NeverUseBeforeIgnoreAfter).ValueGeneratedNever().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.UseValue; + property.AfterSaveBehavior = PropertyValueBehavior.Ignore; + + property = b.Property(e => e.NeverIgnoreBeforeIgnoreAfter).ValueGeneratedNever().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Ignore; + property.AfterSaveBehavior = PropertyValueBehavior.Ignore; + + property = b.Property(e => e.NeverThrowBeforeIgnoreAfter).ValueGeneratedNever().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Throw; + property.AfterSaveBehavior = PropertyValueBehavior.Ignore; + + property = b.Property(e => e.NeverUseBeforeThrowAfter).ValueGeneratedNever().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.UseValue; + property.AfterSaveBehavior = PropertyValueBehavior.Throw; + + property = b.Property(e => e.NeverIgnoreBeforeThrowAfter).ValueGeneratedNever().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Ignore; + property.AfterSaveBehavior = PropertyValueBehavior.Throw; + + property = b.Property(e => e.NeverThrowBeforeThrowAfter).ValueGeneratedNever().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Throw; + property.AfterSaveBehavior = PropertyValueBehavior.Throw; + + b.Property(e => e.OnAdd).ValueGeneratedOnAdd(); + + property = b.Property(e => e.OnAddUseBeforeUseAfter).ValueGeneratedOnAdd().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.UseValue; + property.AfterSaveBehavior = PropertyValueBehavior.UseValue; + + property = b.Property(e => e.OnAddIgnoreBeforeUseAfter).ValueGeneratedOnAdd().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Ignore; + property.AfterSaveBehavior = PropertyValueBehavior.UseValue; + + property = b.Property(e => e.OnAddThrowBeforeUseAfter).ValueGeneratedOnAdd().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Throw; + property.AfterSaveBehavior = PropertyValueBehavior.UseValue; + + property = b.Property(e => e.OnAddUseBeforeIgnoreAfter).ValueGeneratedOnAdd().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.UseValue; + property.AfterSaveBehavior = PropertyValueBehavior.Ignore; + + property = b.Property(e => e.OnAddIgnoreBeforeIgnoreAfter).ValueGeneratedOnAdd().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Ignore; + property.AfterSaveBehavior = PropertyValueBehavior.Ignore; + + property = b.Property(e => e.OnAddThrowBeforeIgnoreAfter).ValueGeneratedOnAdd().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Throw; + property.AfterSaveBehavior = PropertyValueBehavior.Ignore; + + property = b.Property(e => e.OnAddUseBeforeThrowAfter).ValueGeneratedOnAdd().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.UseValue; + property.AfterSaveBehavior = PropertyValueBehavior.Throw; + + property = b.Property(e => e.OnAddIgnoreBeforeThrowAfter).ValueGeneratedOnAdd().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Ignore; + property.AfterSaveBehavior = PropertyValueBehavior.Throw; + + property = b.Property(e => e.OnAddThrowBeforeThrowAfter).ValueGeneratedOnAdd().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Throw; + property.AfterSaveBehavior = PropertyValueBehavior.Throw; + + b.Property(e => e.OnAddOrUpdate).ValueGeneratedOnAddOrUpdate(); + + property = b.Property(e => e.OnAddOrUpdateUseBeforeUseAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.UseValue; + property.AfterSaveBehavior = PropertyValueBehavior.UseValue; + + property = b.Property(e => e.OnAddOrUpdateIgnoreBeforeUseAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Ignore; + property.AfterSaveBehavior = PropertyValueBehavior.UseValue; + + property = b.Property(e => e.OnAddOrUpdateThrowBeforeUseAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Throw; + property.AfterSaveBehavior = PropertyValueBehavior.UseValue; + + property = b.Property(e => e.OnAddOrUpdateUseBeforeIgnoreAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.UseValue; + property.AfterSaveBehavior = PropertyValueBehavior.Ignore; + + property = b.Property(e => e.OnAddOrUpdateIgnoreBeforeIgnoreAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Ignore; + property.AfterSaveBehavior = PropertyValueBehavior.Ignore; + + property = b.Property(e => e.OnAddOrUpdateThrowBeforeIgnoreAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Throw; + property.AfterSaveBehavior = PropertyValueBehavior.Ignore; + + property = b.Property(e => e.OnAddOrUpdateUseBeforeThrowAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.UseValue; + property.AfterSaveBehavior = PropertyValueBehavior.Throw; + + property = b.Property(e => e.OnAddOrUpdateIgnoreBeforeThrowAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Ignore; + property.AfterSaveBehavior = PropertyValueBehavior.Throw; + + property = b.Property(e => e.OnAddOrUpdateThrowBeforeThrowAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.BeforeSaveBehavior = PropertyValueBehavior.Throw; + property.AfterSaveBehavior = PropertyValueBehavior.Throw; + }); } } } diff --git a/src/EFCore.Specification.Tests/TestUtilities/Extensions.cs b/src/EFCore.Specification.Tests/TestUtilities/Extensions.cs index bb729a5c995..13b08c9fda6 100644 --- a/src/EFCore.Specification.Tests/TestUtilities/Extensions.cs +++ b/src/EFCore.Specification.Tests/TestUtilities/Extensions.cs @@ -81,8 +81,8 @@ private static void CloneProperties(IEntityType sourceEntityType, EntityType tar clonedProperty.IsNullable = property.IsNullable; clonedProperty.IsConcurrencyToken = property.IsConcurrencyToken; clonedProperty.ValueGenerated = property.ValueGenerated; - clonedProperty.IsReadOnlyBeforeSave = property.IsReadOnlyBeforeSave; - clonedProperty.IsReadOnlyAfterSave = property.IsReadOnlyAfterSave; + clonedProperty.BeforeSaveBehavior = property.BeforeSaveBehavior; + clonedProperty.AfterSaveBehavior = property.AfterSaveBehavior; property.GetAnnotations().ForEach(annotation => clonedProperty[annotation.Name] = annotation.Value); } } diff --git a/src/EFCore.Specification.Tests/TestUtilities/PropertyComparer.cs b/src/EFCore.Specification.Tests/TestUtilities/PropertyComparer.cs index 56caf298bc3..eb54acc9a7d 100644 --- a/src/EFCore.Specification.Tests/TestUtilities/PropertyComparer.cs +++ b/src/EFCore.Specification.Tests/TestUtilities/PropertyComparer.cs @@ -37,8 +37,8 @@ public bool Equals(IProperty x, IProperty y) && x.IsNullable == y.IsNullable && x.IsConcurrencyToken == y.IsConcurrencyToken && x.ValueGenerated == y.ValueGenerated - && x.IsReadOnlyBeforeSave == y.IsReadOnlyBeforeSave - && x.IsReadOnlyAfterSave == y.IsReadOnlyAfterSave + && x.BeforeSaveBehavior == y.BeforeSaveBehavior + && x.AfterSaveBehavior == y.AfterSaveBehavior && (!_compareAnnotations || x.GetAnnotations().SequenceEqual(y.GetAnnotations(), AnnotationComparer.Instance)); } diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index 6a85aac9e59..0e27a9fd50e 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -155,7 +155,7 @@ private void SetEntityState(EntityState oldState, EntityState newState, bool acc // Hot path; do not use LINQ foreach (var property in EntityType.GetProperties()) { - if (property.IsReadOnlyAfterSave) + if (property.AfterSaveBehavior != PropertyValueBehavior.UseValue) { _stateData.FlagProperty(property.GetIndex(), PropertyFlag.TemporaryOrModified, isFlagged: false); } @@ -789,7 +789,7 @@ public virtual InternalEntityEntry PrepareToSave() { foreach (var property in EntityType.GetProperties()) { - if (property.IsReadOnlyBeforeSave + if (property.BeforeSaveBehavior == PropertyValueBehavior.Throw && !HasTemporaryValue(property) && !HasDefaultValue(property)) { @@ -801,7 +801,7 @@ public virtual InternalEntityEntry PrepareToSave() { foreach (var property in EntityType.GetProperties()) { - if (property.IsReadOnlyAfterSave + if (property.AfterSaveBehavior == PropertyValueBehavior.Throw && IsModified(property)) { throw new InvalidOperationException(CoreStrings.PropertyReadOnlyAfterSave(property.Name, EntityType.DisplayName())); @@ -957,10 +957,13 @@ public virtual void DiscardStoreGeneratedValues() public virtual bool IsStoreGenerated(IProperty property) => property.ValueGenerated != ValueGenerated.Never && ((EntityState == EntityState.Added - && (property.IsStoreGeneratedAlways + && (property.BeforeSaveBehavior == PropertyValueBehavior.Ignore || HasTemporaryValue(property) || HasDefaultValue(property))) - || (property.ValueGenerated == ValueGenerated.OnAddOrUpdate && EntityState == EntityState.Modified && (property.IsStoreGeneratedAlways || !IsModified(property)))); + || (property.ValueGenerated == ValueGenerated.OnAddOrUpdate + && EntityState == EntityState.Modified + && (property.AfterSaveBehavior == PropertyValueBehavior.Ignore + || !IsModified(property)))); private bool HasDefaultValue(IProperty property) => property.ClrType.IsDefaultValue(this[property]); diff --git a/src/EFCore/Metadata/IMutableProperty.cs b/src/EFCore/Metadata/IMutableProperty.cs index 7abbc7608f9..5056bc07e7b 100644 --- a/src/EFCore/Metadata/IMutableProperty.cs +++ b/src/EFCore/Metadata/IMutableProperty.cs @@ -1,6 +1,8 @@ // 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; + namespace Microsoft.EntityFrameworkCore.Metadata { /// @@ -28,24 +30,62 @@ public interface IMutableProperty : IProperty, IMutablePropertyBase /// Gets or sets a value indicating when a value for this property will be generated by the database. Even when the /// property is set to be generated by the database, EF may still attempt to save a specific value (rather than /// having one generated by the database) when the entity is added and a value is assigned, or the property is - /// marked as modified for an existing entity. See for more information. + /// marked as modified for an existing entity. See and + /// for more information. /// new ValueGenerated ValueGenerated { get; set; } /// - /// Gets or sets a value indicating whether or not this property can be modified before the entity is - /// saved to the database. If true, an exception will be thrown if a value is assigned to - /// this property when it is in the state. + /// + /// Gets a value indicating whether or not this property can be modified before the entity is + /// saved to the database. + /// + /// + /// If , then an exception + /// will be thrown if a value is assigned to this property when it is in + /// the state. + /// + /// + /// If , then any value + /// set will be ignored when it is in the state. + /// + /// + new PropertyValueBehavior BeforeSaveBehavior { get; set; } + + /// + /// + /// Gets a value indicating whether or not this property can be modified before the entity is + /// saved to the database. + /// + /// + /// If , then an exception + /// will be thrown if a new value is assigned to this property after the entity exists in the database. + /// + /// + /// If , then any modification to the + /// property value of an entity that already exists in the database will be ignored. + /// /// + new PropertyValueBehavior AfterSaveBehavior { get; set; } + + /// + /// This property is obsolete. Use instead. + /// + [Obsolete("Use BeforeSaveBehavior instead.")] new bool IsReadOnlyBeforeSave { get; set; } /// - /// Gets or sets a value indicating whether or not this property can be modified after the entity is - /// saved to the database. If true, an exception will be thrown if a new value is assigned to - /// this property after the entity exists in the database. + /// This property is obsolete. Use instead. /// + [Obsolete("Use AfterSaveBehavior instead.")] new bool IsReadOnlyAfterSave { get; set; } + /// + /// This property is obsolete. Use or instead. + /// + [Obsolete("Use BeforeSaveBehavior or AfterSaveBehavior instead.")] + new bool IsStoreGeneratedAlways { get; set; } + /// /// Gets or sets a value indicating whether this property is used as a concurrency token. When a property is configured /// as a concurrency token the value in the database will be checked when an instance of this entity type @@ -54,14 +94,5 @@ public interface IMutableProperty : IProperty, IMutablePropertyBase /// changes will not be applied to the database. /// new bool IsConcurrencyToken { get; set; } - - /// - /// Gets or sets a value indicating whether or not the database will always generate a value for this property. - /// If set to true, a value will always be read back from the database whenever the entity is saved - /// regardless of the state of the property. If set to false, whenever a value is assigned to the property - /// (or marked as modified) EF will attempt to save that value to the database rather than letting the - /// database generate one. - /// - new bool IsStoreGeneratedAlways { get; set; } } } diff --git a/src/EFCore/Metadata/IProperty.cs b/src/EFCore/Metadata/IProperty.cs index 8a2dd68fb59..f5ac4f9cc30 100644 --- a/src/EFCore/Metadata/IProperty.cs +++ b/src/EFCore/Metadata/IProperty.cs @@ -23,33 +23,62 @@ public interface IProperty : IPropertyBase bool IsNullable { get; } /// - /// Gets a value indicating whether or not this property can be modified before the entity is - /// saved to the database. If true, an exception will be thrown if a value is assigned to - /// this property when it is in the state. + /// + /// Gets a value indicating whether or not this property can be modified before the entity is + /// saved to the database. + /// + /// + /// If , then an exception + /// will be thrown if a value is assigned to this property when it is in + /// the state. + /// + /// + /// If , then any value + /// set will be ignored when it is in the state. + /// /// + PropertyValueBehavior BeforeSaveBehavior { get; } + + /// + /// + /// Gets a value indicating whether or not this property can be modified after the entity is + /// saved to the database. + /// + /// + /// If , then an exception + /// will be thrown if a new value is assigned to this property after the entity exists in the database. + /// + /// + /// If , then any modification to the + /// property value of an entity that already exists in the database will be ignored. + /// + /// + PropertyValueBehavior AfterSaveBehavior { get; } + + /// + /// This property is obsolete. Use instead. + /// + [Obsolete("Use BeforeSaveBehavior instead.")] bool IsReadOnlyBeforeSave { get; } /// - /// Gets a value indicating whether or not this property can be modified after the entity is - /// saved to the database. If true, an exception will be thrown if a new value is assigned to - /// this property after the entity exists in the database. + /// This property is obsolete. Use instead. /// + [Obsolete("Use AfterSaveBehavior instead.")] bool IsReadOnlyAfterSave { get; } /// - /// Gets a value indicating whether or not the database will always generate a value for this property. - /// If set to true, a value will always be read back from the database whenever the entity is saved - /// regardless of the state of the property. If set to false, whenever a value is assigned to the property - /// (or marked as modified) EF will attempt to save that value to the database rather than letting the - /// database generate one. + /// This property is obsolete. Use or instead. /// + [Obsolete("Use BeforeSaveBehavior or AfterSaveBehavior instead.")] bool IsStoreGeneratedAlways { get; } /// /// Gets a value indicating when a value for this property will be generated by the database. Even when the /// property is set to be generated by the database, EF may still attempt to save a specific value (rather than /// having one generated by the database) when the entity is added and a value is assigned, or the property is - /// marked as modified for an existing entity. See for more information. + /// marked as modified for an existing entity. See and + /// for more information. /// ValueGenerated ValueGenerated { get; } diff --git a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs index 26cf262c13d..86ab3d8a3f6 100644 --- a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs +++ b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs @@ -461,7 +461,8 @@ public static IEnumerable GetNotificationProperties( { if (string.IsNullOrEmpty(propertyName)) { - foreach (var property in entityType.GetProperties().Where(p => !p.IsReadOnlyAfterSave)) + foreach (var property in entityType.GetProperties() + .Where(p => p.AfterSaveBehavior == PropertyValueBehavior.UseValue)) { yield return property; } diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs index be4e3a2ed2d..42151ca84b5 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs @@ -194,12 +194,13 @@ public virtual bool IsConcurrencyToken(bool concurrencyToken, ConfigurationSourc /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual bool ReadOnlyAfterSave(bool isReadOnlyAfterSave, ConfigurationSource configurationSource) + public virtual bool BeforeSave(PropertyValueBehavior? behavior, ConfigurationSource configurationSource) { - if (configurationSource.Overrides(Metadata.GetIsReadOnlyAfterSaveConfigurationSource()) - || (Metadata.IsReadOnlyAfterSave == isReadOnlyAfterSave)) + if (configurationSource.Overrides(Metadata.GetBeforeSaveBehaviorConfigurationSource()) + || Metadata.BeforeSaveBehavior == behavior) { - Metadata.SetIsReadOnlyAfterSave(isReadOnlyAfterSave, configurationSource); + Metadata.SetBeforeSaveBehavior(behavior, configurationSource); + return true; } @@ -210,12 +211,13 @@ public virtual bool ReadOnlyAfterSave(bool isReadOnlyAfterSave, ConfigurationSou /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual bool ReadOnlyBeforeSave(bool isReadOnlyBeforeSave, ConfigurationSource configurationSource) + public virtual bool AfterSave(PropertyValueBehavior? behavior, ConfigurationSource configurationSource) { - if (configurationSource.Overrides(Metadata.GetIsReadOnlyBeforeSaveConfigurationSource()) - || (Metadata.IsReadOnlyBeforeSave == isReadOnlyBeforeSave)) + if (configurationSource.Overrides(Metadata.GetAfterSaveBehaviorConfigurationSource()) + || Metadata.AfterSaveBehavior == behavior) { - Metadata.SetIsReadOnlyBeforeSave(isReadOnlyBeforeSave, configurationSource); + Metadata.SetAfterSaveBehavior(behavior, configurationSource); + return true; } @@ -239,23 +241,6 @@ public virtual bool ValueGenerated(ValueGenerated? valueGenerated, Configuration return false; } - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public virtual bool IsStoreGeneratedAlways(bool isStoreGeneratedAlways, ConfigurationSource configurationSource) - { - if (configurationSource.Overrides(Metadata.GetIsStoreGeneratedAlwaysConfigurationSource()) - || (Metadata.IsStoreGeneratedAlways == isStoreGeneratedAlways)) - { - Metadata.SetIsStoreGeneratedAlways(isStoreGeneratedAlways, configurationSource); - - return true; - } - - return false; - } - /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -293,39 +278,39 @@ public virtual InternalPropertyBuilder Attach( newPropertyBuilder.MergeAnnotationsFrom(this); - var oldIsReadOnlyAfterSaveConfigurationSource = Metadata.GetIsReadOnlyAfterSaveConfigurationSource(); - if (oldIsReadOnlyAfterSaveConfigurationSource.HasValue) + var oldBeforeSaveBehaviorConfigurationSource = Metadata.GetBeforeSaveBehaviorConfigurationSource(); + if (oldBeforeSaveBehaviorConfigurationSource.HasValue) { - newPropertyBuilder.ReadOnlyAfterSave(Metadata.IsReadOnlyAfterSave, - oldIsReadOnlyAfterSaveConfigurationSource.Value); + newPropertyBuilder.BeforeSave(Metadata.BeforeSaveBehavior, + oldBeforeSaveBehaviorConfigurationSource.Value); } - var oldIsReadOnlyBeforeSaveConfigurationSource = Metadata.GetIsReadOnlyBeforeSaveConfigurationSource(); - if (oldIsReadOnlyBeforeSaveConfigurationSource.HasValue) + + var oldAfterSaveBehaviorConfigurationSource = Metadata.GetAfterSaveBehaviorConfigurationSource(); + if (oldAfterSaveBehaviorConfigurationSource.HasValue) { - newPropertyBuilder.ReadOnlyBeforeSave(Metadata.IsReadOnlyBeforeSave, - oldIsReadOnlyBeforeSaveConfigurationSource.Value); + newPropertyBuilder.AfterSave(Metadata.AfterSaveBehavior, + oldAfterSaveBehaviorConfigurationSource.Value); } + var oldIsNullableConfigurationSource = Metadata.GetIsNullableConfigurationSource(); if (oldIsNullableConfigurationSource.HasValue) { newPropertyBuilder.IsRequired(!Metadata.IsNullable, oldIsNullableConfigurationSource.Value); } + var oldIsConcurrencyTokenConfigurationSource = Metadata.GetIsConcurrencyTokenConfigurationSource(); if (oldIsConcurrencyTokenConfigurationSource.HasValue) { newPropertyBuilder.IsConcurrencyToken(Metadata.IsConcurrencyToken, oldIsConcurrencyTokenConfigurationSource.Value); } + var oldValueGeneratedConfigurationSource = Metadata.GetValueGeneratedConfigurationSource(); if (oldValueGeneratedConfigurationSource.HasValue) { newPropertyBuilder.ValueGenerated(Metadata.ValueGenerated, oldValueGeneratedConfigurationSource.Value); } - var oldIsStoreGeneratedAlwaysConfigurationSource = Metadata.GetIsStoreGeneratedAlwaysConfigurationSource(); - if (oldIsStoreGeneratedAlwaysConfigurationSource.HasValue) - { - newPropertyBuilder.IsStoreGeneratedAlways(Metadata.IsStoreGeneratedAlways, oldIsStoreGeneratedAlwaysConfigurationSource.Value); - } + var oldFieldInfoConfigurationSource = Metadata.GetFieldInfoConfigurationSource(); if (oldFieldInfoConfigurationSource.HasValue) { diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index f1644c16445..e4287bc51f3 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -19,15 +19,18 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal /// public class Property : PropertyBase, IMutableProperty { - private int _flags; + private bool? _isConcurrencyToken; + private bool? _isNullable; + private ValueGenerated? _valueGenerated; + private PropertyValueBehavior? _beforeSaveBehavior; + private PropertyValueBehavior? _afterSaveBehavior; private ConfigurationSource _configurationSource; private ConfigurationSource? _typeConfigurationSource; - private ConfigurationSource? _isReadOnlyAfterSaveConfigurationSource; - private ConfigurationSource? _isReadOnlyBeforeSaveConfigurationSource; + private ConfigurationSource? _beforeSaveBehaviorConfigurationSource; + private ConfigurationSource? _afterSaveBehaviorConfigurationSource; private ConfigurationSource? _isNullableConfigurationSource; private ConfigurationSource? _isConcurrencyTokenConfigurationSource; - private ConfigurationSource? _isStoreGeneratedAlwaysConfigurationSource; private ConfigurationSource? _valueGeneratedConfigurationSource; // Warning: Never access these fields directly as access needs to be thread-safe @@ -77,7 +80,7 @@ public Property( public new virtual EntityType DeclaringType { [DebuggerStepThrough] - get { return DeclaringEntityType; } + get => DeclaringEntityType; } /// @@ -133,12 +136,8 @@ public virtual void UpdateTypeConfigurationSource(ConfigurationSource configurat /// public virtual bool IsNullable { - get - { - bool value; - return TryGetFlag(PropertyFlags.IsNullable, out value) ? value : DefaultIsNullable; - } - set { SetIsNullable(value, ConfigurationSource.Explicit); } + get => _isNullable ?? DefaultIsNullable; + set => SetIsNullable(value, ConfigurationSource.Explicit); } /// @@ -163,7 +162,7 @@ public virtual void SetIsNullable(bool nullable, ConfigurationSource configurati UpdateIsNullableConfigurationSource(configurationSource); var isChanging = IsNullable != nullable; - SetFlag(nullable, PropertyFlags.IsNullable); + _isNullable = nullable; if (isChanging) { DeclaringEntityType.Model.ConventionDispatcher.OnPropertyNullableChanged(Builder); @@ -194,13 +193,8 @@ protected override void OnFieldInfoSet(FieldInfo oldFieldInfo) /// public virtual ValueGenerated ValueGenerated { - get - { - var value = _flags & (int)PropertyFlags.ValueGenerated; - - return value == 0 ? DefaultValueGenerated : (ValueGenerated)((value >> 8) - 1); - } - set { SetValueGenerated(value, ConfigurationSource.Explicit); } + get => _valueGenerated ?? DefaultValueGenerated; + set => SetValueGenerated(value, ConfigurationSource.Explicit); } /// @@ -209,7 +203,7 @@ public virtual ValueGenerated ValueGenerated /// public virtual void SetValueGenerated(ValueGenerated? valueGenerated, ConfigurationSource configurationSource) { - _flags &= ~(int)PropertyFlags.ValueGenerated; + _valueGenerated = valueGenerated; if (valueGenerated == null) { @@ -217,7 +211,6 @@ public virtual void SetValueGenerated(ValueGenerated? valueGenerated, Configurat } else { - _flags |= ((int)valueGenerated + 1) << 8; UpdateValueGeneratedConfigurationSource(configurationSource); } } @@ -237,161 +230,188 @@ private void UpdateValueGeneratedConfigurationSource(ConfigurationSource configu /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual bool IsReadOnlyBeforeSave + public virtual PropertyValueBehavior BeforeSaveBehavior { - get - { - bool value; - return TryGetFlag(PropertyFlags.IsReadOnlyBeforeSave, out value) ? value : DefaultIsReadOnlyBeforeSave; - } - set { SetIsReadOnlyBeforeSave(value, ConfigurationSource.Explicit); } + get => _beforeSaveBehavior ?? DefaultBeforeSaveBehavior; + set => SetBeforeSaveBehavior(value, ConfigurationSource.Explicit); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual void SetIsReadOnlyBeforeSave(bool readOnlyBeforeSave, ConfigurationSource configurationSource) + public virtual void SetBeforeSaveBehavior(PropertyValueBehavior? beforeSaveBehavior, ConfigurationSource configurationSource) { - SetFlag(readOnlyBeforeSave, PropertyFlags.IsReadOnlyBeforeSave); - UpdateIsReadOnlyBeforeSaveConfigurationSource(configurationSource); + if (BeforeSaveBehavior != beforeSaveBehavior) + { + _beforeSaveBehavior = beforeSaveBehavior; + PropertyMetadataChanged(); + } + else + { + _beforeSaveBehavior = beforeSaveBehavior; + } + + UpdateBeforeSaveBehaviorConfigurationSource(configurationSource); } - private bool DefaultIsReadOnlyBeforeSave - => (ValueGenerated == ValueGenerated.OnAddOrUpdate) - && !IsStoreGeneratedAlways; + private PropertyValueBehavior DefaultBeforeSaveBehavior + => ValueGenerated == ValueGenerated.OnAddOrUpdate + ? PropertyValueBehavior.Ignore + : PropertyValueBehavior.UseValue; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual ConfigurationSource? GetIsReadOnlyBeforeSaveConfigurationSource() => _isReadOnlyBeforeSaveConfigurationSource; + public virtual ConfigurationSource? GetBeforeSaveBehaviorConfigurationSource() => _beforeSaveBehaviorConfigurationSource; - private void UpdateIsReadOnlyBeforeSaveConfigurationSource(ConfigurationSource configurationSource) - => _isReadOnlyBeforeSaveConfigurationSource = configurationSource.Max(_isReadOnlyBeforeSaveConfigurationSource); + private void UpdateBeforeSaveBehaviorConfigurationSource(ConfigurationSource configurationSource) + => _beforeSaveBehaviorConfigurationSource = configurationSource.Max(_beforeSaveBehaviorConfigurationSource); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual bool IsReadOnlyAfterSave + public virtual PropertyValueBehavior AfterSaveBehavior { - get - { - bool value; - return TryGetFlag(PropertyFlags.IsReadOnlyAfterSave, out value) ? value : DefaultIsReadOnlyAfterSave; - } - set { SetIsReadOnlyAfterSave(value, ConfigurationSource.Explicit); } + get => _afterSaveBehavior ?? DefaultAfterSaveBehavior; + set => SetAfterSaveBehavior(value, ConfigurationSource.Explicit); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual void SetIsReadOnlyAfterSave(bool readOnlyAfterSave, ConfigurationSource configurationSource) + public virtual void SetAfterSaveBehavior(PropertyValueBehavior? afterSaveBehavior, ConfigurationSource configurationSource) { - if (!readOnlyAfterSave + if (afterSaveBehavior != PropertyValueBehavior.Throw && Keys != null) { throw new InvalidOperationException(CoreStrings.KeyPropertyMustBeReadOnly(Name, DeclaringEntityType.DisplayName())); } - SetFlag(readOnlyAfterSave, PropertyFlags.IsReadOnlyAfterSave); - UpdateIsReadOnlyAfterSaveConfigurationSource(configurationSource); + + if (AfterSaveBehavior != afterSaveBehavior) + { + _afterSaveBehavior = afterSaveBehavior; + PropertyMetadataChanged(); + } + else + { + _afterSaveBehavior = afterSaveBehavior; + } + + UpdateAfterSaveBehaviorConfigurationSource(configurationSource); } - private bool DefaultIsReadOnlyAfterSave - => ((ValueGenerated == ValueGenerated.OnAddOrUpdate) - && !IsStoreGeneratedAlways) - || Keys != null; + private PropertyValueBehavior DefaultAfterSaveBehavior + => Keys != null + ? PropertyValueBehavior.Throw + : ValueGenerated == ValueGenerated.OnAddOrUpdate + ? PropertyValueBehavior.Ignore + : PropertyValueBehavior.UseValue; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual ConfigurationSource? GetIsReadOnlyAfterSaveConfigurationSource() => _isReadOnlyAfterSaveConfigurationSource; + public virtual ConfigurationSource? GetAfterSaveBehaviorConfigurationSource() => _afterSaveBehaviorConfigurationSource; - private void UpdateIsReadOnlyAfterSaveConfigurationSource(ConfigurationSource configurationSource) - => _isReadOnlyAfterSaveConfigurationSource = configurationSource.Max(_isReadOnlyAfterSaveConfigurationSource); + private void UpdateAfterSaveBehaviorConfigurationSource(ConfigurationSource configurationSource) + => _afterSaveBehaviorConfigurationSource = configurationSource.Max(_afterSaveBehaviorConfigurationSource); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual bool IsConcurrencyToken + [Obsolete("Use BeforeSaveBehavior instead.")] + public virtual bool IsReadOnlyBeforeSave { - get + get => BeforeSaveBehavior == PropertyValueBehavior.Throw; + set { - bool value; - return TryGetFlag(PropertyFlags.IsConcurrencyToken, out value) ? value : DefaultIsConcurrencyToken; + if (value) + { + BeforeSaveBehavior = PropertyValueBehavior.Throw; + } } - set { SetIsConcurrencyToken(value, ConfigurationSource.Explicit); } } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual void SetIsConcurrencyToken(bool concurrencyToken, ConfigurationSource configurationSource) + [Obsolete("Use AfterSaveBehavior instead.")] + public virtual bool IsReadOnlyAfterSave { - if (IsConcurrencyToken != concurrencyToken) + get => AfterSaveBehavior == PropertyValueBehavior.Throw; + set { - SetFlag(concurrencyToken, PropertyFlags.IsConcurrencyToken); - - DeclaringEntityType.PropertyMetadataChanged(); + if (value) + { + AfterSaveBehavior = PropertyValueBehavior.Throw; + } } - UpdateIsConcurrencyTokenConfigurationSource(configurationSource); } - private bool DefaultIsConcurrencyToken => false; - /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual ConfigurationSource? GetIsConcurrencyTokenConfigurationSource() => _isConcurrencyTokenConfigurationSource; - - private void UpdateIsConcurrencyTokenConfigurationSource(ConfigurationSource configurationSource) - => _isConcurrencyTokenConfigurationSource = configurationSource.Max(_isConcurrencyTokenConfigurationSource); + public virtual bool IsConcurrencyToken + { + get => _isConcurrencyToken ?? DefaultIsConcurrencyToken; + set => SetIsConcurrencyToken(value, ConfigurationSource.Explicit); + } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual bool IsStoreGeneratedAlways + public virtual void SetIsConcurrencyToken(bool concurrencyToken, ConfigurationSource configurationSource) { - get + if (IsConcurrencyToken != concurrencyToken) { - bool value; - return TryGetFlag(PropertyFlags.StoreGeneratedAlways, out value) ? value : DefaultStoreGeneratedAlways; + _isConcurrencyToken = concurrencyToken; + + PropertyMetadataChanged(); } - set { SetIsStoreGeneratedAlways(value, ConfigurationSource.Explicit); } + UpdateIsConcurrencyTokenConfigurationSource(configurationSource); } + private bool DefaultIsConcurrencyToken => false; + /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual void SetIsStoreGeneratedAlways(bool storeGeneratedAlways, ConfigurationSource configurationSource) - { - if (IsStoreGeneratedAlways != storeGeneratedAlways) - { - SetFlag(storeGeneratedAlways, PropertyFlags.StoreGeneratedAlways); - - DeclaringEntityType.PropertyMetadataChanged(); - } - UpdateIsStoreGeneratedAlwaysConfigurationSource(configurationSource); - } + public virtual ConfigurationSource? GetIsConcurrencyTokenConfigurationSource() => _isConcurrencyTokenConfigurationSource; - private bool DefaultStoreGeneratedAlways => (ValueGenerated == ValueGenerated.OnAddOrUpdate) && IsConcurrencyToken; + private void UpdateIsConcurrencyTokenConfigurationSource(ConfigurationSource configurationSource) + => _isConcurrencyTokenConfigurationSource = configurationSource.Max(_isConcurrencyTokenConfigurationSource); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual ConfigurationSource? GetIsStoreGeneratedAlwaysConfigurationSource() => _isStoreGeneratedAlwaysConfigurationSource; - - private void UpdateIsStoreGeneratedAlwaysConfigurationSource(ConfigurationSource configurationSource) - => _isStoreGeneratedAlwaysConfigurationSource = configurationSource.Max(_isStoreGeneratedAlwaysConfigurationSource); + [Obsolete("Use BeforeSaveBehavior or AfterSaveBehavior instead.")] + public virtual bool IsStoreGeneratedAlways + { + get => AfterSaveBehavior == PropertyValueBehavior.Ignore || BeforeSaveBehavior == PropertyValueBehavior.Ignore; + set + { + if (value) + { + BeforeSaveBehavior = PropertyValueBehavior.Ignore; + AfterSaveBehavior = PropertyValueBehavior.Ignore; + } + else + { + BeforeSaveBehavior = PropertyValueBehavior.UseValue; + AfterSaveBehavior = PropertyValueBehavior.UseValue; + } + } + } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used @@ -424,26 +444,6 @@ public virtual IEnumerable GetContainingIndexes() protected override Annotation OnAnnotationSet(string name, Annotation annotation, Annotation oldAnnotation) => DeclaringType.Model.ConventionDispatcher.OnPropertyAnnotationSet(Builder, name, annotation, oldAnnotation); - private bool TryGetFlag(PropertyFlags flag, out bool value) - { - var coded = _flags & (int)flag; - value = coded == (int)flag; - return coded != 0; - } - - private void SetFlag(bool value, PropertyFlags flag) - { - if (value) - { - _flags |= (int)flag; - } - else - { - var falseValue = ((int)flag << 1) & (int)flag; - _flags = (_flags & ~(int)flag) | falseValue; - } - } - /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -459,17 +459,6 @@ public static string Format([NotNull] IEnumerable properties, bool in IMutableEntityType IMutableProperty.DeclaringEntityType => DeclaringEntityType; IMutableTypeBase IMutablePropertyBase.DeclaringType => DeclaringType; - private enum PropertyFlags - { - IsConcurrencyToken = 3 << 0, - IsNullable = 3 << 2, - IsReadOnlyBeforeSave = 3 << 4, - IsReadOnlyAfterSave = 3 << 6, - ValueGenerated = 7 << 8, - RequiresValueGenerator = 3 << 11, - StoreGeneratedAlways = 3 << 13 - } - /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -494,11 +483,8 @@ public static bool AreCompatible([NotNull] IReadOnlyList properties, [ /// public virtual PropertyIndexes PropertyIndexes { - get - { - return NonCapturingLazyInitializer.EnsureInitialized(ref _indexes, this, - property => property.DeclaringType.CalculateIndexes(property)); - } + get => NonCapturingLazyInitializer.EnsureInitialized(ref _indexes, this, + property => property.DeclaringType.CalculateIndexes(property)); [param: CanBeNull] set diff --git a/src/EFCore/Metadata/Internal/PropertyExtensions.cs b/src/EFCore/Metadata/Internal/PropertyExtensions.cs index 7b2db5c3f69..e0118ad6dd2 100644 --- a/src/EFCore/Metadata/Internal/PropertyExtensions.cs +++ b/src/EFCore/Metadata/Internal/PropertyExtensions.cs @@ -213,14 +213,14 @@ public static string ToDebugString([NotNull] this IProperty property, bool singl builder.Append(" Concurrency"); } - if (property.IsReadOnlyAfterSave) + if (property.BeforeSaveBehavior != PropertyValueBehavior.UseValue) { - builder.Append(" ReadOnlyAfterSave"); + builder.Append(" BeforeSave:").Append(property.BeforeSaveBehavior); } - if (property.IsReadOnlyBeforeSave) + if (property.AfterSaveBehavior != PropertyValueBehavior.UseValue) { - builder.Append(" ReadOnlyBeforeSave"); + builder.Append(" AfterSave:").Append(property.AfterSaveBehavior); } if (property.ValueGenerated != ValueGenerated.Never) @@ -228,11 +228,6 @@ public static string ToDebugString([NotNull] this IProperty property, bool singl builder.Append(" ValueGenerated.").Append(property.ValueGenerated); } - if (property.IsStoreGeneratedAlways) - { - builder.Append(" StoreGeneratedAlways"); - } - if (property.GetMaxLength() != null) { builder.Append(" MaxLength").Append(property.GetMaxLength()); diff --git a/src/EFCore/Metadata/PropertyValueBehavior.cs b/src/EFCore/Metadata/PropertyValueBehavior.cs new file mode 100644 index 00000000000..15464f9266e --- /dev/null +++ b/src/EFCore/Metadata/PropertyValueBehavior.cs @@ -0,0 +1,27 @@ +// 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. + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + /// + /// Indicates how changes to the value of a property will be handled by Entity Framework change tracking + /// which in turn will determine whether the value set is sent to the database or not. + /// + public enum PropertyValueBehavior + { + /// + /// The value set or changed will be sent to the database in the normal way. + /// + UseValue, + + /// + /// Any value set or changed will be ignored. + /// + Ignore, + + /// + /// If an explicit value is set or the value is changed, then an exception will be thrown. + /// + Throw + } +} diff --git a/src/EFCore/breakingchanges.netcore.json b/src/EFCore/breakingchanges.netcore.json index 6dc39e11f16..ba08e0dd5a1 100644 --- a/src/EFCore/breakingchanges.netcore.json +++ b/src/EFCore/breakingchanges.netcore.json @@ -502,6 +502,36 @@ "MemberId": "Microsoft.EntityFrameworkCore.Metadata.IMutableEntityType RemoveDelegatedIdentityEntityType(System.String name, System.String definingNavigationName, Microsoft.EntityFrameworkCore.Metadata.IMutableEntityType definingEntityType)", "Kind": "Addition" }, + { + "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IMutableProperty : Microsoft.EntityFrameworkCore.Metadata.IProperty, Microsoft.EntityFrameworkCore.Metadata.IMutablePropertyBase", + "MemberId": "Microsoft.EntityFrameworkCore.Metadata.PropertyValueBehavior get_AfterSaveBehavior()", + "Kind": "Addition" + }, + { + "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IMutableProperty : Microsoft.EntityFrameworkCore.Metadata.IProperty, Microsoft.EntityFrameworkCore.Metadata.IMutablePropertyBase", + "MemberId": "Microsoft.EntityFrameworkCore.Metadata.PropertyValueBehavior get_BeforeSaveBehavior()", + "Kind": "Addition" + }, + { + "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IMutableProperty : Microsoft.EntityFrameworkCore.Metadata.IProperty, Microsoft.EntityFrameworkCore.Metadata.IMutablePropertyBase", + "MemberId": "System.Void set_AfterSaveBehavior(Microsoft.EntityFrameworkCore.Metadata.PropertyValueBehavior value)", + "Kind": "Addition" + }, + { + "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IMutableProperty : Microsoft.EntityFrameworkCore.Metadata.IProperty, Microsoft.EntityFrameworkCore.Metadata.IMutablePropertyBase", + "MemberId": "System.Void set_BeforeSaveBehavior(Microsoft.EntityFrameworkCore.Metadata.PropertyValueBehavior value)", + "Kind": "Addition" + }, + { + "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IProperty : Microsoft.EntityFrameworkCore.Metadata.IPropertyBase", + "MemberId": "Microsoft.EntityFrameworkCore.Metadata.PropertyValueBehavior get_AfterSaveBehavior()", + "Kind": "Addition" + }, + { + "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IProperty : Microsoft.EntityFrameworkCore.Metadata.IPropertyBase", + "MemberId": "Microsoft.EntityFrameworkCore.Metadata.PropertyValueBehavior get_BeforeSaveBehavior()", + "Kind": "Addition" + }, { "TypeId": "public interface Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptionsExtension", "MemberId": "System.Boolean ApplyServices(Microsoft.Extensions.DependencyInjection.IServiceCollection services)", diff --git a/src/EFCore/breakingchanges.netframework.json b/src/EFCore/breakingchanges.netframework.json index 6dc39e11f16..ba08e0dd5a1 100644 --- a/src/EFCore/breakingchanges.netframework.json +++ b/src/EFCore/breakingchanges.netframework.json @@ -502,6 +502,36 @@ "MemberId": "Microsoft.EntityFrameworkCore.Metadata.IMutableEntityType RemoveDelegatedIdentityEntityType(System.String name, System.String definingNavigationName, Microsoft.EntityFrameworkCore.Metadata.IMutableEntityType definingEntityType)", "Kind": "Addition" }, + { + "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IMutableProperty : Microsoft.EntityFrameworkCore.Metadata.IProperty, Microsoft.EntityFrameworkCore.Metadata.IMutablePropertyBase", + "MemberId": "Microsoft.EntityFrameworkCore.Metadata.PropertyValueBehavior get_AfterSaveBehavior()", + "Kind": "Addition" + }, + { + "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IMutableProperty : Microsoft.EntityFrameworkCore.Metadata.IProperty, Microsoft.EntityFrameworkCore.Metadata.IMutablePropertyBase", + "MemberId": "Microsoft.EntityFrameworkCore.Metadata.PropertyValueBehavior get_BeforeSaveBehavior()", + "Kind": "Addition" + }, + { + "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IMutableProperty : Microsoft.EntityFrameworkCore.Metadata.IProperty, Microsoft.EntityFrameworkCore.Metadata.IMutablePropertyBase", + "MemberId": "System.Void set_AfterSaveBehavior(Microsoft.EntityFrameworkCore.Metadata.PropertyValueBehavior value)", + "Kind": "Addition" + }, + { + "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IMutableProperty : Microsoft.EntityFrameworkCore.Metadata.IProperty, Microsoft.EntityFrameworkCore.Metadata.IMutablePropertyBase", + "MemberId": "System.Void set_BeforeSaveBehavior(Microsoft.EntityFrameworkCore.Metadata.PropertyValueBehavior value)", + "Kind": "Addition" + }, + { + "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IProperty : Microsoft.EntityFrameworkCore.Metadata.IPropertyBase", + "MemberId": "Microsoft.EntityFrameworkCore.Metadata.PropertyValueBehavior get_AfterSaveBehavior()", + "Kind": "Addition" + }, + { + "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IProperty : Microsoft.EntityFrameworkCore.Metadata.IPropertyBase", + "MemberId": "Microsoft.EntityFrameworkCore.Metadata.PropertyValueBehavior get_BeforeSaveBehavior()", + "Kind": "Addition" + }, { "TypeId": "public interface Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptionsExtension", "MemberId": "System.Boolean ApplyServices(Microsoft.Extensions.DependencyInjection.IServiceCollection services)", diff --git a/test/EFCore.InMemory.FunctionalTests/StoreGeneratedInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/StoreGeneratedInMemoryTest.cs deleted file mode 100644 index 2c717fd1419..00000000000 --- a/test/EFCore.InMemory.FunctionalTests/StoreGeneratedInMemoryTest.cs +++ /dev/null @@ -1,197 +0,0 @@ -// 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 Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Specification.Tests; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.EntityFrameworkCore.InMemory.FunctionalTests -{ - public class StoreGeneratedInMemoryTest - : StoreGeneratedTestBase - { - public StoreGeneratedInMemoryTest(StoreGeneratedInMemoryFixture fixture) - : base(fixture) - { - } - - public override void Identity_key_with_read_only_before_save_throws_if_explicit_values_set() - { - // In-memory store does not support store generation - } - - public override void Identity_property_on_Added_entity_with_temporary_value_gets_value_from_store() - { - // In-memory store does not support store generation - } - - public override void Identity_property_on_Added_entity_with_default_value_gets_value_from_store() - { - // In-memory store does not support store generation - } - - public override void Identity_property_on_Modified_entity_with_read_only_after_save_throws_if_value_is_in_modified_state() - { - // In-memory store does not support store generation - } - - public override void Identity_property_on_Modified_entity_is_included_in_update_when_modified() - { - // In-memory store does not support store generation - } - - public override void Identity_property_on_Modified_entity_is_not_included_in_update_when_not_modified() - { - // In-memory store does not support store generation - } - - public override void Computed_property_on_Added_entity_with_temporary_value_gets_value_from_store() - { - // In-memory store does not support store generation - } - - public override void Computed_property_on_Added_entity_with_default_value_gets_value_from_store() - { - // In-memory store does not support store generation - } - - public override void Computed_property_on_Modified_entity_with_read_only_after_save_throws_if_value_is_in_modified_state() - { - // In-memory store does not support store generation - } - - public override void Computed_property_on_Modified_entity_is_included_in_update_when_modified() - { - // In-memory store does not support store generation - } - - public override void Computed_property_on_Modified_entity_is_read_from_store_when_not_modified() - { - // In-memory store does not support store generation - } - - public override void Always_computed_property_on_Added_entity_with_temporary_value_gets_value_from_store() - { - // In-memory store does not support store generation - } - - public override void Always_computed_property_on_Added_entity_with_default_value_gets_value_from_store() - { - // In-memory store does not support store generation - } - - public override void Always_computed_property_on_Added_entity_with_read_only_before_save_throws_if_explicit_values_set() - { - // In-memory store does not support store generation - } - - public override void Always_computed_property_on_Added_entity_cannot_have_value_set_explicitly() - { - // In-memory store does not support store generation - } - - public override void Always_computed_property_on_Modified_entity_with_read_only_after_save_throws_if_value_is_in_modified_state() - { - // In-memory store does not support store generation - } - - public override void Always_computed_property_on_Modified_entity_is_not_included_in_update_even_when_modified() - { - // In-memory store does not support store generation - } - - public override void Always_computed_property_on_Modified_entity_is_read_from_store_when_not_modified() - { - // In-memory store does not support store generation - } - - public override void Always_identity_property_on_Added_entity_with_temporary_value_gets_value_from_store() - { - // In-memory store does not support store generation - } - - public override void Always_identity_property_on_Added_entity_with_default_value_gets_value_from_store() - { - // In-memory store does not support store generation - } - - public override void Always_identity_property_on_Added_entity_with_read_only_before_save_throws_if_explicit_values_set() - { - // In-memory store does not support store generation - } - - public override void Always_identity_property_on_Added_entity_gets_store_value_even_when_set_explicitly() - { - // In-memory store does not support store generation - } - - public override void Always_identity_property_on_Modified_entity_with_read_only_after_save_throws_if_value_is_in_modified_state() - { - // In-memory store does not support store generation - } - - public override void Always_identity_property_on_Modified_entity_is_not_included_in_update_when_modified() - { - // In-memory store does not support store generation - } - - public override void Always_identity_property_on_Modified_entity_is_not_included_in_the_update_when_not_modified() - { - // In-memory store does not support store generation - } - - public class StoreGeneratedInMemoryFixture : StoreGeneratedFixtureBase - { - private const string DatabaseName = "StoreGeneratedTest"; - - private readonly IServiceProvider _serviceProvider; - - public StoreGeneratedInMemoryFixture() - { - _serviceProvider = new ServiceCollection() - .AddEntityFrameworkInMemoryDatabase() - .AddSingleton(TestModelSource.GetFactory(OnModelCreating)) - .BuildServiceProvider(); - } - - public override InMemoryTestStore CreateTestStore() - => InMemoryTestStore.GetOrCreateShared(DatabaseName, () => - { - var optionsBuilder = new DbContextOptionsBuilder() - .UseInMemoryDatabase(nameof(StoreGeneratedInMemoryFixture)) - .ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning)) - .UseInternalServiceProvider(_serviceProvider); - - using (var context = new StoreGeneratedContext(optionsBuilder.Options)) - { - context.Database.EnsureDeleted(); - context.Database.EnsureCreated(); - } - }); - - public override DbContext CreateContext(InMemoryTestStore testStore) - { - var optionsBuilder = new DbContextOptionsBuilder() - .UseInMemoryDatabase(nameof(StoreGeneratedInMemoryFixture)) - .ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning)) - .UseInternalServiceProvider(_serviceProvider); - - var context = new StoreGeneratedContext(optionsBuilder.Options); - - return context; - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity(b => - { - // In-memory store does not support store generation of keys - b.Property(e => e.Id).Metadata.IsReadOnlyBeforeSave = false; - }); - } - } - } -} diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs index d47cdb428d4..e9fbfd20b33 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs @@ -6,6 +6,7 @@ using System.Linq; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Specification.Tests.TestUtilities.Xunit; using Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests.Utilities; using Microsoft.EntityFrameworkCore.Storage; @@ -180,12 +181,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasSequence("MySequence") .StartsAt(77); - // TODO: Nested closure for Metadata modelBuilder .Entity() .Property(e => e.Id) .HasDefaultValueSql("next value for MySequence") - .Metadata.IsReadOnlyBeforeSave = true; + .Metadata.BeforeSaveBehavior = PropertyValueBehavior.Throw; } } @@ -390,7 +390,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() .Property(e => e.CreatedOn) .HasDefaultValueSql("getdate()") - .Metadata.IsReadOnlyBeforeSave = true; + .Metadata.BeforeSaveBehavior = PropertyValueBehavior.Throw; } } @@ -434,9 +434,13 @@ public BlogContextComputedColumn(string databaseName) protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity() + var property = modelBuilder.Entity() .Property(e => e.FullName) - .HasComputedColumnSql("FirstName + ' ' + LastName"); + .HasComputedColumnSql("FirstName + ' ' + LastName") + .Metadata; + + property.BeforeSaveBehavior = PropertyValueBehavior.Throw; + property.AfterSaveBehavior = PropertyValueBehavior.Throw; } } @@ -491,7 +495,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .Property(e => e.FullName) - .HasComputedColumnSql("[dbo].[GetFullName]([FirstName], [LastName])"); + .HasComputedColumnSql("[dbo].[GetFullName]([FirstName], [LastName])") + .Metadata.AfterSaveBehavior = PropertyValueBehavior.Throw; } } @@ -825,7 +830,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .Entity() .Property(e => e.Id) .HasDefaultValueSql("next value for MySequence") - .Metadata.IsReadOnlyBeforeSave = true; + .Metadata.BeforeSaveBehavior = PropertyValueBehavior.Throw; } } diff --git a/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs index 7ba9cb70231..ad658449918 100644 --- a/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs @@ -110,49 +110,49 @@ public override DbContext CreateContext(SqlServerTestStore testStore) protected override void OnModelCreating(ModelBuilder modelBuilder) { - base.OnModelCreating(modelBuilder); - modelBuilder.Entity(b => { - b.Property(e => e.Id) - .UseSqlServerIdentityColumn(); - - b.Property(e => e.Identity) - .HasDefaultValue("Banana Joe"); - - b.Property(e => e.IdentityReadOnlyBeforeSave) - .HasDefaultValue("Doughnut Sheriff"); - - b.Property(e => e.IdentityReadOnlyAfterSave) - .HasDefaultValue("Anton"); - - b.Property(e => e.AlwaysIdentity) - .HasDefaultValue("Banana Joe"); - - b.Property(e => e.AlwaysIdentityReadOnlyBeforeSave) - .HasDefaultValue("Doughnut Sheriff"); - - b.Property(e => e.AlwaysIdentityReadOnlyAfterSave) - .HasDefaultValue("Anton"); - - b.Property(e => e.Computed) - .HasDefaultValue("Alan"); - - b.Property(e => e.ComputedReadOnlyBeforeSave) - .HasDefaultValue("Carmen"); - - b.Property(e => e.ComputedReadOnlyAfterSave) - .HasDefaultValue("Tina Rex"); - - b.Property(e => e.AlwaysComputed) - .HasDefaultValue("Alan"); - - b.Property(e => e.AlwaysComputedReadOnlyBeforeSave) - .HasDefaultValue("Carmen"); + b.Property(e => e.Id).UseSqlServerIdentityColumn(); + b.Property(e => e.Identity).HasDefaultValue("Banana Joe"); + b.Property(e => e.IdentityReadOnlyBeforeSave).HasDefaultValue("Doughnut Sheriff"); + b.Property(e => e.IdentityReadOnlyAfterSave).HasDefaultValue("Anton"); + b.Property(e => e.AlwaysIdentity).HasDefaultValue("Banana Joe"); + b.Property(e => e.AlwaysIdentityReadOnlyBeforeSave).HasDefaultValue("Doughnut Sheriff"); + b.Property(e => e.AlwaysIdentityReadOnlyAfterSave).HasDefaultValue("Anton"); + b.Property(e => e.Computed).HasDefaultValue("Alan"); + b.Property(e => e.ComputedReadOnlyBeforeSave).HasDefaultValue("Carmen"); + b.Property(e => e.ComputedReadOnlyAfterSave).HasDefaultValue("Tina Rex"); + b.Property(e => e.AlwaysComputed).HasDefaultValue("Alan"); + b.Property(e => e.AlwaysComputedReadOnlyBeforeSave).HasDefaultValue("Carmen"); + b.Property(e => e.AlwaysComputedReadOnlyAfterSave).HasDefaultValue("Tina Rex"); + }); - b.Property(e => e.AlwaysComputedReadOnlyAfterSave) - .HasDefaultValue("Tina Rex"); + modelBuilder.Entity(b => + { + b.Property(e => e.OnAdd).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddUseBeforeUseAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddIgnoreBeforeUseAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddThrowBeforeUseAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddUseBeforeIgnoreAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddIgnoreBeforeIgnoreAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddThrowBeforeIgnoreAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddUseBeforeThrowAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddIgnoreBeforeThrowAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddThrowBeforeThrowAfter).HasDefaultValue("Rabbit"); + + b.Property(e => e.OnAddOrUpdate).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateUseBeforeUseAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateIgnoreBeforeUseAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateThrowBeforeUseAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateUseBeforeIgnoreAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateIgnoreBeforeIgnoreAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateThrowBeforeIgnoreAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateUseBeforeThrowAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateIgnoreBeforeThrowAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateThrowBeforeThrowAfter).HasDefaultValue("Rabbit"); }); + + base.OnModelCreating(modelBuilder); } } } diff --git a/test/EFCore.Sqlite.FunctionalTests/StoreGeneratedSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/StoreGeneratedSqliteTest.cs index 7c559d330ca..335f7c574bd 100644 --- a/test/EFCore.Sqlite.FunctionalTests/StoreGeneratedSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/StoreGeneratedSqliteTest.cs @@ -36,17 +36,18 @@ public StoreGeneratedSqliteFixture() public override SqliteTestStore CreateTestStore() { - return SqliteTestStore.GetOrCreateShared(DatabaseName, () => - { - var optionsBuilder = new DbContextOptionsBuilder() - .UseSqlite(SqliteTestStore.CreateConnectionString(DatabaseName)) - .UseInternalServiceProvider(_serviceProvider); - - using (var context = new StoreGeneratedContext(optionsBuilder.Options)) + return SqliteTestStore.GetOrCreateShared( + DatabaseName, () => { - context.Database.EnsureClean(); - } - }); + var optionsBuilder = new DbContextOptionsBuilder() + .UseSqlite(SqliteTestStore.CreateConnectionString(DatabaseName)) + .UseInternalServiceProvider(_serviceProvider); + + using (var context = new StoreGeneratedContext(optionsBuilder.Options)) + { + context.Database.EnsureClean(); + } + }); } public override DbContext CreateContext(SqliteTestStore testStore) @@ -63,46 +64,50 @@ public override DbContext CreateContext(SqliteTestStore testStore) protected override void OnModelCreating(ModelBuilder modelBuilder) { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity(b => - { - b.Property(e => e.Identity) - .HasDefaultValue("Banana Joe"); - - b.Property(e => e.IdentityReadOnlyBeforeSave) - .HasDefaultValue("Doughnut Sheriff"); - - b.Property(e => e.IdentityReadOnlyAfterSave) - .HasDefaultValue("Anton"); - - b.Property(e => e.AlwaysIdentity) - .HasDefaultValue("Banana Joe"); - - b.Property(e => e.AlwaysIdentityReadOnlyBeforeSave) - .HasDefaultValue("Doughnut Sheriff"); - - b.Property(e => e.AlwaysIdentityReadOnlyAfterSave) - .HasDefaultValue("Anton"); - - b.Property(e => e.Computed) - .HasDefaultValue("Alan"); - - b.Property(e => e.ComputedReadOnlyBeforeSave) - .HasDefaultValue("Carmen"); - - b.Property(e => e.ComputedReadOnlyAfterSave) - .HasDefaultValue("Tina Rex"); - - b.Property(e => e.AlwaysComputed) - .HasDefaultValue("Alan"); - - b.Property(e => e.AlwaysComputedReadOnlyBeforeSave) - .HasDefaultValue("Carmen"); + modelBuilder.Entity( + b => + { + b.Property(e => e.Identity).HasDefaultValue("Banana Joe"); + b.Property(e => e.IdentityReadOnlyBeforeSave).HasDefaultValue("Doughnut Sheriff"); + b.Property(e => e.IdentityReadOnlyAfterSave).HasDefaultValue("Anton"); + b.Property(e => e.AlwaysIdentity).HasDefaultValue("Banana Joe"); + b.Property(e => e.AlwaysIdentityReadOnlyBeforeSave).HasDefaultValue("Doughnut Sheriff"); + b.Property(e => e.AlwaysIdentityReadOnlyAfterSave).HasDefaultValue("Anton"); + b.Property(e => e.Computed).HasDefaultValue("Alan"); + b.Property(e => e.ComputedReadOnlyBeforeSave).HasDefaultValue("Carmen"); + b.Property(e => e.ComputedReadOnlyAfterSave).HasDefaultValue("Tina Rex"); + b.Property(e => e.AlwaysComputed).HasDefaultValue("Alan"); + b.Property(e => e.AlwaysComputedReadOnlyBeforeSave).HasDefaultValue("Carmen"); + b.Property(e => e.AlwaysComputedReadOnlyAfterSave).HasDefaultValue("Tina Rex"); + }); + + modelBuilder.Entity( + b => + { + b.Property(e => e.OnAdd).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddUseBeforeUseAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddIgnoreBeforeUseAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddThrowBeforeUseAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddUseBeforeIgnoreAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddIgnoreBeforeIgnoreAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddThrowBeforeIgnoreAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddUseBeforeThrowAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddIgnoreBeforeThrowAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddThrowBeforeThrowAfter).HasDefaultValue("Rabbit"); + + b.Property(e => e.OnAddOrUpdate).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateUseBeforeUseAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateIgnoreBeforeUseAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateThrowBeforeUseAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateUseBeforeIgnoreAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateIgnoreBeforeIgnoreAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateThrowBeforeIgnoreAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateUseBeforeThrowAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateIgnoreBeforeThrowAfter).HasDefaultValue("Rabbit"); + b.Property(e => e.OnAddOrUpdateThrowBeforeThrowAfter).HasDefaultValue("Rabbit"); + }); - b.Property(e => e.AlwaysComputedReadOnlyAfterSave) - .HasDefaultValue("Tina Rex"); - }); + base.OnModelCreating(modelBuilder); } } } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs b/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs index 29f8ce1d687..cc38f1a3a46 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs @@ -133,8 +133,10 @@ public virtual void Read_only_before_save_properties_throw_if_not_null_or_temp() var entityType = model.FindEntityType(typeof(SomeEntity).FullName); var keyProperty = entityType.FindProperty("Id"); var nonKeyProperty = entityType.FindProperty("Name"); +#pragma warning disable 618 nonKeyProperty.IsReadOnlyBeforeSave = true; keyProperty.IsReadOnlyBeforeSave = true; +#pragma warning restore 618 var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); @@ -163,7 +165,9 @@ public virtual void Read_only_after_save_properties_throw_if_modified() var model = BuildModel(); var entityType = model.FindEntityType(typeof(SomeEntity).FullName); var nonKeyProperty = entityType.FindProperty("Name"); +#pragma warning disable 618 nonKeyProperty.IsReadOnlyAfterSave = true; +#pragma warning restore 618 var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs index 2f2c39cac1f..1ec5fb47516 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs @@ -1536,6 +1536,7 @@ public void Key_properties_are_always_read_only_after_save() var nameProperty = entityType.GetOrAddProperty(Customer.NameProperty); nameProperty.IsNullable = false; +#pragma warning disable 618 Assert.False(((IProperty)idProperty).IsReadOnlyAfterSave); Assert.False(((IProperty)nameProperty).IsReadOnlyAfterSave); Assert.False(((IProperty)idProperty).IsReadOnlyBeforeSave); @@ -1548,11 +1549,11 @@ public void Key_properties_are_always_read_only_after_save() Assert.False(((IProperty)idProperty).IsReadOnlyBeforeSave); Assert.False(((IProperty)nameProperty).IsReadOnlyBeforeSave); - nameProperty.IsReadOnlyAfterSave = true; + nameProperty.AfterSaveBehavior = PropertyValueBehavior.Throw; Assert.Equal( CoreStrings.KeyPropertyMustBeReadOnly(Customer.NameProperty.Name, nameof(Customer)), - Assert.Throws(() => nameProperty.IsReadOnlyAfterSave = false).Message); + Assert.Throws(() => nameProperty.AfterSaveBehavior = PropertyValueBehavior.UseValue).Message); nameProperty.IsReadOnlyBeforeSave = true; @@ -1560,32 +1561,33 @@ public void Key_properties_are_always_read_only_after_save() Assert.True(((IProperty)nameProperty).IsReadOnlyAfterSave); Assert.False(((IProperty)idProperty).IsReadOnlyBeforeSave); Assert.True(((IProperty)nameProperty).IsReadOnlyBeforeSave); +#pragma warning restore 618 } [Fact] - public void Store_computed_values_are_read_only_before_and_after_save_by_default() + public void Store_computed_values_are_ignored_before_and_after_save_by_default() { var model = new Model(); var entityType = model.AddEntityType(typeof(Customer)); var nameProperty = entityType.GetOrAddProperty(Customer.NameProperty); - Assert.False(((IProperty)nameProperty).IsReadOnlyAfterSave); - Assert.False(((IProperty)nameProperty).IsReadOnlyBeforeSave); + Assert.Equal(PropertyValueBehavior.UseValue, nameProperty.BeforeSaveBehavior); + Assert.Equal(PropertyValueBehavior.UseValue, nameProperty.AfterSaveBehavior); nameProperty.ValueGenerated = ValueGenerated.OnAddOrUpdate; - Assert.True(((IProperty)nameProperty).IsReadOnlyAfterSave); - Assert.True(((IProperty)nameProperty).IsReadOnlyBeforeSave); + Assert.Equal(PropertyValueBehavior.Ignore, nameProperty.BeforeSaveBehavior); + Assert.Equal(PropertyValueBehavior.Ignore, nameProperty.AfterSaveBehavior); - nameProperty.IsReadOnlyBeforeSave = false; + nameProperty.BeforeSaveBehavior = PropertyValueBehavior.UseValue; - Assert.True(((IProperty)nameProperty).IsReadOnlyAfterSave); - Assert.False(((IProperty)nameProperty).IsReadOnlyBeforeSave); + Assert.Equal(PropertyValueBehavior.UseValue, nameProperty.BeforeSaveBehavior); + Assert.Equal(PropertyValueBehavior.Ignore, nameProperty.AfterSaveBehavior); - nameProperty.IsReadOnlyAfterSave = false; + nameProperty.AfterSaveBehavior = PropertyValueBehavior.UseValue; - Assert.False(((IProperty)nameProperty).IsReadOnlyAfterSave); - Assert.False(((IProperty)nameProperty).IsReadOnlyBeforeSave); + Assert.Equal(PropertyValueBehavior.UseValue, nameProperty.BeforeSaveBehavior); + Assert.Equal(PropertyValueBehavior.UseValue, nameProperty.AfterSaveBehavior); } [Fact] @@ -1595,6 +1597,7 @@ public void Store_always_computed_values_are_not_read_only_before_and_after_save var entityType = model.AddEntityType(typeof(Customer)); var nameProperty = entityType.GetOrAddProperty(Customer.NameProperty); +#pragma warning disable 618 Assert.False(((IProperty)nameProperty).IsReadOnlyAfterSave); Assert.False(((IProperty)nameProperty).IsReadOnlyBeforeSave); @@ -1613,6 +1616,7 @@ public void Store_always_computed_values_are_not_read_only_before_and_after_save Assert.True(((IProperty)nameProperty).IsReadOnlyAfterSave); Assert.True(((IProperty)nameProperty).IsReadOnlyBeforeSave); +#pragma warning restore 618 } [Fact] diff --git a/test/EFCore.Tests/Metadata/Internal/InternalPropertyBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalPropertyBuilderTest.cs index 5231084474f..13a13640947 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalPropertyBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalPropertyBuilderTest.cs @@ -8,9 +8,7 @@ using Microsoft.EntityFrameworkCore.InMemory.FunctionalTests; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Specification.Tests; using Microsoft.EntityFrameworkCore.ValueGeneration; using Xunit; @@ -330,69 +328,69 @@ public void Cannot_set_required_to_false_if_nonnullable() } [Fact] - public void Can_only_override_lower_or_equal_source_ReadOnlyAfterSave() + public void Can_only_override_lower_or_equal_source_BeforeSaveBehavior() { var builder = CreateInternalPropertyBuilder(); var metadata = builder.Metadata; - Assert.True(builder.ReadOnlyAfterSave(true, ConfigurationSource.DataAnnotation)); - Assert.True(builder.ReadOnlyAfterSave(false, ConfigurationSource.DataAnnotation)); + Assert.True(builder.BeforeSave(PropertyValueBehavior.Throw, ConfigurationSource.DataAnnotation)); + Assert.True(builder.BeforeSave(PropertyValueBehavior.Ignore, ConfigurationSource.DataAnnotation)); - Assert.False(metadata.IsReadOnlyAfterSave); + Assert.Equal(PropertyValueBehavior.Ignore, metadata.BeforeSaveBehavior); - Assert.False(builder.ReadOnlyAfterSave(true, ConfigurationSource.Convention)); - Assert.False(metadata.IsReadOnlyAfterSave); + Assert.False(builder.BeforeSave(PropertyValueBehavior.UseValue, ConfigurationSource.Convention)); + Assert.Equal(PropertyValueBehavior.Ignore, metadata.BeforeSaveBehavior); } [Fact] - public void Can_only_override_existing_ReadOnlyAfterSave_value_explicitly() + public void Can_only_override_existing_BeforeSaveBehavior_value_explicitly() { var metadata = CreateProperty(); - Assert.Null(metadata.GetIsReadOnlyAfterSaveConfigurationSource()); - metadata.IsReadOnlyAfterSave = false; + Assert.Null(metadata.GetBeforeSaveBehaviorConfigurationSource()); + metadata.BeforeSaveBehavior = PropertyValueBehavior.Throw; var builder = metadata.Builder; - Assert.Equal(ConfigurationSource.Explicit, metadata.GetIsReadOnlyAfterSaveConfigurationSource()); - Assert.True(builder.ReadOnlyAfterSave(false, ConfigurationSource.DataAnnotation)); - Assert.False(builder.ReadOnlyAfterSave(true, ConfigurationSource.DataAnnotation)); + Assert.Equal(ConfigurationSource.Explicit, metadata.GetBeforeSaveBehaviorConfigurationSource()); + Assert.True(builder.BeforeSave(PropertyValueBehavior.Throw, ConfigurationSource.DataAnnotation)); + Assert.False(builder.BeforeSave(PropertyValueBehavior.Ignore, ConfigurationSource.DataAnnotation)); - Assert.False(metadata.IsReadOnlyAfterSave); + Assert.Equal(PropertyValueBehavior.Throw, metadata.BeforeSaveBehavior); - Assert.True(builder.ReadOnlyAfterSave(true, ConfigurationSource.Explicit)); - Assert.True(metadata.IsReadOnlyAfterSave); + Assert.True(builder.BeforeSave(PropertyValueBehavior.Ignore, ConfigurationSource.Explicit)); + Assert.Equal(PropertyValueBehavior.Ignore, metadata.BeforeSaveBehavior); } [Fact] - public void Can_only_override_lower_or_equal_source_ReadOnlyBeforeSave() + public void Can_only_override_lower_or_equal_source_AfterSaveBehavior() { var builder = CreateInternalPropertyBuilder(); var metadata = builder.Metadata; - Assert.True(builder.ReadOnlyBeforeSave(true, ConfigurationSource.DataAnnotation)); - Assert.True(builder.ReadOnlyBeforeSave(false, ConfigurationSource.DataAnnotation)); + Assert.True(builder.AfterSave(PropertyValueBehavior.Throw, ConfigurationSource.DataAnnotation)); + Assert.True(builder.AfterSave(PropertyValueBehavior.Ignore, ConfigurationSource.DataAnnotation)); - Assert.False(metadata.IsReadOnlyBeforeSave); + Assert.Equal(PropertyValueBehavior.Ignore, metadata.AfterSaveBehavior); - Assert.False(builder.ReadOnlyBeforeSave(true, ConfigurationSource.Convention)); - Assert.False(metadata.IsReadOnlyBeforeSave); + Assert.False(builder.AfterSave(PropertyValueBehavior.UseValue, ConfigurationSource.Convention)); + Assert.Equal(PropertyValueBehavior.Ignore, metadata.AfterSaveBehavior); } [Fact] - public void Can_only_override_existing_ReadOnlyBeforeSave_value_explicitly() + public void Can_only_override_existing_AfterSaveBehavior_value_explicitly() { var metadata = CreateProperty(); - Assert.Null(metadata.GetIsReadOnlyBeforeSaveConfigurationSource()); - metadata.IsReadOnlyBeforeSave = true; + Assert.Null(metadata.GetAfterSaveBehaviorConfigurationSource()); + metadata.AfterSaveBehavior = PropertyValueBehavior.Throw; var builder = metadata.Builder; - Assert.Equal(ConfigurationSource.Explicit, metadata.GetIsReadOnlyBeforeSaveConfigurationSource()); - Assert.True(builder.ReadOnlyBeforeSave(true, ConfigurationSource.DataAnnotation)); - Assert.False(builder.ReadOnlyBeforeSave(false, ConfigurationSource.DataAnnotation)); + Assert.Equal(ConfigurationSource.Explicit, metadata.GetAfterSaveBehaviorConfigurationSource()); + Assert.True(builder.AfterSave(PropertyValueBehavior.Throw, ConfigurationSource.DataAnnotation)); + Assert.False(builder.AfterSave(PropertyValueBehavior.Ignore, ConfigurationSource.DataAnnotation)); - Assert.True(metadata.IsReadOnlyBeforeSave); + Assert.Equal(PropertyValueBehavior.Throw, metadata.AfterSaveBehavior); - Assert.True(builder.ReadOnlyBeforeSave(false, ConfigurationSource.Explicit)); - Assert.False(metadata.IsReadOnlyBeforeSave); + Assert.True(builder.AfterSave(PropertyValueBehavior.Ignore, ConfigurationSource.Explicit)); + Assert.Equal(PropertyValueBehavior.Ignore, metadata.AfterSaveBehavior); } private InternalPropertyBuilder CreateInternalPropertyBuilder() diff --git a/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs b/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs index e0218f3a844..0a930bfeebb 100644 --- a/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs @@ -172,6 +172,7 @@ public void Can_mark_property_to_always_use_store_generated_values() var entityType = new Model().AddEntityType(typeof(Entity)); var property = entityType.AddProperty("Name", typeof(string)); +#pragma warning disable 618 Assert.False(property.IsStoreGeneratedAlways); property.IsStoreGeneratedAlways = true; @@ -179,6 +180,7 @@ public void Can_mark_property_to_always_use_store_generated_values() property.IsStoreGeneratedAlways = false; Assert.False(property.IsStoreGeneratedAlways); +#pragma warning restore 618 } [Fact] @@ -187,10 +189,11 @@ public void Store_generated_concurrency_tokens_always_use_store_values_by_defaul var entityType = new Model().AddEntityType(typeof(Entity)); var property = entityType.AddProperty("Name", typeof(string)); +#pragma warning disable 618 Assert.False(((IProperty)property).IsStoreGeneratedAlways); property.ValueGenerated = ValueGenerated.OnAddOrUpdate; - Assert.False(((IProperty)property).IsStoreGeneratedAlways); + Assert.True(((IProperty)property).IsStoreGeneratedAlways); property.IsConcurrencyToken = true; Assert.True(((IProperty)property).IsStoreGeneratedAlways); @@ -203,6 +206,7 @@ public void Store_generated_concurrency_tokens_always_use_store_values_by_defaul property.IsStoreGeneratedAlways = false; Assert.False(((IProperty)property).IsStoreGeneratedAlways); +#pragma warning restore 618 } [Fact] @@ -211,8 +215,10 @@ public void Property_is_read_write_by_default() var entityType = new Model().AddEntityType(typeof(Entity)); var property = entityType.AddProperty("Name", typeof(string)); +#pragma warning disable 618 Assert.False(property.IsReadOnlyAfterSave); Assert.False(property.IsReadOnlyBeforeSave); +#pragma warning restore 618 } [Fact] @@ -220,12 +226,11 @@ public void Property_can_be_marked_as_read_only_before_save() { var entityType = new Model().AddEntityType(typeof(Entity)); var property = entityType.AddProperty("Name", typeof(string)); +#pragma warning disable 618 property.IsReadOnlyBeforeSave = true; Assert.True(property.IsReadOnlyBeforeSave); - - property.IsReadOnlyBeforeSave = false; - Assert.False(property.IsReadOnlyBeforeSave); +#pragma warning restore 618 } [Fact] @@ -233,12 +238,11 @@ public void Property_can_be_marked_as_read_only_after_save() { var entityType = new Model().AddEntityType(typeof(Entity)); var property = entityType.AddProperty("Name", typeof(string)); +#pragma warning disable 618 property.IsReadOnlyAfterSave = true; Assert.True(property.IsReadOnlyAfterSave); - - property.IsReadOnlyAfterSave = false; - Assert.False(property.IsReadOnlyAfterSave); +#pragma warning restore 618 } [Fact] @@ -247,6 +251,7 @@ public void Property_can_be_marked_as_read_only_always() var entityType = new Model().AddEntityType(typeof(Entity)); var property = entityType.AddProperty("Name", typeof(string)); +#pragma warning disable 618 Assert.False(property.IsReadOnlyBeforeSave); Assert.False(property.IsReadOnlyAfterSave); @@ -255,6 +260,7 @@ public void Property_can_be_marked_as_read_only_always() Assert.True(property.IsReadOnlyBeforeSave); Assert.True(property.IsReadOnlyAfterSave); +#pragma warning restore 618 } private class Entity