Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle row-version columns in owned types with conversions #29730

Merged
merged 2 commits into from
Dec 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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();
ajcvickers marked this conversation as resolved.
Show resolved Hide resolved
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