Skip to content

Commit

Permalink
Handle row-version columns in owned types with conversions
Browse files Browse the repository at this point in the history
Fixes #29689

Also add more end-to-end tests for optimistic concurrency, converters, owned types, inheritance, and table sharing.
  • Loading branch information
ajcvickers committed Dec 1, 2022
1 parent 35e9e5a commit f10256a
Show file tree
Hide file tree
Showing 16 changed files with 876 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,38 @@ public virtual void ProcessModelFinalizing(

foreach (var (conventionEntityType, exampleProperty) in entityTypesMissingConcurrencyColumn)
{
var providerType = exampleProperty.GetProviderClrType()
?? (exampleProperty.GetValueConverter() ?? exampleProperty.FindTypeMapping()?.Converter)?.ProviderClrType
?? exampleProperty.ClrType;
conventionEntityType.Builder.CreateUniqueProperty(
providerType,
var propertyBuilder = conventionEntityType.Builder.CreateUniqueProperty(
exampleProperty.ClrType,
ConcurrencyPropertyPrefix + exampleProperty.Name,
!exampleProperty.IsNullable)!
.HasColumnName(concurrencyColumnName)!
.HasColumnType(exampleProperty.GetColumnType())!
.IsConcurrencyToken(true)!
.ValueGenerated(exampleProperty.ValueGenerated);
.ValueGenerated(exampleProperty.ValueGenerated)!;

var typeMapping = exampleProperty.FindTypeMapping();
if (typeMapping != null)
{
propertyBuilder = propertyBuilder.HasTypeMapping(typeMapping)!;
}

var converter = exampleProperty.GetValueConverter();
if (converter != null)
{
propertyBuilder = propertyBuilder.HasConversion(converter)!;
}

var providerType = exampleProperty.GetProviderClrType();
if (providerType != propertyBuilder.Metadata.GetProviderClrType())
{
propertyBuilder = propertyBuilder.HasConversion(providerType)!;
}

var comparer = exampleProperty.GetValueComparer();
if (comparer != null)
{
propertyBuilder.HasValueComparer(comparer);
}
}
}
}
Expand Down
57 changes: 57 additions & 0 deletions test/EFCore.Relational.Specification.Tests/F1RelationalFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,62 @@ protected override void BuildModelExternal(ModelBuilder modelBuilder)
modelBuilder.Entity<EngineSupplier>().ToTable("EngineSuppliers");
modelBuilder.Entity<Gearbox>().ToTable("Gearboxes");
modelBuilder.Entity<Sponsor>().ToTable("Sponsors");

modelBuilder.Entity<FanTpt>().UseTptMappingStrategy();
modelBuilder.Entity<FanTpc>().UseTpcMappingStrategy();

modelBuilder.Entity<Circuit>(
b =>
{
b.ToTable("Circuits");
b.Property(e => e.Name).HasColumnName("Name");
});

modelBuilder.Entity<City>(
b =>
{
b.ToTable("Circuits");
b.Property(e => e.Name).HasColumnName("Name");
});

modelBuilder.Entity<CircuitTpt>(
b =>
{
b.UseTptMappingStrategy();
b.Property(e => e.Name).HasColumnName("Name");
});

modelBuilder.Entity<StreetCircuitTpt>(
b =>
{
b.ToTable("StreetCircuitsTpt");
});

modelBuilder.Entity<CityTpt>(
b =>
{
b.ToTable("StreetCircuitsTpt");
b.Property(e => e.Name).HasColumnName("Name");
});

modelBuilder.Entity<CircuitTpc>(
b =>
{
b.UseTpcMappingStrategy();
b.Property(e => e.Name).HasColumnName("Name");
});

modelBuilder.Entity<StreetCircuitTpc>(
b =>
{
b.ToTable("StreetCircuitsTpc");
});

