Skip to content

feat(EmitHelper): use cache to reduce dynamic type creation.#7797

Merged
ArgoZhang merged 9 commits intomainfrom
feat-dynamic
Mar 24, 2026
Merged

feat(EmitHelper): use cache to reduce dynamic type creation.#7797
ArgoZhang merged 9 commits intomainfrom
feat-dynamic

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Mar 24, 2026

Link issues

fixes #7788

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Introduce caching for dynamically emitted DataTable types and streamline dynamic table context usage.

New Features:

  • Cache dynamically emitted DataTable dynamic object types in CacheManager keyed by column schema to avoid redundant type generation.

Enhancements:

  • Refine DataTableDynamicContext internal row-to-item cache naming and usage for clarity.
  • Ensure column attribute callbacks still execute when using cached dynamic types to maintain column configuration.
  • Update dynamic table sample to set localized display text for all columns via FooLocalizer.

Copilot AI review requested due to automatic review settings March 24, 2026 05:21
@bb-auto bb-auto bot added the enhancement New feature or request label Mar 24, 2026
@bb-auto bb-auto bot added this to the v10.4.0 milestone Mar 24, 2026
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Mar 24, 2026

Reviewer's Guide

Introduces a cache-based mechanism for dynamic type creation in DataTableDynamicContext using CacheManager, refactors the data row cache field naming, and slightly adjusts a sample to set column display text consistently via the dynamic context callback.

Sequence diagram for cache-based dynamic type creation in DataTableDynamicContext

sequenceDiagram
    participant DataTableDynamicContext
    participant CacheManager
    participant CacheInstance
    participant EmitHelper

    DataTableDynamicContext->>DataTableDynamicContext: Build columnNames and cacheKey
    DataTableDynamicContext->>CacheManager: GetDynamicObjectTypeByName(key, cols, OnColumnCreating, out cached)
    CacheManager->>CacheInstance: GetOrCreate(key, factory)
    alt type not in cache
        CacheInstance->>CacheManager: invoke factory
        CacheManager->>EmitHelper: CreateTypeByName(dynamicTypeName, cols, DataTableDynamicObject, creatingCallback)
        EmitHelper-->>CacheManager: Type
        CacheManager-->>CacheInstance: created Type
        CacheInstance-->>CacheManager: Type
        CacheManager-->>DataTableDynamicContext: Type (cached = false)
        DataTableDynamicContext->>DataTableDynamicContext: Use cols with OnColumnCreating
    else type in cache
        CacheInstance-->>CacheManager: cached Type
        CacheManager-->>DataTableDynamicContext: Type (cached = true)
        DataTableDynamicContext->>DataTableDynamicContext: if AddAttributesCallback != null
        DataTableDynamicContext->>DataTableDynamicContext: foreach col in cols
        DataTableDynamicContext->>DataTableDynamicContext: AddAttributesCallback(this, col)
    end
    DataTableDynamicContext->>DataTableDynamicContext: return dynamicType
Loading

Updated class diagram for DataTableDynamicContext and CacheManager dynamic type caching

classDiagram
    class DataTableDynamicContext {
        -ConcurrentDictionary~Guid, (IDynamicObject, DataRow)~ _dataCache
        -DataTable DataTable
        -Action~DataTableDynamicContext, ITableColumn~ AddAttributesCallback
        +DataTableDynamicContext(DataTable table, Action~DataTableDynamicContext, ITableColumn~ callback)
        +IEnumerable~IDynamicObject~ GetItems()
        +Task AddAsync(IEnumerable~IDynamicObject~ selectedItems)
        +Task~bool~ DeleteAsync(IEnumerable~IDynamicObject~ items)
        -Task OnCellValueChanged(IDynamicObject item, ITableColumn column, object val)
        -List~IDynamicObject~ BuildItems()
        -Type CreateType()
    }

    class CacheManager {
        <<static>>
        +Type GetDynamicObjectTypeByName(string key, IEnumerable~ITableColumn~ cols, Func~ITableColumn, IEnumerable~CustomAttributeBuilder~~ creatingCallback, out bool cached)
        -T GetOrCreate~T~(string key, Func~string, T~ factory)
    }

    class DataTableDynamicObject {
        Guid DynamicObjectPrimaryKey
        DataRow Row
    }

    class ITableColumn {
        string Text
        string GetFieldName()
    }

    class DataRow
    class DataTable
    class CustomAttributeBuilder

    DataTableDynamicContext ..> CacheManager : uses
    DataTableDynamicContext ..> DataTableDynamicObject : creates
    DataTableDynamicContext ..> ITableColumn : configures
    DataTableDynamicContext ..> DataRow : maps
    CacheManager ..> CustomAttributeBuilder : uses
