Skip to content
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
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel;
using Microsoft.EntityFrameworkCore.TestUtilities;

Expand All @@ -10,9 +11,12 @@ public class F1OracleFixture : F1RelationalFixture
{
protected override ITestStoreFactory TestStoreFactory => OracleTestStoreFactory.Instance;

protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
public override ModelBuilder CreateModelBuilder()
=> new ModelBuilder(OracleConventionSetBuilder.Build());

protected override void BuildModelExternal(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder, context);
base.BuildModelExternal(modelBuilder);

modelBuilder.Entity<Chassis>().Property<byte[]>("Version").IsRowVersion();
modelBuilder.Entity<Driver>().Property<byte[]>("Version").IsRowVersion();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.EntityFrameworkCore.InMemory.Metadata.Conventions.Internal
{
Expand All @@ -17,5 +19,25 @@ public class InMemoryConventionSetBuilder : IConventionSetBuilder
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual ConventionSet AddConventions(ConventionSet conventionSet) => conventionSet;

/// <summary>
/// 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.
/// </summary>
public static ConventionSet Build()
{
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.AddDbContext<DbContext>(o => o.UseInMemoryDatabase(Guid.NewGuid().ToString()))
.BuildServiceProvider();

using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
using (var context = serviceScope.ServiceProvider.GetService<DbContext>())
{
return ConventionSet.CreateConventionSet(context);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build
=> base.AddOptions(builder).ConfigureWarnings(w =>
w.Ignore(RelationalEventId.BatchSmallerThanMinBatchSize));

protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
protected override void BuildModelExternal(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder, context);
base.BuildModelExternal(modelBuilder);

modelBuilder.Entity<Chassis>().ToTable("Chassis");
modelBuilder.Entity<Team>().ToTable("Teams").Property(e => e.Id).ValueGeneratedNever();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,7 @@ protected virtual void Generate(params MigrationOperation[] operation)
protected virtual void Generate(Action<ModelBuilder> buildAction, params MigrationOperation[] operation)
{
var modelBuilder = TestHelpers.CreateConventionBuilder();
modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersionAnnotation);
buildAction(modelBuilder);

var batch = TestHelpers.CreateContextServices().GetRequiredService<IMigrationsSqlGenerator>()
Expand Down
11 changes: 6 additions & 5 deletions src/EFCore.Specification.Tests/DataAnnotationTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,22 @@ public virtual ModelBuilder CreateModelBuilder()
var conventionSetBuilder = CreateConventionSetBuilder(context);
var conventionSet = new CoreConventionSetBuilder(context.GetService<CoreConventionSetBuilderDependencies>())
.CreateConventionSet();

conventionSet = conventionSetBuilder == null
? conventionSet
: conventionSetBuilder.AddConventions(conventionSet);

conventionSet.ModelBuiltConventions.Add(
new ValidatingConvention(context.GetService<IModelValidator>()));

return new ModelBuilder(conventionSet);
}

protected virtual IConventionSetBuilder CreateConventionSetBuilder(DbContext context)
=> new CompositeConventionSetBuilder(context.GetService<IEnumerable<IConventionSetBuilder>>().ToList());

protected virtual void Validate(ModelBuilder modelBuilder)
{
modelBuilder.GetInfrastructure().Metadata.Validate();
var context = CreateContext();
context.GetService<IModelValidator>().Validate(modelBuilder.Model);
}
=> modelBuilder.FinalizeModel();

protected class Person
{
Expand Down
24 changes: 21 additions & 3 deletions src/EFCore.Specification.Tests/F1FixtureBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel;

namespace Microsoft.EntityFrameworkCore
Expand All @@ -13,13 +14,30 @@ public abstract class F1FixtureBase : SharedStoreFixtureBase<F1Context>
protected override bool UsePooling => true;

public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
=> base.AddOptions(builder).ConfigureWarnings(w =>
w.Ignore(CoreEventId.SaveChangesStarting, CoreEventId.SaveChangesCompleted));
=> base.AddOptions(builder)
.UseModel(CreateModelExternal())
.ConfigureWarnings(
w => w.Ignore(CoreEventId.SaveChangesStarting, CoreEventId.SaveChangesCompleted));

protected override bool ShouldLogCategory(string logCategory)
=> logCategory == DbLoggerCategory.Update.Name;

protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
private IModel CreateModelExternal()
{
// Doing this differently here from other tests to have regression coverage for
// building models externally from the context instance.
var builder = CreateModelBuilder();

BuildModelExternal(builder);

builder.FinalizeModel();

return builder.Model;
}

public abstract ModelBuilder CreateModelBuilder();

protected virtual void BuildModelExternal(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Chassis>(b => { b.HasKey(c => c.TeamId); });

Expand Down
13 changes: 13 additions & 0 deletions src/EFCore.Specification.Tests/OptimisticConcurrencyTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel;
using Microsoft.EntityFrameworkCore.TestUtilities.Xunit;
Expand All @@ -28,6 +29,18 @@ protected OptimisticConcurrencyTestBase(TFixture fixture)

protected TFixture Fixture { get; }

[Fact]
public virtual void External_model_builder_uses_validation()
{
var modelBuilder = Fixture.CreateModelBuilder();
modelBuilder.Entity("Dummy");

Assert.Equal(
CoreStrings.ShadowEntity("Dummy"),
Assert.Throws<InvalidOperationException>
(() => modelBuilder.FinalizeModel()).Message);
}

[Fact]
public virtual void Nullable_client_side_concurrency_token_can_be_used()
{
Expand Down
10 changes: 3 additions & 7 deletions src/EFCore.Specification.Tests/TestUtilities/TestModelSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
Expand All @@ -24,20 +23,17 @@ private TestModelSource(Action<ModelBuilder, DbContext> onModelCreating, ModelSo
protected override IModel CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
{
var conventionSet = CreateConventionSet(conventionSetBuilder);
conventionSet.ModelBuiltConventions.Add(new ValidatingConvention(validator));

var modelBuilder = new ModelBuilder(conventionSet);
var model = modelBuilder.GetInfrastructure().Metadata;
model.SetProductVersion(ProductInfo.GetVersion());

Dependencies.ModelCustomizer.Customize(modelBuilder, context);

_onModelCreating(modelBuilder, context);

model.Validate();
modelBuilder.FinalizeModel();

validator.Validate(model);

return model;
return modelBuilder.GetInfrastructure().Metadata;
}

public static Func<IServiceProvider, IModelSource> GetFactory(Action<ModelBuilder> onModelCreating)
Expand Down
10 changes: 3 additions & 7 deletions src/EFCore/Infrastructure/ModelSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections.Concurrent;
using System.Threading;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
Expand Down Expand Up @@ -76,18 +75,15 @@ protected virtual IModel CreateModel(
Check.NotNull(validator, nameof(validator));

var conventionSet = CreateConventionSet(conventionSetBuilder);
conventionSet.ModelBuiltConventions.Add(new ValidatingConvention(validator));
Copy link
Member

Choose a reason for hiding this comment

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

Move this to CreateConventionSet


var modelBuilder = new ModelBuilder(conventionSet);
var model = modelBuilder.GetInfrastructure().Metadata;
model.SetProductVersion(ProductInfo.GetVersion());

Dependencies.ModelCustomizer.Customize(modelBuilder, context);

model.Validate();
modelBuilder.FinalizeModel();

validator.Validate(model);

return model;
return modelBuilder.GetInfrastructure().Metadata;
}

/// <summary>
Expand Down
12 changes: 10 additions & 2 deletions src/EFCore/Metadata/Conventions/ConventionSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,15 @@ public class ConventionSet
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static ConventionSet CreateConventionSet([NotNull] DbContext context)
=> new CompositeConventionSetBuilder(context.GetService<IEnumerable<IConventionSetBuilder>>().ToList())
.AddConventions(context.GetService<ICoreConventionSetBuilder>().CreateConventionSet());
{
var conventionSet = new CompositeConventionSetBuilder(
context.GetService<IEnumerable<IConventionSetBuilder>>().ToList())
.AddConventions(
context.GetService<ICoreConventionSetBuilder>().CreateConventionSet());

conventionSet.ModelBuiltConventions.Add(new ValidatingConvention(context.GetService<IModelValidator>()));

return conventionSet;
}
}
}
38 changes: 38 additions & 0 deletions src/EFCore/Metadata/Conventions/Internal/ValidatingConvention.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
{
/// <summary>
/// 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.
/// </summary>
public class ValidatingConvention : IModelBuiltConvention
{
private readonly IModelValidator _validator;

/// <summary>
/// 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.
/// </summary>
public ValidatingConvention([NotNull] IModelValidator validator)
{
_validator = validator;
}

/// <summary>
/// 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.
/// </summary>
public virtual InternalModelBuilder Apply(InternalModelBuilder modelBuilder)
{
_validator.Validate(modelBuilder.Metadata);

return modelBuilder;
}
}
}
18 changes: 18 additions & 0 deletions src/EFCore/ModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.ComponentModel;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
Expand Down Expand Up @@ -38,6 +39,8 @@ public ModelBuilder([NotNull] ConventionSet conventions)
Check.NotNull(conventions, nameof(conventions));

_builder = new InternalModelBuilder(new Model(conventions));

_builder.Metadata.SetProductVersion(ProductInfo.GetVersion());
}

/// <summary>
Expand Down Expand Up @@ -393,6 +396,21 @@ public virtual ModelBuilder UsePropertyAccessMode(PropertyAccessMode propertyAcc
return this;
}

/// <summary>
/// Forces post-processing on the model such that it is ready for use by the runtime. This post
/// processing happens automatically when using OnModelCreating; this method allows it to be run
/// explicitly in cases where the automatic execution is not possible.
/// </summary>
/// <returns>
/// The same <see cref="ModelBuilder" /> instance so that additional configuration calls can be chained.
/// </returns>
public virtual ModelBuilder FinalizeModel()
{
Builder.Metadata.Validate();

return this;
}

private InternalModelBuilder Builder => this.GetInfrastructure();

#region Hidden System.Object members
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ public void Snapshot_with_enum_discriminator_uses_converted_values()
codeHelper))));

var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder();
modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersionAnnotation);
modelBuilder.Entity<WithAnnotations>(
eb =>
{
Expand All @@ -282,7 +283,7 @@ public void Snapshot_with_enum_discriminator_uses_converted_values()
eb.Property<RawEnum>("EnumDiscriminator").HasConversion<int>();
});

modelBuilder.GetInfrastructure().Metadata.Validate();
modelBuilder.FinalizeModel();

var modelSnapshotCode = generator.GenerateSnapshot(
"MyNamespace",
Expand Down Expand Up @@ -336,7 +337,7 @@ private static void AssertConverter(ValueConverter valueConverter, string expect
var property = modelBuilder.Entity<WithAnnotations>().Property(e => e.Id).Metadata;
property.SetMaxLength(1000);

modelBuilder.GetInfrastructure().Metadata.Validate();
modelBuilder.FinalizeModel();

var codeHelper = new CSharpHelper(new SqlServerTypeMappingSource(
TestServiceFactory.Instance.Create<TypeMappingSourceDependencies>(),
Expand Down Expand Up @@ -520,6 +521,7 @@ public void Snapshots_compile()
var generator = CreateMigrationsCodeGenerator();

var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder();
modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersionAnnotation);
modelBuilder.Entity<EntityWithConstructorBinding>(
x =>
{
Expand All @@ -539,7 +541,7 @@ public void Snapshots_compile()
var property2 = entityType.AddProperty("Ham", typeof(RawEnum));
property2.SetValueConverter(new ValueConverter<RawEnum, string>(v => v.ToString(), v => (RawEnum)Enum.Parse(typeof(RawEnum), v), new ConverterMappingHints(size: 10)));

modelBuilder.GetInfrastructure().Metadata.Validate();
modelBuilder.FinalizeModel();

var modelSnapshotCode = generator.GenerateSnapshot(
"MyNamespace",
Expand Down
Loading