-
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.
- Loading branch information
1 parent
2209fee
commit 20394e6
Showing
11 changed files
with
1,268 additions
and
11 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
557 changes: 556 additions & 1 deletion
557
entity-framework/core/what-is-new/ef-core-7.0/whatsnew.md
Large diffs are not rendered by default.
Oops, something went wrong.
117 changes: 117 additions & 0 deletions
117
samples/core/Miscellaneous/NewInEFCore7/InjectLoggerSample.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,117 @@ | ||
using System.ComponentModel.DataAnnotations.Schema; | ||
using Microsoft.EntityFrameworkCore; | ||
using Microsoft.EntityFrameworkCore.Diagnostics; | ||
using Microsoft.EntityFrameworkCore.Infrastructure; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace NewInEfCore7; | ||
|
||
public static class InjectLoggerSample | ||
{ | ||
public static async Task Injecting_services_into_entities() | ||
{ | ||
PrintSampleName(); | ||
|
||
var loggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); }); | ||
|
||
var serviceProvider = new ServiceCollection() | ||
.AddDbContext<CustomerContext>( | ||
b => b.UseLoggerFactory(loggerFactory) | ||
.UseSqlite("Data Source = customers.db")) | ||
.BuildServiceProvider(); | ||
|
||
using (var scope = serviceProvider.CreateScope()) | ||
{ | ||
var context = scope.ServiceProvider.GetRequiredService<CustomerContext>(); | ||
|
||
await context.Database.EnsureDeletedAsync(); | ||
await context.Database.EnsureCreatedAsync(); | ||
|
||
await context.AddRangeAsync( | ||
new Customer { Name = "Alice", PhoneNumber = "+1 515 555 0123" }, | ||
new Customer { Name = "Mac", PhoneNumber = "+1 515 555 0124" }); | ||
|
||
await context.SaveChangesAsync(); | ||
} | ||
|
||
using (var scope = serviceProvider.CreateScope()) | ||
{ | ||
var context = scope.ServiceProvider.GetRequiredService<CustomerContext>(); | ||
|
||
var customer = await context.Customers.SingleAsync(e => e.Name == "Alice"); | ||
customer.PhoneNumber = "+1 515 555 0125"; | ||
} | ||
} | ||
|
||
private static void PrintSampleName([CallerMemberName] string? methodName = null) | ||
{ | ||
Console.WriteLine($">>>> Sample: {methodName}"); | ||
Console.WriteLine(); | ||
} | ||
|
||
public class CustomerContext : DbContext | ||
{ | ||
public CustomerContext(DbContextOptions<CustomerContext> options) | ||
: base(options) | ||
{ | ||
} | ||
|
||
public DbSet<Customer> Customers | ||
=> Set<Customer>(); | ||
|
||
#region OnConfiguring | ||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) | ||
=> optionsBuilder.AddInterceptors(new LoggerInjectionInterceptor()); | ||
#endregion | ||
} | ||
|
||
#region LoggerInjectionInterceptor | ||
public class LoggerInjectionInterceptor : IMaterializationInterceptor | ||
{ | ||
private ILogger? _logger; | ||
|
||
public object InitializedInstance(MaterializationInterceptionData materializationData, object instance) | ||
{ | ||
if (instance is IHasLogger hasLogger) | ||
{ | ||
_logger ??= materializationData.Context.GetService<ILoggerFactory>().CreateLogger("CustomersLogger"); | ||
hasLogger.Logger = _logger; | ||
} | ||
|
||
return instance; | ||
} | ||
} | ||
#endregion | ||
|
||
#region IHasLogger | ||
public interface IHasLogger | ||
{ | ||
ILogger? Logger { get; set; } | ||
} | ||
#endregion | ||
|
||
#region CustomerIHasLogger | ||
public class Customer : IHasLogger | ||
{ | ||
private string? _phoneNumber; | ||
|
||
public int Id { get; set; } | ||
public string Name { get; set; } = null!; | ||
|
||
public string? PhoneNumber | ||
{ | ||
get => _phoneNumber; | ||
set | ||
{ | ||
Logger?.LogInformation(1, $"Updating phone number for '{Name}' from '{_phoneNumber}' to '{value}'."); | ||
|
||
_phoneNumber = value; | ||
} | ||
} | ||
|
||
[NotMapped] | ||
public ILogger? Logger { get; set; } | ||
} | ||
#endregion | ||
} |
120 changes: 120 additions & 0 deletions
120
samples/core/Miscellaneous/NewInEFCore7/LazyConnectionStringSample.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,120 @@ | ||
using Microsoft.EntityFrameworkCore.Diagnostics; | ||
using Microsoft.EntityFrameworkCore.Infrastructure; | ||
|
||
namespace NewInEfCore7; | ||
|
||
public static class LazyConnectionStringSample | ||
{ | ||
public static async Task Lazy_initialization_of_a_connection_string() | ||
{ | ||
PrintSampleName(); | ||
|
||
var services = new ServiceCollection(); | ||
|
||
services.AddScoped<IClientConnectionStringFactory, TestClientConnectionStringFactory>(); | ||
|
||
services.AddDbContext<CustomerContext>( | ||
b => b.UseSqlServer() | ||
.LogTo(Console.WriteLine, LogLevel.Information) | ||
.EnableSensitiveDataLogging()); | ||
|
||
var serviceProvider = services.BuildServiceProvider(); | ||
|
||
using (var scope = serviceProvider.CreateScope()) | ||
{ | ||
var context = scope.ServiceProvider.GetRequiredService<CustomerContext>(); | ||
|
||
await context.Database.EnsureDeletedAsync(); | ||
await context.Database.EnsureCreatedAsync(); | ||
|
||
await context.AddRangeAsync( | ||
new Customer { Name = "Alice" }, | ||
new Customer { Name = "Mac" }); | ||
|
||
await context.SaveChangesAsync(); | ||
|
||
var customer = await context.Customers.SingleAsync(e => e.Name == "Alice"); | ||
Console.WriteLine(); | ||
Console.WriteLine($"Loaded {customer.Name}"); | ||
Console.WriteLine(); | ||
} | ||
} | ||
|
||
private static void PrintSampleName([CallerMemberName] string? methodName = null) | ||
{ | ||
Console.WriteLine($">>>> Sample: {methodName}"); | ||
Console.WriteLine(); | ||
} | ||
|
||
public class CustomerContext : DbContext | ||
{ | ||
private readonly IClientConnectionStringFactory _connectionStringFactory; | ||
|
||
public CustomerContext( | ||
DbContextOptions<CustomerContext> options, | ||
IClientConnectionStringFactory connectionStringFactory) | ||
: base(options) | ||
{ | ||
_connectionStringFactory = connectionStringFactory; | ||
} | ||
|
||
public DbSet<Customer> Customers | ||
=> Set<Customer>(); | ||
|
||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) | ||
=> optionsBuilder.AddInterceptors( | ||
new ConnectionStringInitializationInterceptor(_connectionStringFactory)); | ||
} | ||
|
||
public interface IClientConnectionStringFactory | ||
{ | ||
Task<string> GetConnectionStringAsync(CancellationToken cancellationToken); | ||
} | ||
|
||
public class TestClientConnectionStringFactory : IClientConnectionStringFactory | ||
{ | ||
public Task<string> GetConnectionStringAsync(CancellationToken cancellationToken) | ||
{ | ||
Console.WriteLine(); | ||
Console.WriteLine(">>> Getting connection string..."); | ||
Console.WriteLine(); | ||
return Task.FromResult(@"Server=(localdb)\mssqllocaldb;Database=LazyConnectionStringSample"); | ||
} | ||
} | ||
|
||
#region ConnectionStringInitializationInterceptor | ||
public class ConnectionStringInitializationInterceptor : DbConnectionInterceptor | ||
{ | ||
private readonly IClientConnectionStringFactory _connectionStringFactory; | ||
|
||
public ConnectionStringInitializationInterceptor(IClientConnectionStringFactory connectionStringFactory) | ||
{ | ||
_connectionStringFactory = connectionStringFactory; | ||
} | ||
|
||
public override InterceptionResult ConnectionOpening( | ||
DbConnection connection, | ||
ConnectionEventData eventData, | ||
InterceptionResult result) | ||
=> throw new NotSupportedException("Synchronous connections not supported."); | ||
|
||
public override async ValueTask<InterceptionResult> ConnectionOpeningAsync( | ||
DbConnection connection, ConnectionEventData eventData, InterceptionResult result, | ||
CancellationToken cancellationToken = new()) | ||
{ | ||
if (string.IsNullOrEmpty(connection.ConnectionString)) | ||
{ | ||
connection.ConnectionString = (await _connectionStringFactory.GetConnectionStringAsync(cancellationToken)); | ||
} | ||
|
||
return result; | ||
} | ||
} | ||
#endregion | ||
|
||
public class Customer | ||
{ | ||
public int Id { get; set; } | ||
public string Name { get; set; } = null!; | ||
} | ||
} |
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
92 changes: 92 additions & 0 deletions
92
samples/core/Miscellaneous/NewInEFCore7/OptimisticConcurrencyInterceptionSample.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,92 @@ | ||
using Microsoft.EntityFrameworkCore; | ||
using Microsoft.EntityFrameworkCore.Diagnostics; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace NewInEfCore7; | ||
|
||
public static class OptimisticConcurrencyInterceptionSample | ||
{ | ||
public static async Task Optimistic_concurrency_interception() | ||
{ | ||
PrintSampleName(); | ||
|
||
await using (var context = new CustomerContext()) | ||
{ | ||
await context.Database.EnsureDeletedAsync(); | ||
await context.Database.EnsureCreatedAsync(); | ||
|
||
await context.AddRangeAsync( | ||
new Customer { Name = "Bill" }, | ||
new Customer { Name = "Bob" }); | ||
|
||
await context.SaveChangesAsync(); | ||
} | ||
|
||
await using (var context1 = new CustomerContext()) | ||
{ | ||
var customer1 = await context1.Customers.SingleAsync(e => e.Name == "Bill"); | ||
|
||
await using (var context2 = new CustomerContext()) | ||
{ | ||
var customer2 = await context1.Customers.SingleAsync(e => e.Name == "Bill"); | ||
context2.Entry(customer2).State = EntityState.Deleted; | ||
await context2.SaveChangesAsync(); | ||
} | ||
|
||
context1.Entry(customer1).State = EntityState.Deleted; | ||
await context1.SaveChangesAsync(); | ||
} | ||
} | ||
|
||
private static void PrintSampleName([CallerMemberName] string? methodName = null) | ||
{ | ||
Console.WriteLine($">>>> Sample: {methodName}"); | ||
Console.WriteLine(); | ||
} | ||
|
||
public class CustomerContext : DbContext | ||
{ | ||
private static readonly SuppressDeleteConcurrencyInterceptor _concurrencyInterceptor = new(); | ||
|
||
public DbSet<Customer> Customers | ||
=> Set<Customer>(); | ||
|
||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) | ||
=> optionsBuilder | ||
.AddInterceptors(_concurrencyInterceptor) | ||
.UseSqlite("Data Source = customers.db") | ||
.LogTo(Console.WriteLine, LogLevel.Information); | ||
} | ||
|
||
#region SuppressDeleteConcurrencyInterceptor | ||
public class SuppressDeleteConcurrencyInterceptor : ISaveChangesInterceptor | ||
{ | ||
public InterceptionResult ThrowingConcurrencyException( | ||
ConcurrencyExceptionEventData eventData, | ||
InterceptionResult result) | ||
{ | ||
if (eventData.Entries.All(e => e.State == EntityState.Deleted)) | ||
{ | ||
Console.WriteLine("Suppressing Concurrency violation for command:"); | ||
Console.WriteLine(((RelationalConcurrencyExceptionEventData)eventData).Command.CommandText); | ||
|
||
return InterceptionResult.Suppress(); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
public ValueTask<InterceptionResult> ThrowingConcurrencyExceptionAsync( | ||
ConcurrencyExceptionEventData eventData, | ||
InterceptionResult result, | ||
CancellationToken cancellationToken = default) | ||
=> new(ThrowingConcurrencyException(eventData, result)); | ||
} | ||
#endregion | ||
|
||
public class Customer | ||
{ | ||
public int Id { get; set; } | ||
public string Name { get; set; } = null!; | ||
} | ||
} |
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
Oops, something went wrong.