Loading

File-Level Changes

Change Details Files
Use a cache to reuse dynamically emitted types for DataTableDynamicContext instead of always creating new ones.
  • Replaces direct EmitHelper.CreateTypeByName calls with a cache-aware CacheManager.GetDynamicObjectTypeByName invocation keyed by column names and types.
  • Builds a stable cache key from DataTable column names and their data types to identify dynamic types.
  • Ensures column attribute callbacks are still invoked when a cached dynamic type is reused by iterating columns and calling AddAttributesCallback when the type comes from cache.
src/BootstrapBlazor/Dynamic/DataTableDynamicContext.cs
src/BootstrapBlazor/Services/CacheManager.cs
Refactor the internal data row cache field and ensure it is used consistently across CRUD and change tracking operations.
  • Renames the concurrent cache dictionary from an auto-property to a private field named _dataCache and updates all references.
  • Clears and updates the cache when rebuilding items, adding rows, deleting rows, and when cell values change to keep DataRow and IDynamicObject mappings in sync.
src/BootstrapBlazor/Dynamic/DataTableDynamicContext.cs
Extend CacheManager to support dynamic type caching for dynamic table contexts.
  • Adds a GetDynamicObjectTypeByName API that uses the existing Instance cache to store and retrieve dynamic types by key.
  • Tracks whether the type was newly created or fetched from cache and exposes this via an out bool cached parameter.
  • Uses EmitHelper.CreateTypeByName internally with a composite name that includes DataTableDynamicContext and the cache key.
src/BootstrapBlazor/Services/CacheManager.cs
Adjust the TablesDynamic sample to set column display text for all properties via the localization callback rather than only for Name.
  • Moves the assignment of col.Text from the Foo.Name-specific branch to the common callback body so that all columns use FooLocalizer[propertyName] as their display text.
  • Removes the redundant per-property col.Text assignment in the Name branch.
src/BootstrapBlazor.Server/Components/Samples/Table/TablesDynamic.razor.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#7788 Introduce or improve caching around EmitHelper-based dynamic type creation in DataTableDynamicContext (or related components) to reduce repeated dynamic object type creation.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In GetDynamicObjectTypeByName, the created flag is mutated inside the cache factory delegate and then read afterward, which is racy if the cache implementation can call the factory more than once or across threads; consider deriving the cached value directly from the cache API (e.g., a TryGet/added flag) instead of relying on a captured local.
  • The cache key for dynamic types currently concatenates column name and full type name with |; if column ordering is not guaranteed or additional properties are added later, you may unintentionally create multiple dynamic types for logically equivalent schemas—consider normalizing/sorting or using a more structured key to avoid redundant type generation.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `GetDynamicObjectTypeByName`, the `created` flag is mutated inside the cache factory delegate and then read afterward, which is racy if the cache implementation can call the factory more than once or across threads; consider deriving the `cached` value directly from the cache API (e.g., a TryGet/added flag) instead of relying on a captured local.
- The cache key for dynamic types currently concatenates column name and full type name with `|`; if column ordering is not guaranteed or additional properties are added later, you may unintentionally create multiple dynamic types for logically equivalent schemas—consider normalizing/sorting or using a more structured key to avoid redundant type generation.

## Individual Comments

### Comment 1
<location path="src/BootstrapBlazor/Dynamic/DataTableDynamicContext.cs" line_range="43" />
<code_context>
     /// <para lang="en">Responsible for associating DataRow with Items to facilitate lookup and improve efficiency</para>
     /// </summary>
-    private ConcurrentDictionary<Guid, (IDynamicObject DynamicObject, DataRow Row)> Caches { get; } = new();
+    private ConcurrentDictionary<Guid, (IDynamicObject DynamicObject, DataRow Row)> _dataCache = new();

     /// <summary>
