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

Updates to What's New #4672

Merged
merged 1 commit into from
Mar 9, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions entity-framework/core/what-is-new/ef-core-8.0/whatsnew.md
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,8 @@ WHERE "Id" = @p1
RETURNING 1;
```

<a name="hierarchyid"></a>

## HierarchyId in .NET and EF Core

Azure SQL and SQL Server have a special data type called [`hierarchyid`](/sql/t-sql/data-types/hierarchyid-data-type-method-reference) that is used to store [hierarchical data](/sql/relational-databases/hierarchical-data-sql-server). In this case, "hierarchical data" essentially means data that forms a tree structure, where each item can have a parent and/or children. Examples of such data are:
Expand Down
266 changes: 244 additions & 22 deletions entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion samples/core/Miscellaneous/NewInEFCore9/BlogsContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> (UseSqlite
? optionsBuilder.UseSqlite(@$"DataSource={GetType().Name}.db")
: optionsBuilder.UseSqlServer(
@$"Server=(localdb)\mssqllocaldb;Database={GetType().Name}",
@$"Server=(localdb)\mssqllocaldb;Database={GetType().Name};ConnectRetryCount=0",
sqlServerOptionsBuilder => sqlServerOptionsBuilder.UseNetTopologySuite()))
.EnableSensitiveDataLogging()
.LogTo(
Expand Down
120 changes: 120 additions & 0 deletions samples/core/Miscellaneous/NewInEFCore9/CustomConventionsSample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;

namespace NewInEfCore9;

public static class CustomConventionsSample
{
public static async Task Conventions_enhancements_in_EF9()
{
PrintSampleName();

await using var context = new TestContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
await context.Seed();

context.LoggingEnabled = true;
context.ChangeTracker.Clear();

Console.WriteLine(context.Model.ToDebugString());
}

private static void PrintSampleName([CallerMemberName] string? methodName = null)
{
Console.WriteLine($">>>> Sample: {methodName}");
Console.WriteLine();
}

public class TestContext : DbContext
{
public bool LoggingEnabled { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(@$"Server=(localdb)\mssqllocaldb;Database={GetType().Name};ConnectRetryCount=0")
.EnableSensitiveDataLogging()
.LogTo(
s =>
{
if (LoggingEnabled)
{
Console.WriteLine(s);
}
}, LogLevel.Information);

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
}

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Conventions.Replace<PropertyDiscoveryConvention>(
serviceProvider => new AttributeBasedPropertyDiscoveryConvention(
serviceProvider.GetRequiredService<ProviderConventionSetBuilderDependencies>()));
}

public async Task Seed()
{
await SaveChangesAsync();
}
}

#region AttributeBasedPropertyDiscoveryConvention
public class AttributeBasedPropertyDiscoveryConvention(ProviderConventionSetBuilderDependencies dependencies)
: PropertyDiscoveryConvention(dependencies)
{
protected override bool IsCandidatePrimitiveProperty(
MemberInfo memberInfo, IConventionTypeBase structuralType, out CoreTypeMapping? mapping)
{
if (base.IsCandidatePrimitiveProperty(memberInfo, structuralType, out mapping))
{
if (Attribute.IsDefined(memberInfo, typeof(PersistAttribute), inherit: true))
{
return true;
}

structuralType.Builder.Ignore(memberInfo.Name);
}

mapping = null;
return false;
}
}
#endregion

#region Country
public class Country
{
[Persist]
public int Code { get; set; }

[Persist]
public required string Name { get; set; }

public bool IsDirty { get; set; } // Will not be mapped by default.

private class FooConfiguration : IEntityTypeConfiguration<Country>
{
private FooConfiguration()
{
}

public void Configure(EntityTypeBuilder<Country> builder)
{
builder.HasKey(e => e.Code);
}
}
}
#endregion
}

#region PersistAttribute
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class PersistAttribute : Attribute
{
}
#endregion
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> (UseSqlite
? optionsBuilder.UseSqlite(@$"DataSource={GetType().Name}.db")
: optionsBuilder.UseSqlServer(
@$"Server=(localdb)\mssqllocaldb;Database={GetType().Name}",
@$"Server=(localdb)\mssqllocaldb;Database={GetType().Name};ConnectRetryCount=0",
sqlServerOptionsBuilder => sqlServerOptionsBuilder.UseNetTopologySuite()))
.EnableSensitiveDataLogging()
.LogTo(Console.WriteLine, LogLevel.Information);
Expand Down
121 changes: 121 additions & 0 deletions samples/core/Miscellaneous/NewInEFCore9/HierarchyIdSample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
public static class HierarchyIdSample
{
public static async Task SQL_Server_HierarchyId()
{
PrintSampleName();

await using var context = new FamilyTreeContext();
await context.Database.EnsureDeletedAsync();

context.LoggingEnabled = true;

await context.Database.EnsureCreatedAsync();
await context.Seed();

context.ChangeTracker.Clear();

#region HierarchyIdQuery
var daisy = await context.Halflings.SingleAsync(e => e.Name == "Daisy");
#endregion

#region HierarchyIdParse1
var child1 = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 1), "Toast");
var child2 = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 2), "Wills");
#endregion

#region HierarchyIdParse2
var child1b = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 1, 5), "Toast");
#endregion

context.AddRange(child1, child2, child1b);

await context.SaveChangesAsync();
}

private static void PrintSampleName([CallerMemberName] string? methodName = null)
{
Console.WriteLine($">>>> Sample: {methodName}");
Console.WriteLine();
}

#region Halfling
public class Halfling
{
public Halfling(HierarchyId pathFromPatriarch, string name, int? yearOfBirth = null)
{
PathFromPatriarch = pathFromPatriarch;
Name = name;
YearOfBirth = yearOfBirth;
}

public int Id { get; private set; }
public HierarchyId PathFromPatriarch { get; set; }
public string Name { get; set; }
public int? YearOfBirth { get; set; }
}
#endregion

public class FamilyTreeContext : DbContext
{
public bool LoggingEnabled { get; set; }

public DbSet<Halfling> Halflings => Set<Halfling>();

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(
@$"Server=(localdb)\mssqllocaldb;Database={GetType().Name};ConnectRetryCount=0",
sqlServerOptionsBuilder => sqlServerOptionsBuilder.UseHierarchyId())
.EnableSensitiveDataLogging()
.LogTo(
s =>
{
if (LoggingEnabled)
{
Console.WriteLine(s);
}
}, LogLevel.Information);

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}

public async Task Seed()
{
#region AddRangeAsync
await AddRangeAsync(
new Halfling(HierarchyId.Parse("/"), "Balbo", 1167),
new Halfling(HierarchyId.Parse("/1/"), "Mungo", 1207),
new Halfling(HierarchyId.Parse("/2/"), "Pansy", 1212),
new Halfling(HierarchyId.Parse("/3/"), "Ponto", 1216),
new Halfling(HierarchyId.Parse("/4/"), "Largo", 1220),
new Halfling(HierarchyId.Parse("/5/"), "Lily", 1222),
new Halfling(HierarchyId.Parse("/1/1/"), "Bungo", 1246),
new Halfling(HierarchyId.Parse("/1/2/"), "Belba", 1256),
new Halfling(HierarchyId.Parse("/1/3/"), "Longo", 1260),
new Halfling(HierarchyId.Parse("/1/4/"), "Linda", 1262),
new Halfling(HierarchyId.Parse("/1/5/"), "Bingo", 1264),
new Halfling(HierarchyId.Parse("/3/1/"), "Rosa", 1256),
new Halfling(HierarchyId.Parse("/3/2/"), "Polo"),
new Halfling(HierarchyId.Parse("/4/1/"), "Fosco", 1264),
new Halfling(HierarchyId.Parse("/1/1/1/"), "Bilbo", 1290),
new Halfling(HierarchyId.Parse("/1/3/1/"), "Otho", 1310),
new Halfling(HierarchyId.Parse("/1/5/1/"), "Falco", 1303),
new Halfling(HierarchyId.Parse("/3/2/1/"), "Posco", 1302),
new Halfling(HierarchyId.Parse("/3/2/2/"), "Prisca", 1306),
new Halfling(HierarchyId.Parse("/4/1/1/"), "Dora", 1302),
new Halfling(HierarchyId.Parse("/4/1/2/"), "Drogo", 1308),
new Halfling(HierarchyId.Parse("/4/1/3/"), "Dudo", 1311),
new Halfling(HierarchyId.Parse("/1/3/1/1/"), "Lotho", 1310),
new Halfling(HierarchyId.Parse("/1/5/1/1/"), "Poppy", 1344),
new Halfling(HierarchyId.Parse("/3/2/1/1/"), "Ponto", 1346),
new Halfling(HierarchyId.Parse("/3/2/1/2/"), "Porto", 1348),
new Halfling(HierarchyId.Parse("/3/2/1/3/"), "Peony", 1350),
new Halfling(HierarchyId.Parse("/4/1/2/1/"), "Frodo", 1368),
new Halfling(HierarchyId.Parse("/4/1/3/1/"), "Daisy", 1350),
new Halfling(HierarchyId.Parse("/3/2/1/1/1/"), "Angelica", 1381));

await SaveChangesAsync();
#endregion
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> (UseSqlite
? optionsBuilder.UseSqlite(@$"DataSource={GetType().Name}.db")
// Note that SQL Server 2022 is required.
: optionsBuilder.UseSqlServer(@$"Server=(localdb)\mssqllocaldb;Database={GetType().Name}"))
: optionsBuilder.UseSqlServer(@$"Server=(localdb)\mssqllocaldb;Database={GetType().Name};ConnectRetryCount=0"))
.EnableSensitiveDataLogging()
.LogTo(
s =>
Expand Down