modelBuilder.Entity<CityTpc>(
b =>
{
b.ToTable("StreetCircuitsTpc");
b.Property(e => e.Name).HasColumnName("Name");
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1834,7 +1834,7 @@ public virtual void Passes_for_missing_concurrency_token_property_on_the_sharing
var personType = model.FindEntityType(typeof(Person))!;
var concurrencyProperty = personType.GetDeclaredProperties().Single(p => p.IsConcurrencyToken);
Assert.Equal("Version", concurrencyProperty.GetColumnName());
Assert.Equal(typeof(byte[]), concurrencyProperty.ClrType);
Assert.Equal(typeof(ulong), concurrencyProperty.ClrType);
}

[ConditionalFact]
Expand Down
27 changes: 27 additions & 0 deletions test/EFCore.Specification.Tests/F1FixtureBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,33 @@ protected virtual void BuildModelExternal(ModelBuilder modelBuilder)
eb.Property<TRowVersion>("Version").IsRowVersion();
eb.Property<int?>(Sponsor.ClientTokenPropertyName).IsConcurrencyToken();
});

modelBuilder.Entity<Fan>();
modelBuilder.Entity<SuperFan>();
modelBuilder.Entity<MegaFan>();

modelBuilder.Entity<FanTpt>();
modelBuilder.Entity<SuperFanTpt>();
modelBuilder.Entity<MegaFanTpt>();

modelBuilder.Entity<FanTpc>();
modelBuilder.Entity<SuperFanTpc>();
modelBuilder.Entity<MegaFanTpc>();

modelBuilder.Entity<Circuit>();
modelBuilder.Entity<StreetCircuit>().HasOne(e => e.City).WithOne().HasForeignKey<City>(e => e.Id);
modelBuilder.Entity<OvalCircuit>();
modelBuilder.Entity<City>();

modelBuilder.Entity<CircuitTpt>();
modelBuilder.Entity<StreetCircuitTpt>().HasOne(e => e.City).WithOne().HasForeignKey<CityTpt>(e => e.Id);
modelBuilder.Entity<OvalCircuitTpt>();
modelBuilder.Entity<CityTpt>();

modelBuilder.Entity<CircuitTpc>();
modelBuilder.Entity<StreetCircuitTpc>().HasOne(e => e.City).WithOne().HasForeignKey<CityTpc>(e => e.Id);
modelBuilder.Entity<OvalCircuitTpc>();
modelBuilder.Entity<CityTpc>();
}