</code_context>
<issue_to_address>
**suggestion:** Consider making the `_dataCache` field `readonly` to avoid accidental reassignment.

This was previously an auto-property with an initializer, so the dictionary reference itself wasn’t reassignable. As a mutable field, `_dataCache` can now be replaced, which doesn’t seem necessary and increases the risk of accidental reassignment. Making it `readonly` keeps the reference fixed while still allowing normal dictionary operations.

```suggestion
    private readonly ConcurrentDictionary<Guid, (IDynamicObject DynamicObject, DataRow Row)> _dataCache = new();
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/BootstrapBlazor/Dynamic/DataTableDynamicContext.cs Outdated
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 24, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (5270336) to head (a326e6a).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #7797   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          764       764           
  Lines        34111     34122   +11     
  Branches      4697      4697           
=========================================
+ Hits         34111     34122   +11     
Flag Coverage Δ
BB 100.00% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces caching for EmitHelper-generated dynamic types used by DataTableDynamicContext, aiming to reduce repeated Reflection.Emit type creation and improve runtime efficiency when constructing dynamic table contexts.

Changes:

  • Added a new CacheManager helper to cache and reuse emitted dynamic Type instances by a computed key.
  • Updated DataTableDynamicContext to generate a schema-based cache key and reuse cached dynamic types.
  • Adjusted the server sample to demonstrate setting column display text via ITableColumn.Text.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
src/BootstrapBlazor/Services/CacheManager.cs Adds an internal API to cache/retrieve emitted dynamic Types and report cache hits.
src/BootstrapBlazor/Dynamic/DataTableDynamicContext.cs Switches dynamic type creation to use the new cache, and renames internal row/object cache field.
src/BootstrapBlazor.Server/Components/Samples/Table/TablesDynamic.razor.cs Updates sample code to set col.Text earlier as a display-name example.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/BootstrapBlazor/Dynamic/DataTableDynamicContext.cs
Comment on lines +93 to +97
// Emit 生成动态类 (使用缓存)
var columnNames = string.Join('|', table.Columns.Cast<DataColumn>().Select(static c => $"{c.ColumnName}:{c.DataType.FullName}"));
var cacheKey = $"BootstrapBlazor-{nameof(DataTableDynamicContext)}-{columnNames}";
var dynamicType = CacheManager.GetDynamicObjectTypeByName(cacheKey, cols, OnColumnCreating, out var cached);

Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

The dynamic type cache key only includes the DataTable schema (column names/types), but the emitted type also depends on AddAttributesCallback/OnColumnCreating (e.g., required/error messages, display attributes, ignore settings). If two contexts share the same schema but provide different callbacks/localizers, the second context will reuse the first type and its baked-in attributes, causing incorrect column metadata/validation (this is already reproducible in TablesDynamic.razor.cs, where two contexts use different localizers with the same schema). Consider incorporating callback identity and/or CultureInfo.CurrentUICulture into the cache key, or disabling type caching when attribute generation is callback-dependent.

Copilot uses AI. Check for mistakes.
Comment thread src/BootstrapBlazor/Dynamic/DataTableDynamicContext.cs
Comment thread src/BootstrapBlazor/Services/CacheManager.cs
Comment on lines +773 to +776
{
created = true;
return EmitHelper.CreateTypeByName($"BootstrapBlazor_{nameof(DataTableDynamicContext)}_{key}", cols, typeof(DataTableDynamicObject), creatingCallback);
});
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

The emitted type name is built from key ($"BootstrapBlazor_{nameof(DataTableDynamicContext)}_{key}"), but the cache key can become very long and may contain characters from column names (e.g., |, :, spaces) and generic type full names. This can bloat metadata and risks DefineType failures for large schemas. Consider generating a short/sanitized type name (e.g., a stable hash of the schema + any other discriminators) while keeping the full string only as the cache key.

Copilot uses AI. Check for mistakes.
@ArgoZhang ArgoZhang merged commit 6ac961e into main Mar 24, 2026
4 checks passed
@ArgoZhang ArgoZhang deleted the feat-dynamic branch March 24, 2026 05:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(EmitHelper): 优化内部缓存减少动态对象创建

2 participants