Soul.SqlSharp
1.0.0-beta.5
dotnet add package Soul.SqlSharp --version 1.0.0-beta.5
NuGet\Install-Package Soul.SqlSharp -Version 1.0.0-beta.5
<PackageReference Include="Soul.SqlSharp" Version="1.0.0-beta.5" />
<PackageVersion Include="Soul.SqlSharp" Version="1.0.0-beta.5" />
<PackageReference Include="Soul.SqlSharp" />
paket add Soul.SqlSharp --version 1.0.0-beta.5
#r "nuget: Soul.SqlSharp, 1.0.0-beta.5"
#:package Soul.SqlSharp@1.0.0-beta.5
#addin nuget:?package=Soul.SqlSharp&version=1.0.0-beta.5&prerelease
#tool nuget:?package=Soul.SqlSharp&version=1.0.0-beta.5&prerelease
Soul.SqlSharp
Soul.SqlSharp 是 C# 版本的轻量级 SQL ORM,核心行为对齐仓颉 sqlsharp,面向直接 SQL、简单实体映射、变更跟踪和高性能物化场景。
当前版本仍是 beta。公开 API 会继续按仓颉版本和性能目标收敛;预览期不承诺长期二进制兼容。
| 方向 | 能力 | 价值 |
|---|---|---|
| 高性能查询 | 实体和 POCO 物化使用 Reflection.Emit / DynamicMethod 生成专用委托 |
查询结果按列 ordinal 直接读取并写入属性,减少反射调用、重复列名匹配、中间对象和多阶段转换成本 |
| 命名参数 | SQL 使用 @name 参数,支持匿名对象、普通对象、Dictionary、DbParameters 和集合参数 |
调用侧不用维护位置参数顺序;IN @ids 这类集合条件可自动展开,同时最终只绑定 SQL 中真实出现的参数 |
| 链式查询 | DbSet<T> 支持字符串 SQL 片段,也支持 Where(x => ...)、Select(x => ...) 等表达式重载 |
简单查询可以类型安全地写条件和投影;复杂 SQL 仍可直接落到字符串片段,保留对最终 SQL 的控制 |
| 变更跟踪 | 自动记录实体状态和原始值,SaveChanges() 统一提交 |
插入、更新、删除走同一工作单元;更新只提交变化列,支持显式 Update、Detach、只读查询和并发检查 |
| 类型转换 | ValueConverter 统一处理实体属性的数据库值、参数值和存储值比较 |
强类型 ID、金额对象、JSON 字段等业务类型可以保留领域建模,不把运行时类型映射接口暴露成公开扩展点 |
| 模型配置 | DataAnnotations 与 Fluent API 均可使用,Fluent API 优先级更高 | 简单模型靠约定和注解,复杂模型用集中式配置覆盖,行为和仓颉版本保持同类口径 |
核心功能包括原生 SQL、链式 DbSet<T> 查询、表达式查询、分页、聚合、投影、变更跟踪、自增主键、字符串主键、复合主键、并发检查、JSON 转换、自定义 ValueConverter 和高性能物化。
安装
dotnet add package Soul.SqlSharp --prerelease
数据库驱动由业务项目自行选择和引用,例如:
dotnet add package MySqlConnector
dotnet add package Npgsql
当前包目标框架:
| Target Framework | 说明 |
|---|---|
net6.0 |
LTS 项目可用 |
net8.0 |
LTS 项目可用 |
net10.0 |
面向新版本运行时 |
快速开始
using MySqlConnector;
using Soul.SqlSharp;
using Soul.SqlSharp.Metadata;
var options = new DbContextOptionsBuilder()
.UseConnectionFactory(() => new MySqlConnection(connectionString))
.UseIdentitySql("SELECT LAST_INSERT_ID()")
.Build();
using var db = new AppDbContext(options);
var user = db.Users
.Params(new { account = "admin" })
.Where("account = @account")
.FirstOrDefault();
public sealed class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet<User> Users => Set<User>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.ToTable("sys_user")
.HasKey(x => x.Id)
.Property(x => x.Id, p => p.ValueGeneratedOnAdd())
.Property(x => x.Account, p => p.HasColumnName("account"));
}
}
public sealed class User
{
public long Id { get; set; }
public string Account { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
public DateTime? DeletedAt { get; set; }
}
实体定义
Soul.SqlSharp 基于公开属性做 ORM 映射。属性是对象对外暴露的契约,字段是实现细节;普通字段不会作为实体列映射目标。
public sealed class Product
{
public long Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public DateTime CreatedAt { get; set; }
}
支持常见 CLR 标量类型、枚举、Guid、DateTime、DateTimeOffset、TimeSpan、DateOnly、TimeOnly、byte[] 等。复杂业务类型建议通过 ValueConverter 显式映射到数据库 provider 类型。
属性映射约定速查
| 场景 | C# 属性 | 默认列名 | 主键识别 | ValueGenerated |
|---|---|---|---|---|
| 自增主键 | public long id { get; set; } |
id |
是 | OnAdd |
| 普通属性 | public string Name { get; set; } |
Name |
否 | Never |
| Column 重命名 | [Column("user_name")] public string Name { get; set; } |
user_name |
否 | Never |
| Key 主键 | [Key] public string Code { get; set; } |
Code |
是 | Never |
| NotMapped 排除 | [NotMapped] public string DisplayName { get; } |
不映射 | 否 | 不适用 |
| String id | public string id { get; set; } |
id |
是 | Never |
约定自增只针对属性名为
id且类型为整数的属性。Id是否作为主键建议用[Key]或 Fluent API 显式声明,避免大小写约定差异带来误解。
DbContext 配置
DbContextOptionsBuilder 通过扩展对象保存配置,公开 API 保持精简。
| 方法 | 说明 |
|---|---|
UseConnectionFactory(Func<DbConnection>) |
注册连接工厂。连接类型由业务项目的 ADO.NET 驱动提供 |
UseIdentitySql(string) |
设置插入自增主键后读取 identity 的 SQL |
UseIdentitySql(Func<IEntityType, string?>) |
按实体类型动态生成 identity SQL |
UseQueryTrackingBehavior(QueryTrackingBehavior) |
设置默认查询跟踪行为 |
LogTo(Action<string, DbParameters>) |
注册 SQL 日志回调 |
WithExtension<T>(T) |
保存自定义扩展对象 |
Build() |
构建 DbContextOptions |
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseConnectionFactory(() => new MySqlConnection(connectionString))
.UseIdentitySql("SELECT LAST_INSERT_ID()")
.UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll)
.LogTo((sql, parameters) =>
{
Console.WriteLine(sql);
Console.WriteLine(parameters);
})
.Build();
也可以在 DbContext 中集中配置模型:
public sealed class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
public DbSet<Product> Products => Set<Product>();
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.Properties<Money>()
.HaveConversion(new MoneyConverter());
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.ToTable("products")
.HasKey(x => x.Id)
.Property(x => x.Id, p => p.ValueGeneratedOnAdd());
}
}
模型配置
DataAnnotations
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Table("sys_user")]
public sealed class AnnotatedUser
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Column("account")]
public string Account { get; set; } = string.Empty;
[ConcurrencyCheck]
public int Version { get; set; }
[NotMapped]
public string DisplayName => Account;
}
| 注解 | 说明 |
|---|---|
[Table("name", Schema = "...")] |
设置表名和可选 schema |
[Column("name")] |
设置列名 |
[Key] |
标记主键 |
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] |
标记数据库生成值 |
[DatabaseGenerated(DatabaseGeneratedOption.None)] |
关闭数据库生成值 |
[ConcurrencyCheck] |
标记并发检查列 |
[NotMapped] |
排除属性 |
Fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ConfiguredUser>()
.ToTable("sys_user")
.HasKey(x => x.Id)
.Property(x => x.Id, p => p.ValueGeneratedOnAdd())
.Property(x => x.Account, p => p.HasColumnName("account"))
.Property(x => x.Version, p => p.IsConcurrencyToken())
.Ignore(x => x.DisplayName);
}
public sealed class ConfiguredUser
{
public long Id { get; set; }
public string Account { get; set; } = string.Empty;
public int Version { get; set; }
public string DisplayName => Account;
}
EntityTypeBuilder 方法
| 方法 | 说明 |
|---|---|
.ToTable(name, schema) |
设置表名和可选 schema |
.HasTableSharding(...) |
配置实体的分表策略 |
.HasTableSuffixes(...) |
使用后缀分表的便捷配置 |
.HasKey(...) |
设置单主键或复合主键 |
.Property(...) |
获取并配置属性 |
.Ignore(...) |
排除属性 |
PropertyBuilder 方法
| 方法 | 说明 |
|---|---|
.HasColumnName(name) |
设置列名 |
.ValueGeneratedNever() |
INSERT 时包含此列 |
.ValueGeneratedOnAdd() |
INSERT 后从数据库读取生成值 |
.IsConcurrencyToken() |
标记为并发检查列 |
.HasConversion(...) |
配置 ValueConverter |
.HasJsonConversion<T>() |
配置 JSON 文本转换 |
优先级规则
| 配置项 | 低到高优先级 |
|---|---|
| 表名 | 类型名 / DbSet 属性名 < [Table] < Fluent API .ToTable() |
| 列名 | 属性名 < [Column] < Fluent API .HasColumnName() |
| 主键 | id 约定 / [Key] < Fluent API .HasKey() |
| 值生成 | id 整数约定 < [DatabaseGenerated] < Fluent API |
| 排除属性 | [NotMapped] 与 Fluent API .Ignore() 效果等同 |
每个 DbContext 类型拥有独立模型缓存,同一实体类型在不同上下文中可以有不同配置。
后缀分表
当分表由调用环境决定、后缀不属于实体数据时,可以配置有限的后缀集合:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<XxjPrice>()
.ToTable("xxj_price")
.HasTableSharding(sharding =>
sharding.UseSuffixes("zj", "js"));
}
后缀分表也可以使用等价的便捷写法 .HasTableSuffixes("zj", "js")。
使用 ForShard 创建绑定到指定物理表的 DbSet,查询和状态操作会共享该路由:
var zjPrices = db.Prices.ForShard("zj");
zjPrices.Add(price); // INSERT INTO xxj_price_zj
var rows = zjPrices
.Where(x => x.ProductId == productId)
.ToList();
后缀匹配不区分大小写,但生成的表名使用配置时的写法。未调用 ForShard、传入未配置后缀,或者尝试把同一个跟踪实体绑定到另一分表时,框架会抛出 InvalidOperationException。后缀只允许 ASCII 字母、数字和下划线。
Queryable 查询 API
DbSet<T> 继承 DbQueryable<T>,用于围绕实体元数据构建查询。它不是完整 IQueryable Provider,但提供字符串 SQL 片段和常用表达式重载。
基础查询
var found = db.Users.Find(1L);
var first = db.Users
.OrderBy("Id")
.First();
var maybe = db.Users
.Where("account = @account")
.Params(new { account = "admin" })
.FirstOrDefault();
var all = db.Users.ToList();
| 方法 | 说明 |
|---|---|
Find(params object?[] keyValues) |
按主键查找单行,未命中返回 null |
First() |
返回第一行,无结果抛异常 |
FirstOrDefault() |
返回第一行,无结果返回 null |
ToList() |
返回列表 |
AsNoTracking() |
查询结果不进入变更跟踪 |
Build() |
返回当前 SQL 构建器和参数,用于调试或组合 |
字符串 SQL 查询
var page = db.Users
.Where("account LIKE @keyword")
.Params(new { keyword = "admin%" })
.OrderBy("Id DESC")
.ToPageResult(index: 1, size: 20);
var projection = db.Users
.Select<UserListItem>("Id", "account AS Account")
.ToList();
public sealed class UserListItem
{
public long Id { get; set; }
public string Account { get; set; } = string.Empty;
}
| 方法 | 说明 |
|---|---|
Where(sql, condition) |
追加 WHERE 条件;多个条件自动用 AND 合并 |
OrderBy(sql, condition) |
追加 ORDER BY 片段 |
GroupBy(sql, condition) |
追加 GROUP BY 片段 |
Having(sql, condition) |
追加 HAVING 条件 |
Select<TResult>(params string[] columns) |
指定投影列并切换结果类型 |
Skip(offset) / Take(count) |
跳过 / 限制行数 |
Page(index, count) |
设置分页 |
ToPageResult(index, size) |
返回 (Items, Total) |
表达式查询
var activeUsers = db.Users
.Where(x => x.Account == "admin")
.OrderByDescending(x => x.Id)
.ToList();
var projection = db.Users
.Where(x => x.Account != null)
.Select<UserListItem>(x => new UserListItem
{
Id = x.Id,
Account = x.Account
})
.ToList();
表达式会在构建 SQL 时翻译,常量和闭包变量会自动参数化:
var minAge = 18;
var maxAge = 60;
var users = db.Users
.Where(x => x.Age >= minAge)
.Where(x => x.Age <= maxAge)
.ToList();
支持的表达式能力:
| 能力 | 示例 |
|---|---|
| 比较 | x.Age > 18、x.Name == "Ada" |
| 逻辑组合 | x.Age > 18 && x.Account != null |
| NULL | x.DeletedAt == null、x.DeletedAt != null |
| 排序 | .OrderBy(x => x.Age)、.OrderByDescending(x => x.Name) |
| 分组 | .GroupBy(x => new { x.Age, x.Version }) |
| HAVING | .Having(x => SqlFns.Count(x.Id) > 0) |
| 投影 | .Select<Dto>(x => new Dto { Name = x.Name }) |
| 聚合列 | .Count(x => x.Id)、.Sum(x => x.Amount) |
DbOps
DbOps 是表达式查询中的 SQL 辅助函数,只能出现在 SQL 表达式里。
var ids = new[] { 1L, 2L, 3L };
var users = db.Users
.Alias("u")
.Where(x =>
DbOps.StartsWith(x.Account, "adm") &&
DbOps.Contains(x.Name, "Admin") &&
DbOps.In(x.Id, ids))
.OrderBy(x => x.Account)
.OrderByDescending(x => x.Id)
.ToList();
var selected = db.Users
.Where(x => DbOps.InSet(x.Id, "1, 2, 3"))
.ToList();
| 方法 | SQL 语义 |
|---|---|
DbOps.Contains(column, value) |
LIKE '%value%' |
DbOps.StartsWith(column, value) |
LIKE 'value%' |
DbOps.EndsWith(column, value) |
LIKE '%value' |
DbOps.In(column, values) |
IN @values |
DbOps.InSet(column, "1,2,3") |
拆分文本集合并按属性类型转换 |
DbOps.InSub(column, subQuery) |
IN (subQuery) |
DbFunctionAttribute
用 [DbFunction] 标记静态方法,可以在表达式查询中映射数据库函数。该特性只作用于方法,不作用于类。
using Soul.SqlSharp.Attributes;
public static class SqlFns
{
[DbFunction("LOWER")]
public static string Lower(string value)
{
throw new NotSupportedException();
}
[DbFunction("UPPER")]
public static string Upper(string value)
{
throw new NotSupportedException();
}
[DbFunction("COUNT")]
public static int Count<T>(T value)
{
throw new NotSupportedException();
}
[DbFunction("COUNT", Format = "*")]
public static int CountAll()
{
throw new NotSupportedException();
}
[DbFunction("COUNT", Format = "DISTINCT {column}")]
public static int CountDistinct<T>(T column)
{
throw new NotSupportedException();
}
}
var rows = db.Users
.GroupBy(x => x.Account)
.Having(x => SqlFns.Count(x.Id) > 0)
.Select<UserStat>(x => new UserStat
{
Account = x.Account,
Count = SqlFns.Count(x.Id)
})
.ToList();
public sealed class UserStat
{
public string Account { get; set; } = string.Empty;
public int Count { get; set; }
}
未标记的普通 .NET 方法不会自动翻译成 SQL;这类方法会在构建 SQL 时抛出 NotSupportedException。
聚合
var count = db.Users.Count("*");
var exists = db.Users.Any();
var totalAge = db.Users.Sum<int>("Age");
var avgAge = db.Users.Avg<decimal>("Age");
var maxId = db.Users.Max<long>("Id");
var minId = db.Users.Min<long>("Id");
var expressionCount = db.Users.Count(x => x.Id);
异步 API
常用查询和执行方法提供异步版本:
var users = await db.Users.ToListAsync(cancellationToken);
var total = await db.Users.CountAsync(cancellationToken);
var page = await db.Users.ToPageResultAsync(1, 20, cancellationToken);
var affected = await db.ExecuteAsync(sql, param, cancellationToken);
原生 SQL
原生 SQL 适合复杂查询、数据库特定语法和手写 SQL 优先的场景。
var users = db.Query<User>(
"SELECT Id, account AS Account, Name FROM sys_user WHERE account = @account",
new { account = "admin" });
var count = db.ExecuteScalar<long>(
"SELECT COUNT(*) FROM sys_user WHERE account = @account",
new { account = "admin" });
var affected = db.Execute(
"UPDATE sys_user SET Name = @name WHERE Id = @id",
new { id = 1L, name = "Ada" });
标量查询允许值类型:
var values = db.Query<int>("SELECT 42");
var total = db.ExecuteScalar<long>("SELECT COUNT(*) FROM sys_user");
DbParameters
DbParameters 是 SQL 参数容器,参数名可写 name、@name、:name 或 ?name,内部会统一归一化。
var parameters = new DbParameters();
parameters.Add("name", "Widget");
parameters.Add("price", 999m);
db.Execute(
"UPDATE products SET Price = @price WHERE Name = @name",
parameters);
也可以从对象或字典创建:
var fromObject = DbParameters.From(new { name = "Widget", price = 999m });
var fromDictionary = DbParameters.From(new Dictionary<string, object?>
{
["name"] = "Widget",
["price"] = 999m
});
DbParameters.From 会跳过明显的业务辅助复杂对象属性,并且最终只绑定 SQL 文本中真实出现的参数,避免把未使用的复杂对象误传给数据库驱动。
var query = db.Users
.Params(new
{
account = "admin",
Orders = new Dictionary<string, string>() // SQL 未引用时不会绑定
})
.Where("account = @account");
IN 参数支持集合展开:
var users = db.Users
.Params(new { ids = new[] { 1L, 2L, 3L } })
.Where("Id IN @ids")
.ToList();
变更跟踪
默认查询会跟踪实体。只读查询可关闭跟踪:
var users = db.Users
.AsNoTracking()
.ToList();
实体状态:
| 状态 | 含义 |
|---|---|
Detached |
未被 ChangeTracker 跟踪 |
Added |
已调用 Add,待 INSERT |
Unchanged |
已持久化,无变化 |
Modified |
已持久化,有属性变化,待 UPDATE |
Deleted |
已调用 Remove,待 DELETE |
基本增删改:
var product = new Product
{
Name = "Widget",
Price = 999m,
CreatedAt = DateTime.UtcNow
};
db.Products.Add(product);
db.SaveChanges();
product.Price = 1099m;
db.SaveChanges();
db.Products.Remove(product);
db.SaveChanges();
跨 DbContext 或不先查询直接更新时,可以显式设置状态:
var detached = new Product
{
Id = 10,
Name = "New Name",
Price = 100m
};
db.Products.Update(detached);
db.SaveChanges();
并发列通过 [ConcurrencyCheck] 或 .IsConcurrencyToken() 配置。UPDATE / DELETE 会把并发列原始值放入 WHERE 条件;实际影响行数为 0 时会抛出并发更新异常。
事务与连接生命周期
using var transaction = db.BeginTransaction();
db.Execute("UPDATE account SET Balance = Balance - @amount WHERE Id = @id", new { id = 1, amount = 100 });
db.Execute("UPDATE account SET Balance = Balance + @amount WHERE Id = @id", new { id = 2, amount = 100 });
transaction.Commit();
可以通过 HasTransaction() 判断当前上下文是否处于事务中:
if (db.HasTransaction())
{
// 当前上下文已有事务
}
默认情况下,查询和执行会按需打开连接并在操作结束后释放。需要在非事务场景连续执行多次操作时,可以手动打开连接:
db.OpenConnection();
try
{
db.Execute("UPDATE products SET Price = Price + 1");
var products = db.Query<Product>("SELECT Id, Name, Price, CreatedAt FROM products");
}
finally
{
db.CloseConnection();
}
事务进行中不要手动关闭连接。用户主动 OpenConnection() 后,后续操作会复用当前连接,直到 CloseConnection() 或释放 DbContext。
ValueConverter
自定义业务类型 converter 配置在模型元数据上,不通过 TypeMapper.Register 或 UseTypeMapper 这类运行时全局兜底入口。ValueConverter 是显式值转换扩展点。
using Soul.SqlSharp.Converters;
public readonly record struct Money(decimal Amount);
public sealed class MoneyConverter : ValueConverter<Money, decimal>
{
public override decimal ConvertToProvider(Money value) => value.Amount;
public override Money ConvertFromProvider(decimal value) => new Money(value);
}
属性级显式配置:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.Property(x => x.Total)
.HasConversion<Money, decimal>(new MoneyConverter());
}
类型级默认约定:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.Properties<Money>()
.HaveConversion(new MoneyConverter());
}
JSON 字段可以直接使用内置转换:
modelBuilder.Entity<Order>()
.Property(x => x.Payload)
.HasJsonConversion<OrderPayload>();
属性级配置优先于类型级约定。converter 会在实体物化、DbQueryable.Find 主键参数、DbQueryable.Params 参数绑定、表达式查询参数、SaveChanges 参数生成和变更跟踪存储值比较中生效。
没有属性元数据上下文的裸 SQL 参数和标量查询不会推断自定义业务类型 converter;这类场景应直接传 provider 值,或走实体 / Queryable 路径。
物化行为
实体、POCO 和标量物化统一由物化器处理。实体和 POCO 物化会生成专用委托,直接调用 DbDataReader.GetInt32、GetInt64、GetString、GetDecimal 等强类型读取方法,并直接写入属性 setter。
等价展开:
var entity = new User();
if (!reader.IsDBNull(0))
{
entity.Id = reader.GetInt64(0);
}
if (!reader.IsDBNull(1))
{
entity.Account = reader.GetString(1);
}
return entity;
如果数据库返回 NULL,物化器会跳过对应属性赋值,保留 CLR 默认值或属性初始化值。这个行为是为了保证 ORM 易用性,不会因为非 nullable 值类型字段遇到脏数据就直接中断整行物化。
标量查询不依赖列名,缓存键按目标类型、读取偏移和 provider 类型区分,避免把列名带入标量物化缓存。
许可证
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
-
net6.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-beta.5 | 36 | 9/22/2026 |