-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add documentation for pre-convention model configuration
Fixes #3278
- Loading branch information
1 parent
cef190d
commit 6c8b620
Showing
11 changed files
with
283 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
--- | ||
title: Model Bulk Configuration - EF Core | ||
description: How to apply bulk configuration during model building in Entity Framework Core | ||
author: AndriySvyryd | ||
ms.date: 11/05/2021 | ||
uid: core/modeling/bulk-configuration | ||
--- | ||
# Model bulk configuration | ||
|
||
When an aspect needs to be configured in the same way across multiple entity types the following techniques allow to reduce code duplication and consolidate the logic. | ||
|
||
See the [full sample project](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Modeling/BulkConfiguration) containing the code snippets presented below. | ||
|
||
## Metadata API | ||
|
||
Every builder object returned from <xref:Microsoft.EntityFrameworkCore.ModelBuilder> exposes a <xref:Microsoft.EntityFrameworkCore.ModelBuilder.Model> or `Metadata` property that provides a low-level access to the objects that comprise the model. In particular, there are methods that allow you to iterate over specific objects in the model and apply common configuration to them. | ||
|
||
In the following example the model contains a custom value type `Money`: | ||
|
||
[!code-csharp[Main](../../../samples/core/Modeling/BulkConfiguration/Money.cs?name=Money)] | ||
|
||
Properties of this type are not discovered by default as the current EF provider doesn't know how to map it to a database type. This snippet of `OnModelCreating` adds all properties of the type `Money` and configures a value converter to a supported type - `decimal`: | ||
|
||
[!code-csharp[Main](../../../samples/core/Modeling/BulkConfiguration/MetadataAPIContext.cs?name=MetadataAPI)] | ||
|
||
### Drawbacks of the Metadata API | ||
|
||
- Unlike Fluent API, every modification to the model needs to be done explicitly. For example, if some of the `Money` properties were configured as navigations by a convention then you need to first remove the navigation referencing the CLR property before adding an entity type property for it. [#9117](https://github.com/dotnet/efcore/issues/9117) will improve this. | ||
- The conventions run after each change. If you remove a navigation discovered by a convention then the convention will run again and add it back. To prevent this from happening you would need to either delay the conventions until after the property is added by calling <xref:Microsoft.EntityFrameworkCore.Metadata.Conventions.IConventionContext.DelayConventions> or to mark the CLR property as ignored using <xref:Microsoft.EntityFrameworkCore.Metadata.IMutableModel.AddIgnored%2A>. | ||
- Entity types might be added after this iteration happens and the configuration won't be applied to them. This can usually be prevented by placing this code at the end of `OnModelCreating`, but if you have two interdependent sets of configurations there might not be an order that will allow them to be applied consistently. | ||
|
||
## Pre-convention configuration | ||
|
||
EF Core 6.0 allows the mapping configuration to be specified once for a given type. It will then be applied to all properties of that type in the model as they are discovered. This is called "pre-convention model configuration", since it configures aspects of the model that are then used by the model building conventions. Such configuration is applied by overriding `<xref:Microsoft.EntityFrameworkCore.DbContext.ConfigureConventions>` on the type derived from `<xref:Microsoft.EntityFrameworkCore.DbContext>`: | ||
|
||
[!code-csharp[Main](../../../samples/core/Modeling/BulkConfiguration/PreConventionContext.cs?name=MoneyConversion)] | ||
|
||
[!code-csharp[Main](../../../samples/core/Modeling/BulkConfiguration/PreConventionContext.cs?name=StringFacets)] | ||
|
||
> [!NOTE] | ||
> The type specified in a call from `ConfigureConventions` can be a base type, an interface or a generic type definition. All matching configurations will be applied in order from the least specific: | ||
> | ||
> 1. Interface | ||
> 2. Base type | ||
> 3. Generic type definition | ||
> 4. Non-nullable value type | ||
> 5. Exact type | ||
### Ignoring types | ||
|
||
Pre-convention configuration also allows to ignore a type and prevent it from being discovered by conventions either as an entity type or a entity type property: | ||
|
||
[!code-csharp[Main](../../../samples/core/Modeling/BulkConfiguration/PreConventionContext.cs?name=IgnoreInterface)] | ||
|
||
### Default type mapping | ||
|
||
Generally, EF will be able to translate queries with constants of a type that is not supported by the provider if you have specified a value converter for a property of this type. However, in the queries that don't involve any properties of this type there is no way for EF to find the correct value converter. In this case it's possible to call <xref:Microsoft.EntityFrameworkCore.ModelConfigurationBuilder.DefaultTypeMapping> to add or override a provider type mapping: | ||
|
||
[!code-csharp[Main](../../../samples/core/Modeling/BulkConfiguration/PreConventionContext.cs?name=DefaultTypeMapping)] | ||
|
||
### Limitations of pre-convention configuration | ||
|
||
- Many aspects cannot be configured with this approach. [#6787](https://github.com/dotnet/efcore/issues/6787) will expand this to more types. | ||
- Currently the configuration is only determined by the CLR type. [#20418](https://github.com/dotnet/efcore/issues/20418) would allow custom predicates. | ||
- This configuration is performed before a model is created, if there are any conflicts that arise when applying it the exception stack trace will not contain the `ConfigureConventions` method, so it might be harder to find the cause. |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
samples/core/Modeling/BulkConfiguration/BulkConfiguration.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<RootNamespace>EFModeling.BulkConfiguration</RootNamespace> | ||
<AssemblyName>EFModeling.BulkConfiguration</AssemblyName> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0-rc.2.21480.5" /> | ||
</ItemGroup> | ||
|
||
</Project> |
33 changes: 33 additions & 0 deletions
33
samples/core/Modeling/BulkConfiguration/MetadataAPIContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using System; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
namespace EFModeling.BulkConfiguration | ||
{ | ||
public class MetadataAPIContext : DbContext | ||
{ | ||
public DbSet<Product> Products { get; set; } | ||
|
||
protected override void OnModelCreating(ModelBuilder modelBuilder) | ||
{ | ||
#region MetadataAPI | ||
foreach (var entityType in modelBuilder.Model.GetEntityTypes()) | ||
{ | ||
foreach (var propertyInfo in entityType.ClrType.GetProperties()) | ||
{ | ||
if (propertyInfo.PropertyType == typeof(Money)) | ||
{ | ||
entityType.AddProperty(propertyInfo) | ||
.SetValueConverter(typeof(Money.MoneyConverter)); | ||
} | ||
} | ||
} | ||
#endregion | ||
} | ||
|
||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) | ||
=> optionsBuilder.UseSqlServer( | ||
@"Server=(localdb)\mssqllocaldb;Database=EFModeling.BulkConfiguration;Trusted_Connection=True") | ||
.LogTo(Console.WriteLine, minimumLevel: Microsoft.Extensions.Logging.LogLevel.Information) | ||
.EnableSensitiveDataLogging(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||
|
||
namespace EFModeling.BulkConfiguration | ||
{ | ||
#region Money | ||
public readonly struct Money | ||
{ | ||
public Money(decimal amount) | ||
=> Amount = amount; | ||
|
||
public decimal Amount { get; } | ||
|
||
public override string ToString() | ||
=> $"${Amount}"; | ||
|
||
public class MoneyConverter : ValueConverter<Money, decimal> | ||
{ | ||
public MoneyConverter() | ||
: base( | ||
v => v.Amount, | ||
v => new Money(v)) | ||
{ | ||
} | ||
} | ||
} | ||
#endregion | ||
} |
44 changes: 44 additions & 0 deletions
44
samples/core/Modeling/BulkConfiguration/PreConventionContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
namespace EFModeling.BulkConfiguration | ||
{ | ||
public class PreConventionContext : DbContext | ||
{ | ||
public DbSet<Product> Products { get; set; } | ||
|
||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) | ||
{ | ||
#region MoneyConversion | ||
configurationBuilder | ||
.Properties<Money>() | ||
.HaveConversion<Money.MoneyConverter>(); | ||
#endregion | ||
|
||
#region StringFacets | ||
configurationBuilder | ||
.Properties<string>() | ||
.AreUnicode(false) | ||
.HaveMaxLength(1024); | ||
#endregion | ||
|
||
#region IgnoreInterface | ||
configurationBuilder | ||
.IgnoreAny(typeof(IList<>)); | ||
#endregion | ||
|
||
#region DefaultTypeMapping | ||
configurationBuilder | ||
.DefaultTypeMapping<Money>() | ||
.HasConversion<Money.MoneyConverter>(); | ||
#endregion | ||
} | ||
|
||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) | ||
=> optionsBuilder.UseSqlServer( | ||
@"Server=(localdb)\mssqllocaldb;Database=EFModeling.BulkConfiguration;Trusted_Connection=True") | ||
.LogTo(Console.WriteLine, minimumLevel: Microsoft.Extensions.Logging.LogLevel.Information) | ||
.EnableSensitiveDataLogging(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace EFModeling.BulkConfiguration | ||
{ | ||
public class Product | ||
{ | ||
public int Id { get; set; } | ||
public Money Price { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
using System; | ||
using System.Linq; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
namespace EFModeling.BulkConfiguration | ||
{ | ||
internal class Program | ||
{ | ||
private static void Main() | ||
{ | ||
Console.WriteLine("Sample showing bulk configuration of value conversion for a simple value object"); | ||
Console.WriteLine(); | ||
|
||
using (var context = new MetadataAPIContext()) | ||
{ | ||
RoundtripValue(context); | ||
} | ||
|
||
using (var context = new PreConventionContext()) | ||
{ | ||
RoundtripValue(context); | ||
} | ||
|
||
Console.WriteLine(); | ||
Console.WriteLine("Sample finished."); | ||
} | ||
|
||
private static void RoundtripValue(DbContext context) | ||
{ | ||
Console.WriteLine("Using " + context.GetType().Name); | ||
Console.WriteLine("Deleting and re-creating database..."); | ||
Console.WriteLine(); | ||
context.Database.EnsureDeleted(); | ||
context.Database.EnsureCreated(); | ||
Console.WriteLine(); | ||
Console.WriteLine("Done. Database is clean and fresh."); | ||
|
||
var product = new Product { Price = new Money(3.99m) }; | ||
context.Add(product); | ||
|
||
Console.WriteLine("Save a new product with price: " + product.Price.Amount); | ||
Console.WriteLine(); | ||
|
||
context.SaveChanges(); | ||
|
||
Console.WriteLine(); | ||
Console.WriteLine("Read the entity back with price: " + context.Set<Product>().Single().Price.Amount); | ||
Console.WriteLine(); | ||
} | ||
} | ||
} |
Oops, something went wrong.