Skip to content
Merged
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,50 +1,52 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;

using PhenX.EntityFrameworkCore.BulkInsert.Abstractions;

namespace PhenX.EntityFrameworkCore.BulkInsert.Metadata;

internal sealed class MetadataProvider
{
private Dictionary<Type, Dictionary<Type, TableMetadata>> _tablesPerContext = new();

public TableMetadata GetTableInfo<T>(DbContext context)
{
var tables = GetTables(context);

if (!tables.TryGetValue(typeof(T), out var table))
{
throw new InvalidOperationException($"Cannot find metadata for type '{typeof(T)}'.");
}

return table;
}

private Dictionary<Type, TableMetadata> GetTables(DbContext context)
{
lock (_tablesPerContext)
{
var type = context.GetType();
if (_tablesPerContext.TryGetValue(context.GetType(), out var tables))
{
return tables;
}

var provider = context.GetService<IBulkInsertProvider>();

tables = context.Model.GetEntityTypes()
// Filter out entities without an associated table
// See also https://learn.microsoft.com/en-us/ef/core/modeling/keyless-entity-types
.Where(x => x.GetTableName() is not null)
.GroupBy(x => x.ClrType)
.ToDictionary(
x => x.Key,
x => new TableMetadata(x.First(), provider.SqlDialect));

_tablesPerContext[type] = tables;

return tables;
}
}
}
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;

using PhenX.EntityFrameworkCore.BulkInsert.Abstractions;

namespace PhenX.EntityFrameworkCore.BulkInsert.Metadata;

internal sealed class MetadataProvider
{
private readonly Dictionary<Type, Dictionary<Type, TableMetadata>> _tablesPerContext = new();

public TableMetadata GetTableInfo<T>(DbContext context)
{
lock (_tablesPerContext)
{
var type = context.GetType();

if (!_tablesPerContext.TryGetValue(type, out var tables))
{
tables = new Dictionary<Type, TableMetadata>();
_tablesPerContext[type] = tables;
}

var modelType = typeof(T);

if (tables.TryGetValue(modelType, out var table))
{
return table;
}

var entityType = context.Model.FindEntityType(modelType);
if (entityType == null)
{
throw new InvalidOperationException($"The type '{modelType.FullName}' is not part of the model for the current context.");
}

// Filter out entities without an associated table
// See also https://learn.microsoft.com/en-us/ef/core/modeling/keyless-entity-types
if (entityType.GetTableName() is null)
{
throw new InvalidOperationException($"The type '{modelType.FullName}' is not mapped to a table in the database or is keyless.");
}

var provider = context.GetService<IBulkInsertProvider>();
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

The IBulkInsertProvider service is retrieved inside the lock on every cache miss. Consider moving this retrieval outside the lock (after checking for cache miss but before the lock, or after the model type check) to reduce lock contention, as GetService<T>() is typically thread-safe.

Copilot uses AI. Check for mistakes.

var tableMetadata = new TableMetadata(entityType, provider.SqlDialect);
tables[modelType] = tableMetadata;

return tableMetadata;
}
}
}
Loading