private static void ConfigureConstructorBinding<TEntity>(IMutableEntityType mutableEntityType, params string[] propertyNames)
Expand Down
32 changes: 32 additions & 0 deletions test/EFCore.Specification.Tests/F1MaterializationInterceptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,38 @@ public InterceptionResult<object> CreatingInstance(
nameof(TitleSponsor) => InterceptionResult<object>.SuppressWithResult(
new TitleSponsor.TitleSponsorProxy(
materializationData.GetPropertyValue<ILazyLoader>("_loader"))),
nameof(SuperFan) => InterceptionResult<object>.SuppressWithResult(
new SuperFan.SuperFanProxy()),
nameof(MegaFan) => InterceptionResult<object>.SuppressWithResult(
new MegaFan.MegaFanProxy()),
nameof(SuperFanTpt) => InterceptionResult<object>.SuppressWithResult(
new SuperFanTpt.SuperFanTptProxy()),
nameof(MegaFanTpt) => InterceptionResult<object>.SuppressWithResult(
new MegaFanTpt.MegaFanTptProxy()),
nameof(SuperFanTpc) => InterceptionResult<object>.SuppressWithResult(
new SuperFanTpc.SuperFanTpcProxy()),
nameof(MegaFanTpc) => InterceptionResult<object>.SuppressWithResult(
new MegaFanTpc.MegaFanTpcProxy()),
nameof(SwagBag) => InterceptionResult<object>.SuppressWithResult(
new SwagBag.SwagBagProxy()),
nameof(StreetCircuit) => InterceptionResult<object>.SuppressWithResult(
new StreetCircuit.StreetCircuitProxy()),
nameof(OvalCircuit) => InterceptionResult<object>.SuppressWithResult(
new OvalCircuit.OvalCircuitProxy()),
nameof(City) => InterceptionResult<object>.SuppressWithResult(
new City.CityProxy()),
nameof(StreetCircuitTpt) => InterceptionResult<object>.SuppressWithResult(
new StreetCircuitTpt.StreetCircuitTptProxy()),
nameof(OvalCircuitTpt) => InterceptionResult<object>.SuppressWithResult(
new OvalCircuitTpt.OvalCircuitTptProxy()),
nameof(CityTpt) => InterceptionResult<object>.SuppressWithResult(
new CityTpt.CityTptProxy()),
nameof(StreetCircuitTpc) => InterceptionResult<object>.SuppressWithResult(
new StreetCircuitTpc.StreetCircuitTpcProxy()),
nameof(OvalCircuitTpc) => InterceptionResult<object>.SuppressWithResult(
new OvalCircuit.OvalCircuitProxy()),
nameof(CityTpc) => InterceptionResult<object>.SuppressWithResult(
new CityTpc.CityTpcProxy()),
_ => result
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel;

public interface IStreetCircuit<TCity>
{
string Name { get; set; }
public TCity City { get; set; }
}

public abstract class Circuit
{
public int Id { get; set; }
public string Name { get; set; }
public ulong ULongVersion { get; init; }

[NotMapped]
public List<byte> BinaryVersion { get; init; }
}

public class StreetCircuit : Circuit, IStreetCircuit<City>
{
public class StreetCircuitProxy : StreetCircuit, IF1Proxy
{
public bool CreatedCalled { get; set; }
public bool InitializingCalled { get; set; }
public bool InitializedCalled { get; set; }
}

public int Length { get; set; }

[Required]
public City City { get; set; }
}

public class OvalCircuit : Circuit
{
public class OvalCircuitProxy : OvalCircuit, IF1Proxy
{
public bool CreatedCalled { get; set; }
public bool InitializingCalled { get; set; }
public bool InitializedCalled { get; set; }
}

public double Banking { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel;

public abstract class CircuitTpc
{
public int Id { get; set; }
public string Name { get; set; }
public ulong ULongVersion { get; init; }

[NotMapped]
public List<byte> BinaryVersion { get; init; }
}

public class StreetCircuitTpc : CircuitTpc, IStreetCircuit<CityTpc>
{
public class StreetCircuitTpcProxy : StreetCircuitTpc, IF1Proxy
{
public bool CreatedCalled { get; set; }
public bool InitializingCalled { get; set; }
public bool InitializedCalled { get; set; }
}

public int Length { get; set; }

[Required]
public CityTpc City { get; set; }
}

public class OvalCircuitTpc : CircuitTpc
{
public class OvalCircuitTpcProxy : OvalCircuitTpc, IF1Proxy
{
public bool CreatedCalled { get; set; }
public bool InitializingCalled { get; set; }
public bool InitializedCalled { get; set; }
}

public double Banking { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel;

public abstract class CircuitTpt
{
public int Id { get; set; }
public string Name { get; set; }
public ulong ULongVersion { get; init; }

[NotMapped]
public List<byte> BinaryVersion { get; init; }
}

public class StreetCircuitTpt : CircuitTpt, IStreetCircuit<CityTpt>
{
public class StreetCircuitTptProxy : StreetCircuitTpt, IF1Proxy
{
public bool CreatedCalled { get; set; }
public bool InitializingCalled { get; set; }
public bool InitializedCalled { get; set; }
}

public int Length { get; set; }

[Required]
public CityTpt City { get; set; }
}

public class OvalCircuitTpt : CircuitTpt
{
public class OvalCircuitTptProxy : OvalCircuitTpt, IF1Proxy
{
public bool CreatedCalled { get; set; }
public bool InitializingCalled { get; set; }
public bool InitializedCalled { get; set; }
}

public double Banking { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel.DataAnnotations;

namespace Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel;

public interface ICity
{
public string Name { get; set; }
}

public class City : ICity
{
public class CityProxy : City, IF1Proxy
{
public bool CreatedCalled { get; set; }
public bool InitializingCalled { get; set; }
public bool InitializedCalled { get; set; }
}

public int Id { get; set; }

[Required]
public string Name { get; set; }
}

public class CityTpt : ICity
{
public class CityTptProxy : CityTpt, IF1Proxy
{
public bool CreatedCalled { get; set; }
public bool InitializingCalled { get; set; }
public bool InitializedCalled { get; set; }
}

public int Id { get; set; }

[Required]
public string Name { get; set; }
}

public class CityTpc : ICity
{
public class CityTpcProxy : CityTpc, IF1Proxy
{
public bool CreatedCalled { get; set; }
public bool InitializingCalled { get; set; }
public bool InitializedCalled { get; set; }
}

public int Id { get; set; }

[Required]
public string Name { get; set; }
}
Loading

0 comments on commit f10256a

Please sign in to comment.