Skip to content

Feature/net10#28

Merged
jfwangncs merged 25 commits intomasterfrom
feature/net10
Feb 27, 2026
Merged

Feature/net10#28
jfwangncs merged 25 commits intomasterfrom
feature/net10

Conversation

@jfwangncs
Copy link
Owner

No description provided.

@jfwangncs jfwangncs self-assigned this Feb 26, 2026
Copilot AI review requested due to automatic review settings February 26, 2026 02:01
Copy link

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 adds support for .NET 10.0 across the solution while improving cross-platform compatibility by extending support to .NET Standard 2.0 and .NET Framework 4.8.1 for several projects. The changes include updating target frameworks, adding conditional compilation for different framework versions, refactoring code for better performance, and introducing a new benchmark project.

Changes:

  • Added .NET 10.0 target framework to multiple projects and updated CI/CD workflows
  • Extended Redis, Office, Request, RabbitMQ, and WeChat projects to support .NET Standard 2.0 and additional framework versions
  • Refactored code to use conditional compilation directives for framework-specific implementations

Reviewed changes

Copilot reviewed 78 out of 78 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/JfYu.sln Added new benchmark project and additional platform configurations (x64, x86)
src/JfYu.WeChat/JfYu.WeChat.csproj Added net9.0 and net10.0 target frameworks
src/JfYu.WeChat/Constant/MiniProgram.cs Changed class to static
src/JfYu.Request/JfYu.Request.csproj Added net9.0 and net10.0 target frameworks
src/JfYu.Request/JfYuHttpClientOptions.cs Changed default cookie container behavior and added thread-safety warnings
src/JfYu.Request/JfYuHttpClient.cs Reordered constructor parameters
src/JfYu.Request/README.md Updated documentation about cookie container thread safety
src/JfYu.Redis/JfYu.Redis.csproj Added netstandard2.0, net9.0, and net10.0 target frameworks
src/JfYu.Redis/Implementation/*.cs Added conditional compilation for .NET Standard 2.0 compatibility
src/JfYu.Redis/Extensions/ArgumentNullExceptionExtension.cs Added polyfill methods for .NET Standard 2.0
src/JfYu.Office/JfYu.Office.csproj Added netstandard2.0, net9.0, and net10.0 target frameworks
src/JfYu.Office/Excel/Write/JfYuWriterBase.cs Improved numeric precision formatting for Excel cells
src/JfYu.Office/ArgumentNullExceptionExtension.cs Added polyfill class for .NET Standard 2.0
src/JfYu.Data/JfYu.Data.csproj Added net9.0 and net10.0 target frameworks
src/JfYu.Data/Service/Service.cs Optimized list operations to use for loops instead of ForEach
src/JfYu.RabbitMQ/JfYu.RabbitMQ.csproj Added net9.0 and net10.0 target frameworks
src/JfYu.RabbitMQ/RabbitMQService.cs Refactored batch sending to use IEnumerable
src/JfYu.UnitTests/JfYu.UnitTests.csproj Added net10.0 target framework and EF6 support for .NET Framework
src/JfYu.UnitTests/Models/*.cs Reorganized faker classes and entity files
src/JfYu.UnitTests/Office/Excel/JfYuExcelDbDataReaderWriterTests.cs Added conditional compilation for EF6 vs EF Core
src/JfYu.UnitTests/Redis/*.cs Removed #if NET8_0_OR_GREATER directives to enable broader framework support
src/JfYu.Benchmark/* Added new benchmark project with insert, update, and select benchmarks
src/Directory.Packages.props Updated package versions and added framework-specific version management
.github/workflows/*.yml Added .NET 10.0 to CI/CD pipelines
Comments suppressed due to low confidence (1)

src/JfYu.Request/JfYuHttpClient.cs:1

  • There are two spaces between 'configuration,' and 'LogFilter'. Consistent spacing should be maintained in parameter lists.

_database = Client.GetDatabase(redisConfiguration.Value.DbIndex).WithKeyPrefix(redisConfiguration.Value.Prefix);
_serializer = serializer;
_lockToken = $"{Environment.MachineName}_{Environment.ProcessId}_{Guid.NewGuid()}";
_lockToken = $"{Environment.MachineName}_{Environment.CurrentManagedThreadId}_{Guid.NewGuid()}";
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

Using Environment.CurrentManagedThreadId in a lock token is problematic because the thread ID can change between lock acquisition and release in async operations. This could prevent proper lock release. Use Environment.ProcessId instead, or consider including a unique identifier that persists across async continuations.

Suggested change
_lockToken = $"{Environment.MachineName}_{Environment.CurrentManagedThreadId}_{Guid.NewGuid()}";
_lockToken = $"{Environment.MachineName}_{Environment.ProcessId}_{Guid.NewGuid()}";

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +47
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.13" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.13" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.13" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.13" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.13" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.13" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.13" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />

<!--<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.3" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />-->
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The net10.0 target framework is using Entity Framework Core 9.0.13 packages. If .NET 10.0 exists, there should be corresponding EF Core 10.x packages available. Using an older EF Core version with a newer .NET version may lead to compatibility issues or missing features. Consider updating to EF Core 10.x packages if they exist, or document why older packages are being used.

Suggested change
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.13" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.13" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.13" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.13" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.13" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.13" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.13" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
<!--<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.3" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />-->
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.3" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 27, 2026 06:16
Copy link

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

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

Comment on lines 22 to 33
public interface IService<T, TContext> where T : BaseEntity
where TContext : DbContext
{
/// <summary>
/// Gets the master database context for write operations.
/// All Add, Update, and Remove operations use this context.
/// </summary>
public TContext Context { get; }

/// <summary>
/// Gets the readonly database context for read operations.
/// Randomly selects from configured read replicas or falls back to master if none configured.
/// All query operations use this context for load balancing.
/// </summary>
public TContext ReadonlyContext { get; }

/// <summary>
/// Adds a single entity asynchronously to the master database.
/// </summary>
/// <param name="entity">The entity to add.</param>
/// <param name="cancellationToken">Cancellation token for the async operation.</param>
/// <returns>The number of state entries written to the database (typically 1 on success).</returns>
Task<int> AddAsync(T entity, CancellationToken cancellationToken= default);
Task<int> AddAsync(T entity, CancellationToken cancellationToken = default);

/// <summary>
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

IService<T,TContext> removed the publicly exposed Context / ReadonlyContext properties. Since this is a public interface, this is a breaking change for any downstream consumers that use these properties (including custom derived services/tests). If the goal is to hide contexts, consider keeping the properties (possibly marked obsolete) or introducing a non-breaking alternative (e.g., protected access on the base class plus a separate interface for internal consumers).

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +112
var service = (Service<User, DataContext>)useService;
var contextField =typeof(Service<User, DataContext>).GetFields(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(q => q.Name.Contains("_context"));
var readonlyContextField = typeof(Service<User, DataContext>).GetFields(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(q => q.Name.Contains("_readonlyContext"));

var context = contextField!.GetValue(service) as DataContext;
var readonlyContext = readonlyContextField!.GetValue(service) as DataContext;

Assert.Contains("127.0.0.1", context!.Database.GetConnectionString());
Assert.Contains("127.0.0.1", readonlyContext!.Database.GetConnectionString());
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

These assertions use reflection to access compiler-generated backing fields for _context / _readonlyContext. This is brittle (field names/shapes can change with compiler changes or refactors) and will make tests harder to maintain. Prefer asserting behavior through the public API, or expose a stable test hook (e.g., internal properties with InternalsVisibleTo, or a derived test-only service that can read protected members).

Copilot uses AI. Check for mistakes.
Comment on lines 26 to 33
if (source.GetType().GetGenericTypeDefinition().Name.StartsWith("Tuple"))
{
var newData = (ITuple)source;
for (int i = 0; i < newData.Length; i++)
var tupleType = source.GetType();
var properties = tupleType.GetProperties();
for (int i = 0; i < properties.Length; i++)
{
if (newData[i] is IList newItemData)
if (properties[i].GetValue(source) is IList newItemData)
{
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

Tuple sheet extraction relies on GetProperties() index order, but reflection property order is not guaranteed. This can lead to writing tuple elements in the wrong order across runtimes/TFMs. To keep deterministic behavior, consider ordering the properties explicitly (e.g., Item1..ItemN), or using ITuple behind #if where available and falling back to ordered reflection only for netstandard2.0.

Copilot uses AI. Check for mistakes.
Comment on lines 14 to 18
using NPOI.SS.Formula.Functions;
using System.Data;
using System.Globalization;
using NPOI.OpenXmlFormats.Spreadsheet;

Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

using NPOI.OpenXmlFormats.Spreadsheet; appears unused in this test file, which may produce unnecessary warnings/noise. Consider removing it if it isn't needed.

Copilot uses AI. Check for mistakes.
/// <param name="logFilter">The log filter.</param>
/// <param name="logger">The logger.</param>
public class JfYuHttpClient(IHttpClientFactory factory, JfYuHttpClientConfiguration configuration, CookieContainer? cookieContainer, LogFilter logFilter, ILogger<JfYuHttpClient>? logger = null) : JfYuBaseRequest
public class JfYuHttpClient(IHttpClientFactory factory, JfYuHttpClientConfiguration configuration, LogFilter logFilter,CookieContainer? cookieContainer = null, ILogger<JfYuHttpClient>? logger = null) : JfYuBaseRequest
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

Making CookieContainer optional in DI is fine, but when it is provided as a singleton shared container, the current JfYuHttpClient implementation still mutates it from multiple scoped instances. The per-instance locking in JfYuHttpClient doesn’t provide cross-instance synchronization, so shared-cookie mode remains subject to race conditions. Consider injecting a shared lock (or avoiding shared mutable CookieContainer entirely) if shared-cookie support is kept.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 27, 2026 07:09
Copy link

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

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

/// WARNING: When set to true, the shared CookieContainer is NOT thread-safe.
/// Concurrent requests may lead to race conditions. Use with caution in multi-threaded environments.
/// </remarks>
public bool UseSharedCookieContainer { get; set; }
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

The default value for UseSharedCookieContainer has been changed from true to false. This is a breaking behavioral change that will affect existing consumers who relied on the default behavior of sharing cookies across requests. Applications that depend on shared cookie state will now have isolated cookies per request by default unless they explicitly set UseSharedCookieContainer = true.

While this change improves thread safety by default, it changes the behavior of existing applications. Consider documenting this as a breaking change in release notes and potentially bumping the major version.

Copilot uses AI. Check for mistakes.
var placeholder = $"{{{replacement.Key}}}";
var text = run.Text;
var texts = text.Split(placeholder);
var texts = text.Split([placeholder], StringSplitOptions.None);
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

String.Split is being called with a string array parameter [placeholder] to split on a single placeholder value. This approach, while functional, is less efficient than the overload that accepts a single string. The array allocation is unnecessary overhead.

Consider using text.Split(new[] { placeholder }, StringSplitOptions.None) or the ReadOnlySpan-based split methods available in more recent framework versions for better performance.

Suggested change
var texts = text.Split([placeholder], StringSplitOptions.None);
#if NET8_0_OR_GREATER
var texts = text.Split(placeholder, StringSplitOptions.None);
#else
var texts = text.Split(new[] { placeholder }, StringSplitOptions.None);
#endif

Copilot uses AI. Check for mistakes.
/// <param name="callback">Optional callback action to report progress.</param>
/// <exception cref="InvalidOperationException">Thrown when a title's value cannot be found.</exception>
protected void Write(IQueryable data, IWorkbook workbook, Type tType, JfYuExcelOptions writeOperation, Dictionary<string, string>? titles, Action<int>? callback = null, bool needAutoCreateSheet = true)
protected void Write(IQueryable data, IWorkbook workbook, Type tType, JfYuExcelOptions writeOperation, Dictionary<string, string>? titles, Action<int>? callback = null)
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

The unused parameter documentation has been removed while the parameter still exists in the method signature. The needAutoCreateSheet parameter is no longer used in the method body, but it hasn't been removed from the signature. This creates confusion and dead code.

Either restore the parameter's usage if it's needed, or remove it entirely from the method signature. Keeping unused parameters in the signature is a code smell that suggests incomplete refactoring.

Copilot uses AI. Check for mistakes.
Comment on lines +57 to +62
public async Task SendAsync<T>(string exchangeName, T message, string routingKey = "", IDictionary<string, object?>? headers = null, CancellationToken cancellationToken = default) => await SendInternalAsync(exchangeName, routingKey, headers, [message], cancellationToken).ConfigureAwait(false);

/// <inheritdoc/>
public async Task SendBatchAsync<T>(string exchangeName, IList<T> messages, string routingKey = "", IDictionary<string, object?>? headers = null, CancellationToken cancellationToken = default) => await SendInternalAsync(exchangeName, routingKey, headers, cancellationToken, messages.ToArray()).ConfigureAwait(false);
public async Task SendBatchAsync<T>(string exchangeName, IList<T> messages, string routingKey = "", IDictionary<string, object?>? headers = null, CancellationToken cancellationToken = default) => await SendInternalAsync(exchangeName, routingKey, headers, messages, cancellationToken).ConfigureAwait(false);

private async Task SendInternalAsync<T>(string exchangeName, string routingKey = "", IDictionary<string, object?>? headers = null, CancellationToken cancellationToken = default, params T[] messages)
private async Task SendInternalAsync<T>(string exchangeName, string routingKey, IDictionary<string, object?>? headers, IEnumerable<T> messages, CancellationToken cancellationToken = default)
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

The RabbitMQ SendInternalAsync method signature has been changed to accept IEnumerable<T> instead of params T[], and the parameter order has been modified. This is a breaking change for the private method that could affect binary compatibility if this library is consumed in certain scenarios.

More importantly, the params array was removed but the method is still being called with collection expressions [message] in SendAsync. While this works, it creates an unnecessary array allocation for each single message send. Consider keeping the params overload for single messages or using a more efficient approach.

Copilot uses AI. Check for mistakes.
@sonarqubecloud
Copy link

@jfwangncs jfwangncs merged commit 405281b into master Feb 27, 2026
12 checks passed
@jfwangncs jfwangncs deleted the feature/net10 branch February 27, 2026 07:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants