Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Alex chow
2025-07-04 00:46:38 +02:00
1034 changed files with 156391 additions and 26295 deletions

View File

@@ -5,3 +5,8 @@ dotnet_analyzer_diagnostic.category-Style.severity = none
# CS0649: 从未对字段“TransactionalAttribute._uowManager”赋值字段将一直保持其默认值 null
dotnet_diagnostic.CS0649.severity = none
# CS8618: 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
dotnet_diagnostic.CS8618.severity = none
charset = utf-8-bom

31
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View File

@@ -0,0 +1,31 @@
---
name: "Bug Report 🐛"
about: 这里只能提交 Bug提交其他无关信息会被关闭
title: ''
labels: Bug
assignees: ''
---
<!--
以下为必读项,不仔细阅读会导致你的 Issue 被关闭
1. 该分类下只能提交 Bug如果要询问使用方法等请前往讨论区https://github.com/dotnetcore/FreeSql/discussions
2. FreeSql写了许多文档在提问题前麻烦先查看[常见问题](https://freesql.net/reference/faq.html)
3. 不完整的信息将不会得到任何回复!发布问题后,请保持对 issue 的关注,有时会有需要进一步沟通的信息,很长时间内没有得到答复的 issue 将被关闭。
4. 提供可重现的代码,至少应描述以下信息 -->
#### 问题描述及重现代码:
```c#
// c# code
```
#### 数据库版本
#### 安装的Nuget包
#### .net framework/. net core 及具体版本

View File

@@ -0,0 +1,33 @@
---
name: "Feature Request 🚀"
about: 这里只能提交 Feature 请求,提交其他无关信息会被关闭
title: ''
labels: Feature
assignees: ''
---
<!--
以下为必读项,不仔细阅读会导致你的 Issue 被关闭
1. 该分类下只能提交 Feature如果要询问使用方法等请前往讨论区https://github.com/dotnetcore/FreeSql/discussions
2. 请尽量详细的描述清楚你的需求
-->
#### Feature 特性
```c#
// c# code
```
#### 简要描述原因
```c#
// c# code
```
#### 使用场景
```c#
// c# code
```

View File

@@ -1,50 +1,33 @@
name: .NET Core Deploy Docfx
name: Docfx Deploy
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
push:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.202
- name: Exclude example projects
run: dotnet sln FreeSql.sln remove Examples/**/*.csproj FreeSql.Tests/**/*.csproj
- name: Install dependencies
run: dotnet restore
- name: Build solution
run: dotnet build --configuration Release --no-restore
generate-docs:
runs-on: windows-latest
needs: build
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.202
with:
dotnet-version: 7.0.x
- name: Remove Examples
run: dotnet sln FreeSql.sln remove (ls -r Examples/**/*.csproj)
- name: Remove FreeSql.Tests
run: dotnet sln FreeSql.sln remove (ls -r FreeSql.Tests/**/*.csproj)
- name: Install dependencies
run: dotnet restore
run: dotnet restore .\FreeSql.sln
- name: Build solution
run: dotnet build --configuration Release --no-restore .\FreeSql.sln
- name: Setup DocFX
uses: crazy-max/ghaction-chocolatey@v1
with:
args: install docfx --version 2.56.7
args: install docfx
- name: DocFX Build
working-directory: docs
run: docfx docfx.json

View File

@@ -1,12 +1,21 @@
<Project>
<Project>
<PropertyGroup>
<RepositoryUrl>https://github.com/dotnetcore/FreeSql</RepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<NoWarn>1701;1702;CS1573;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8618;CS8619;CS8620;CS8622;CS8625;CS8629;CS8714;CS1591;CS0649;CA2200</NoWarn>
</PropertyGroup>
<!--
经常出于版本交叉问题,暂时关闭,在每个项目上设置版本号
<PropertyGroup>
<Version>3.5.210-preview20250626</Version>
</PropertyGroup>
-->
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
</ItemGroup>

Binary file not shown.

View File

@@ -1,13 +1,10 @@
using FreeSql;
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using FreeSql;
using FreeSql.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace aspnetcore_transaction.Controllers
{
@@ -24,24 +21,25 @@ namespace aspnetcore_transaction.Controllers
[HttpGet("1")]
//[Transactional]
virtual public object Get([FromServices] BaseRepository<Song> repoSong, [FromServices] BaseRepository<Detail> repoDetail, [FromServices] SongRepository repoSong2,
public async Task<object> Get([FromServices] BaseRepository<Song> repoSong, [FromServices] BaseRepository<Detail> repoDetail, [FromServices] SongRepository repoSong2,
[FromServices] SongService serviceSong)
{
//repoSong.Insert(new Song());
//repoDetail.Insert(new Detail());
//repoSong2.Insert(new Song());
serviceSong.Test1();
//serviceSong.Test1();
await serviceSong.Test11();
return "111";
}
[HttpGet("2")]
//[Transactional]
async virtual public Task<object> GetAsync([FromServices] BaseRepository<Song> repoSong, [FromServices] BaseRepository<Detail> repoDetail, [FromServices] SongRepository repoSong2,
[Transactional]
public async Task<object> GetAsync([FromServices] BaseRepository<Song> repoSong, [FromServices] BaseRepository<Detail> repoDetail, [FromServices] SongRepository repoSong2,
[FromServices] SongService serviceSong)
{
await serviceSong.Test2();
await serviceSong.Test3();
await repoSong.InsertAsync(new Song());
await repoDetail.InsertAsync(new Detail());
return "111";
}
}
@@ -61,15 +59,21 @@ namespace aspnetcore_transaction.Controllers
}
[Transactional(Propagation = Propagation.Nested)] //sqlite 不能嵌套事务,会锁库的
public virtual void Test1()
public void Test1()
{
_repoSong.Insert(new Song());
_repoDetail.Insert(new Detail());
_repoSong2.Insert(new Song());
}
[Transactional(Propagation = Propagation.Nested)] //sqlite 不能嵌套事务,会锁库的
public Task Test11()
{
return Task.Delay(TimeSpan.FromSeconds(1)).ContinueWith(t =>
_repoSong.InsertAsync(new Song()));
}
[Transactional(Propagation = Propagation.Nested)] //sqlite 不能嵌套事务,会锁库的
async public virtual Task Test2()
public async Task Test2()
{
await _repoSong.InsertAsync(new Song());
await _repoDetail.InsertAsync(new Detail());
@@ -77,7 +81,7 @@ namespace aspnetcore_transaction.Controllers
}
[Transactional(Propagation = Propagation.Nested)] //sqlite 不能嵌套事务,会锁库的
async public virtual Task<object> Test3()
public async Task<object> Test3()
{
await _repoSong.InsertAsync(new Song());
await _repoDetail.InsertAsync(new Detail());
@@ -110,53 +114,4 @@ namespace aspnetcore_transaction.Controllers
public int SongId { get; set; }
public string Title { get; set; }
}
public static class IdleBusExtesions
{
static AsyncLocal<string> AsyncLocalTenantId = new AsyncLocal<string>();
public static IdleBus<IFreeSql> ChangeTenant(this IdleBus<IFreeSql> ib, string tenantId)
{
AsyncLocalTenantId.Value = tenantId;
return ib;
}
public static IFreeSql Get(this IdleBus<IFreeSql> ib) => ib.Get(AsyncLocalTenantId.Value ?? "default");
public static IBaseRepository<T> GetRepository<T>(this IdleBus<IFreeSql> ib) where T : class => ib.Get().GetRepository<T>();
static void test()
{
IdleBus<IFreeSql> ib = null; //单例注入
var fsql = ib.Get(); //获取当前租户对应的 IFreeSql
var fsql00102 = ib.ChangeTenant("00102").Get(); //切换租户,后面的操作都是针对 00102
var songRepository = ib.GetRepository<Song>();
var detailRepository = ib.GetRepository<Detail>();
}
public static IServiceCollection AddRepository(this IServiceCollection services, params Assembly[] assemblies)
{
services.AddScoped(typeof(IBaseRepository<>), typeof(YourDefaultRepository<>));
services.AddScoped(typeof(BaseRepository<>), typeof(YourDefaultRepository<>));
services.AddScoped(typeof(IBaseRepository<,>), typeof(YourDefaultRepository<,>));
services.AddScoped(typeof(BaseRepository<,>), typeof(YourDefaultRepository<,>));
if (assemblies?.Any() == true)
foreach (var asse in assemblies)
foreach (var repo in asse.GetTypes().Where(a => a.IsAbstract == false && typeof(IBaseRepository).IsAssignableFrom(a)))
services.AddScoped(repo);
return services;
}
}
class YourDefaultRepository<T> : BaseRepository<T> where T : class
{
public YourDefaultRepository(IdleBus<IFreeSql> ib) : base(ib.Get(), null, null) { }
}
class YourDefaultRepository<T, TKey> : BaseRepository<T, TKey> where T : class
{
public YourDefaultRepository(IdleBus<IFreeSql> ib) : base(ib.Get(), null, null) { }
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using FreeSql;
using FreeSql.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace aspnetcore_transaction.Domain
{
public class SongRepository : DefaultRepository<Song, int>
{
public SongRepository(UnitOfWorkManager uowm) : base(uowm?.Orm, uowm) { }
}
[Description("123")]
public class Song
{
/// <summary>
/// 自增
/// </summary>
[Column(IsIdentity = true)]
[Description("自增id")]
public int Id { get; set; }
public string Title { get; set; }
}
public class Detail
{
[Column(IsIdentity = true)]
public int Id { get; set; }
public int SongId { get; set; }
public string Title { get; set; }
}
}

View File

@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Rougamo />
</Weavers>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="Rougamo" minOccurs="0" maxOccurs="1" type="xs:anyType" />
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -18,10 +13,15 @@ namespace aspnetcore_transaction
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(loggerBuilder =>
{
loggerBuilder.SetMinimumLevel(LogLevel.Critical);
//loggerBuilder.ClearProviders();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseServiceProviderFactory(new FreeSql.DynamicProxyServiceProviderFactory());
;
}
}

View File

@@ -1,27 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:35350/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"dbcontext_01": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:35351/"
}
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:64375/",
"sslPort": 44336
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"aspnetcore_transaction": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}

View File

@@ -0,0 +1,57 @@
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using aspnetcore_transaction.Domain;
using FreeSql;
using FreeSql.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace aspnetcore_transaction.Services
{
public class SongService
{
BaseRepository<Song> _repoSong;
BaseRepository<Detail> _repoDetail;
SongRepository _repoSong2;
public SongService(BaseRepository<Song> repoSong, BaseRepository<Detail> repoDetail, SongRepository repoSong2)
{
var tb = repoSong.Orm.CodeFirst.GetTableByEntity(typeof(Song));
_repoSong = repoSong;
_repoDetail = repoDetail;
_repoSong2 = repoSong2;
}
[Transactional(Propagation = Propagation.Nested)] //sqlite 不能嵌套事务,会锁库的
public void Test1()
{
_repoSong.Insert(new Song());
_repoDetail.Insert(new Detail());
_repoSong2.Insert(new Song());
}
[Transactional(Propagation = Propagation.Nested)] //sqlite 不能嵌套事务,会锁库的
public Task Test11()
{
return Task.Delay(TimeSpan.FromSeconds(1)).ContinueWith(t =>
_repoSong.InsertAsync(new Song()));
}
[Transactional(Propagation = Propagation.Nested)] //sqlite 不能嵌套事务,会锁库的
public async Task Test2()
{
await _repoSong.InsertAsync(new Song());
await _repoDetail.InsertAsync(new Detail());
await _repoSong2.InsertAsync(new Song());
}
[Transactional(Propagation = Propagation.Nested)] //sqlite 不能嵌套事务,会锁库的
public async Task<object> Test3()
{
await _repoSong.InsertAsync(new Song());
await _repoDetail.InsertAsync(new Detail());
await _repoSong2.InsertAsync(new Song());
return "123";
}
}
}

View File

@@ -1,19 +1,14 @@
using System;
using System.Collections.Generic;
using System;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using aspnetcore_transaction.Controllers;
using FreeSql;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace aspnetcore_transaction
{
@@ -22,15 +17,19 @@ namespace aspnetcore_transaction
public Startup(IConfiguration configuration)
{
Configuration = configuration;
Fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\test_trans.db")
Fsql = new FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=50;TrustServerCertificate=true")
.UseAutoSyncStructure(true)
.UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText))
//.UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText))
.UseNoneCommandParameter(true)
.Build();
Fsql.Aop.TraceBefore += (_, e) => Trace.WriteLine($"----TraceBefore---{e.Identifier} {e.Operation}");
Fsql.Aop.TraceAfter += (_, e) => Trace.WriteLine($"----TraceAfter---{e.Identifier} {e.Operation} {e.Remark} {e.Exception?.Message} {e.ElapsedMilliseconds}ms\r\n");
//Fsql.Aop.TraceBefore += (_, e) => Trace.WriteLine($"----TraceBefore---{e.Identifier} {e.Operation}");
Fsql.Aop.TraceAfter += (_, e) =>
{
//Trace.WriteLine($"----TraceAfter---{e.Identifier} {e.Operation} {e.Remark} {e.Exception?.Message} {e.ElapsedMilliseconds}ms\r\n");
if (e.Exception != null && e.Exception.Message.StartsWith("【主库】状态不可用,等待后台检查程序恢复方可使用。") == false) Console.WriteLine(e.Exception.Message + " === " + Fsql.Ado.MasterPool.Statistics);
};
}
public IConfiguration Configuration { get; }
@@ -38,11 +37,17 @@ namespace aspnetcore_transaction
public void ConfigureServices(IServiceCollection services)
{
ThreadPool.SetMinThreads(1000, 1000);
services.AddControllersWithViews();
services.AddSingleton<IFreeSql>(Fsql);
services.AddScoped<UnitOfWorkManager>();
services.AddFreeRepository(null, typeof(Startup).Assembly);
////批量注入
//foreach (var repo in typeof(Startup).Assembly.GetTypes()
// .Where(a => a.IsAbstract == false && typeof(IBaseRepository).IsAssignableFrom(a)))
// services.AddScoped(repo);
services.AddScoped<SongService>();
}
@@ -52,6 +57,12 @@ namespace aspnetcore_transaction
Console.OutputEncoding = Encoding.GetEncoding("GB2312");
Console.InputEncoding = Encoding.GetEncoding("GB2312");
app.Use(async (context, next) =>
{
TransactionalAttribute.SetServiceProvider(context.RequestServices);
await next();
});
app.UseHttpMethodOverride(new HttpMethodOverrideOptions { FormFieldName = "X-Http-Method-Override" });
app.UseDeveloperExceptionPage();
app.UseRouting();

View File

@@ -1,54 +1,49 @@
using FreeSql;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System;
using System.Data;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Rougamo.Context;
namespace FreeSql
{
/// <summary>
/// 使用事务执行,请查看 Program.cs 代码开启动态代理
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class TransactionalAttribute : DynamicProxyAttribute, IActionFilter
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class TransactionalAttribute : Rougamo.MoAttribute
{
public Propagation Propagation { get; set; } = Propagation.Required;
public IsolationLevel IsolationLevel { get => _IsolationLevelPriv.Value; set => _IsolationLevelPriv = value; }
IsolationLevel? _IsolationLevelPriv;
public IsolationLevel IsolationLevel { get => m_IsolationLevel.Value; set => m_IsolationLevel = value; }
IsolationLevel? m_IsolationLevel;
static AsyncLocal<IServiceProvider> m_ServiceProvider = new AsyncLocal<IServiceProvider>();
public static void SetServiceProvider(IServiceProvider serviceProvider) => m_ServiceProvider.Value = serviceProvider;
[DynamicProxyFromServices]
#pragma warning disable IDE0044 // 添加只读修饰符
UnitOfWorkManager _uowManager;
#pragma warning restore IDE0044 // 添加只读修饰符
IUnitOfWork _uow;
public override Task Before(DynamicProxyBeforeArguments args) => OnBefore(_uowManager);
public override Task After(DynamicProxyAfterArguments args) => OnAfter(args.Exception);
//这里是为了 controller
public void OnActionExecuting(ActionExecutingContext context) => OnBefore(context.HttpContext.RequestServices.GetService(typeof(UnitOfWorkManager)) as UnitOfWorkManager);
public void OnActionExecuted(ActionExecutedContext context) => OnAfter(context.Exception);
Task OnBefore(UnitOfWorkManager uowm)
public override void OnEntry(MethodContext context)
{
_uow = uowm.Begin(this.Propagation, this._IsolationLevelPriv);
return Task.FromResult(false);
var uowManager = m_ServiceProvider.Value.GetService(typeof(UnitOfWorkManager)) as UnitOfWorkManager;
_uow = uowManager.Begin(this.Propagation, this.m_IsolationLevel);
}
Task OnAfter(Exception ex)
public override void OnExit(MethodContext context)
{
try
if (typeof(Task).IsAssignableFrom(context.RealReturnType))
{
if (ex == null) _uow.Commit();
else _uow.Rollback();
((Task)context.ReturnValue).ContinueWith(t => _OnExit());
return;
}
finally
_OnExit();
void _OnExit()
{
_uow.Dispose();
try
{
if (context.Exception == null) _uow.Commit();
else _uow.Rollback();
}
catch { }
finally
{
_uow.Dispose();
}
}
return Task.FromResult(false);
}
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -11,14 +11,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FreeSql.DbContext" Version="3.2.802" />
<PackageReference Include="FreeSql.Provider.Sqlite" Version="3.2.802" />
<PackageReference Include="Rougamo.Fody" Version="1.1.1" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
<PackageReference Include="FreeSql.DynamicProxy" Version="1.5.0" />
<PackageReference Include="IdleBus" Version="1.5.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\FreeSql.DbContext\FreeSql.DbContext.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Sqlite\FreeSql.Provider.Sqlite.csproj" />
</ItemGroup>
</Project>

View File

@@ -9,9 +9,9 @@
自增
</summary>
</member>
<member name="T:FreeSql.TransactionalAttribute">
<member name="P:aspnetcore_transaction.Domain.Song.Id">
<summary>
使用事务执行,请查看 Program.cs 代码开启动态代理
自增
</summary>
</member>
</members>

View File

@@ -0,0 +1,36 @@
using FreeSql.DataAnnotations;
using Newtonsoft.Json;
using System.ComponentModel;
#nullable disable
namespace Densen.Models.ids
{
/// <summary>
/// 角色声明
/// </summary>
[JsonObject(MemberSerialization.OptIn), Table(DisableSyncStructure = true)]
public partial class AspNetRoleClaims
{
[DisplayName("ID")]
[JsonProperty, Column(IsPrimary = true, IsIdentity = true)]
public int Id { get; set; }
[DisplayName("角色ID")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string RoleId { get; set; }
[DisplayName("角色声明")]
[JsonProperty, Column(StringLength = -2)]
public string ClaimType { get; set; }
[DisplayName("值")]
[JsonProperty, Column(StringLength = -2)]
public string ClaimValue { get; set; }
[Navigate(nameof(RoleId))]
public virtual AspNetRoles AspNetRoles { get; set; }
}
}

View File

@@ -0,0 +1,39 @@
using FreeSql.DataAnnotations;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.ComponentModel;
#nullable disable
namespace Densen.Models.ids
{
/// <summary>
/// 角色定义
/// </summary>
[JsonObject(MemberSerialization.OptIn), Table(DisableSyncStructure = true)]
public partial class AspNetRoles
{
[DisplayName("ID")]
[JsonProperty, Column(StringLength = -2, IsPrimary = true, IsNullable = false)]
public string Id { get; set; }
[DisplayName("角色")]
[JsonProperty, Column(StringLength = -2)]
public string Name { get; set; }
[DisplayName("标准化名称")]
[JsonProperty, Column(StringLength = -2)]
public string NormalizedName { get; set; }
[DisplayName("并发票据")]
[JsonProperty, Column(StringLength = -2)]
public string ConcurrencyStamp { get; set; }
//导航属性
[Navigate(nameof(AspNetUserRoles.RoleId))]
[DisplayName("角色表")]
public virtual List<AspNetUserRoles> AspNetUserRoless { get; set; }
}
}

View File

@@ -0,0 +1,39 @@
using FreeSql.DataAnnotations;
using Newtonsoft.Json;
using System.ComponentModel;
#nullable disable
namespace Densen.Models.ids
{
/// <summary>
/// 用户声明
/// </summary>
[JsonObject(MemberSerialization.OptIn), Table(DisableSyncStructure = true)]
public partial class AspNetUserClaims
{
[DisplayName("ID")]
[JsonProperty, Column(IsPrimary = true, IsIdentity = true)]
public int Id { get; set; }
[DisplayName("用户ID")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string UserId { get; set; }
[DisplayName("声明类型")]
[JsonProperty, Column(StringLength = -2)]
public string ClaimType { get; set; }
[DisplayName("值")]
[JsonProperty, Column(StringLength = -2)]
public string ClaimValue { get; set; }
/// <summary>
/// 用户
/// </summary>
[Navigate(nameof(UserId))]
public virtual AspNetUsers AspNetUsers { get; set; }
}
}

View File

@@ -0,0 +1,39 @@
using FreeSql.DataAnnotations;
using Newtonsoft.Json;
using System.ComponentModel;
#nullable disable
namespace Densen.Models.ids
{
/// <summary>
/// 用户登录
/// </summary>
[JsonObject(MemberSerialization.OptIn), Table(DisableSyncStructure = true)]
public partial class AspNetUserLogins
{
[DisplayName("外联登录")]
[JsonProperty, Column(StringLength = -2, IsPrimary = true, IsNullable = false)]
public string LoginProvider { get; set; }
[DisplayName("用户ID")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string UserId { get; set; }
[DisplayName("外联Key")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string ProviderKey { get; set; }
[DisplayName("外联名称")]
[JsonProperty, Column(StringLength = -2)]
public string ProviderDisplayName { get; set; }
/// <summary>
/// 用户
/// </summary>
[Navigate(nameof(UserId))]
public virtual AspNetUsers AspNetUsers { get; set; }
}
}

View File

@@ -0,0 +1,45 @@
using FreeSql.DataAnnotations;
using Newtonsoft.Json;
using System.ComponentModel;
#nullable disable
namespace Densen.Models.ids
{
/// <summary>
/// 角色表
/// <para>存储向哪些用户分配哪些角色</para>
/// </summary>
[JsonObject(MemberSerialization.OptIn), Table(DisableSyncStructure = true)]
public partial class AspNetUserRoles
{
[DisplayName("用户ID")]
[JsonProperty, Column(StringLength = -2, IsPrimary = true, IsNullable = false)]
public string UserId { get; set; }
[JsonProperty, Column(IsIgnore = true)]
[DisplayName("用户")]
public string UserName { get => roleName ?? (AspNetUserss?.UserName); set => userName = value; }
string userName;
[DisplayName("角色ID")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string RoleId { get; set; }
[JsonProperty, Column(IsIgnore = true)]
[DisplayName("角色名称")]
public string RoleName { get => roleName ?? (AspNetRoless?.Name); set => roleName = value; }
string roleName;
[DisplayName("角色定义")]
[Navigate(nameof(RoleId))]
public virtual AspNetRoles AspNetRoless { get; set; }
[DisplayName("用户表")]
[Navigate(nameof(UserId))]
public virtual AspNetUsers AspNetUserss { get; set; }
}
}

View File

@@ -0,0 +1,36 @@
using FreeSql.DataAnnotations;
using Newtonsoft.Json;
using System.ComponentModel;
#nullable disable
namespace Densen.Models.ids
{
/// <summary>
/// 用户令牌
/// </summary>
[JsonObject(MemberSerialization.OptIn), Table(DisableSyncStructure = true)]
public partial class AspNetUserTokens
{
[DisplayName("用户ID")]
[JsonProperty, Column(StringLength = -2, IsPrimary = true, IsNullable = false)]
public string UserId { get; set; }
[DisplayName("名称")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string Name { get; set; }
[DisplayName("外部登录提供程序")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string LoginProvider { get; set; }
[DisplayName("值")]
[JsonProperty, Column(StringLength = -2)]
public string Value { get; set; }
[Navigate(nameof(UserId))]
public virtual AspNetUsers AspNetUsers { get; set; }
}
}

View File

@@ -0,0 +1,149 @@
using FreeSql.DataAnnotations;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
#nullable disable
namespace Densen.Models.ids
{
/// <summary>
/// 用户表
/// </summary>
[JsonObject(MemberSerialization.OptIn), Table(DisableSyncStructure = true)]
public partial class AspNetUsers
{
[DisplayName("用户ID")]
[JsonProperty, Column(StringLength = -2, IsPrimary = true, IsNullable = false)]
public string Id { get; set; }
[JsonProperty, Column(StringLength = -2)]
[DisplayName("用户名")]
public string UserName { get; set; }
[JsonProperty, Column(IsIgnore = true)]
[DisplayName("角色")]
public string RoleName { get => roleName ?? (AspNetUserRoless != null ? string.Join(",", AspNetUserRoless?.Select(a => a.RoleName ?? a.RoleId).ToList()) : ""); set => roleName = value; }
string roleName;
[JsonProperty, Column(StringLength = -2)]
public string Email { get; set; }
[DisplayName("电话")]
[JsonProperty, Column(StringLength = -2)]
public string PhoneNumber { get; set; }
[DisplayName("自定义名称")]
[JsonProperty, Column(StringLength = -2)]
public string Name { get; set; }
[DisplayName("自定义角色")]
[JsonProperty, Column(StringLength = -2)]
public string UserRole { get; set; }
[DisplayName("密码哈希")]
[JsonProperty, Column(StringLength = -2)]
public string PasswordHash { get; set; }
[DisplayName("电子邮件已确认")]
[JsonProperty]
public int EmailConfirmed { get; set; }
[DisplayName("电话号码已确认")]
[JsonProperty]
public int PhoneNumberConfirmed { get; set; }
[DisplayName("锁定结束")]
[JsonProperty, Column(StringLength = -2)]
public string LockoutEnd { get; set; }
[DisplayName("启用双因素登录")]
[JsonProperty]
public int TwoFactorEnabled { get; set; }
[DisplayName("并发票据")]
[JsonProperty, Column(StringLength = -2)]
public string ConcurrencyStamp { get; set; }
[DisplayName("防伪印章")]
[JsonProperty, Column(StringLength = -2)]
public string SecurityStamp { get; set; }
[DisplayName("标准化电子邮件")]
[JsonProperty, Column(StringLength = -2)]
public string NormalizedEmail { get; set; }
[DisplayName("标准化用户名")]
[JsonProperty, Column(StringLength = -2)]
public string NormalizedUserName { get; set; }
[DisplayName("启用锁定")]
[JsonProperty]
public int LockoutEnabled { get; set; }
[DisplayName("国家")]
[JsonProperty, Column(StringLength = -2)]
public string Country { get; set; }
[DisplayName("省")]
[JsonProperty, Column(StringLength = -2)]
public string Province { get; set; }
[DisplayName("城市")]
[JsonProperty, Column(StringLength = -2)]
public string City { get; set; }
[DisplayName("县")]
[JsonProperty, Column(StringLength = -2)]
public string County { get; set; }
[DisplayName("邮编")]
[JsonProperty, Column(StringLength = -2)]
public string Zip { get; set; }
[DisplayName("街道")]
[JsonProperty, Column(StringLength = -2)]
public string Street { get; set; }
[DisplayName("税号")]
[JsonProperty, Column(StringLength = -2)]
public string TaxNumber { get; set; }
[DisplayName("提供者")]
[JsonProperty, Column(StringLength = -2)]
public string provider { get; set; }
[DisplayName("UUID")]
[JsonProperty, Column(StringLength = -2)]
public string UUID { get; set; }
[DisplayName("生日")]
[JsonProperty, Column(StringLength = -2)]
public string DOB { get; set; }
[DisplayName("访问失败次数")]
[JsonProperty]
public int AccessFailedCount { get; set; }
//导航属性
[Navigate(nameof(AspNetUserRoles.UserId))]
[DisplayName("角色表")]
public virtual List<AspNetUserRoles> AspNetUserRoless { get; set; }
[Navigate(nameof(AspNetUserClaims.UserId))]
[DisplayName("用户声明")]
public virtual List<AspNetUserClaims> AspNetUserClaimss { get; set; }
[Navigate(nameof(AspNetUserLogins.UserId))]
[DisplayName("用户登录")]
public virtual List<AspNetUserLogins> AspNetUserLoginss { get; set; }
[JsonProperty, Column(IsIgnore = true)]
[DisplayName("1st角色")]
public string RoleName1st { get => roleName1st ?? ((AspNetUserRoless != null && AspNetUserRoless.Any()) ? AspNetUserRoless?.Select(a => a.RoleName ?? a.RoleId ?? "").First() : ""); set => roleName1st = value; }
string roleName1st;
}
}

View File

@@ -0,0 +1,53 @@
using FreeSql.DataAnnotations;
using Newtonsoft.Json;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
#nullable disable
namespace Densen.Models.ids
{
/// <summary>
/// 设备代码
/// </summary>
[JsonObject(MemberSerialization.OptIn), Table(DisableSyncStructure = true)]
public partial class DeviceCodes
{
[Display(Name = "用户代码")]
[JsonProperty, Column(StringLength = -2, IsPrimary = true, IsNullable = false)]
public string UserCode { get; set; }
[Display(Name = "设备代码")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string DeviceCode { get; set; }
[Display(Name = "主题编号")]
[JsonProperty, Column(StringLength = -2)]
public string SubjectId { get; set; }
[Display(Name = "会话编号")]
[JsonProperty, Column(StringLength = -2)]
public string SessionId { get; set; }
[Display(Name = "客户编号")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string ClientId { get; set; }
[Display(Name = "描述")]
[JsonProperty, Column(StringLength = -2)]
public string Description { get; set; }
[Display(Name = "创建时间")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string CreationTime { get; set; }
[Display(Name = "到期")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string Expiration { get; set; }
[DisplayName("数据")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string Data { get; set; }
}
}

View File

@@ -0,0 +1,49 @@
using FreeSql.DataAnnotations;
using Newtonsoft.Json;
using System.ComponentModel;
#nullable disable
namespace Densen.Models.ids
{
/// <summary>
/// 密钥
/// </summary>
[JsonObject(MemberSerialization.OptIn), Table(DisableSyncStructure = true)]
public partial class Keys
{
[DisplayName("ID")]
[JsonProperty, Column(StringLength = -2, IsPrimary = true, IsNullable = false)]
public string Id { get; set; }
[DisplayName("版本")]
[JsonProperty]
public int Version { get; set; }
[DisplayName("创建")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string Created { get; set; }
[DisplayName("使用")]
[JsonProperty, Column(StringLength = -2)]
public string Use { get; set; }
[DisplayName("算法")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string Algorithm { get; set; }
[DisplayName("是X509证书")]
[JsonProperty]
public int IsX509Certificate { get; set; }
[DisplayName("数据保护")]
[JsonProperty]
public int DataProtected { get; set; }
[DisplayName("数据")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string Data { get; set; }
}
}

View File

@@ -0,0 +1,57 @@
using FreeSql.DataAnnotations;
using Newtonsoft.Json;
using System.ComponentModel;
#nullable disable
namespace Densen.Models.ids
{
/// <summary>
/// 持久化保存
/// </summary>
[JsonObject(MemberSerialization.OptIn), Table(DisableSyncStructure = true)]
public partial class PersistedGrants
{
[DisplayName("键值")]
[JsonProperty, Column(StringLength = -2, IsPrimary = true, IsNullable = false)]
public string Key { get; set; }
[DisplayName("类型")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string Type { get; set; }
[DisplayName("主题编号")]
[JsonProperty, Column(StringLength = -2)]
public string SubjectId { get; set; }
[DisplayName("会话编号")]
[JsonProperty, Column(StringLength = -2)]
public string SessionId { get; set; }
[DisplayName("客户编号")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string ClientId { get; set; }
[DisplayName("描述")]
[JsonProperty, Column(StringLength = -2)]
public string Description { get; set; }
[DisplayName("创建时间")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string CreationTime { get; set; }
[DisplayName("到期")]
[JsonProperty, Column(StringLength = -2)]
public string Expiration { get; set; }
[DisplayName("消耗时间")]
[JsonProperty, Column(StringLength = -2)]
public string ConsumedTime { get; set; }
[DisplayName("数据")]
[JsonProperty, Column(StringLength = -2, IsNullable = false)]
public string Data { get; set; }
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Densen.Identity.Models
{
public class WebAppIdentityUser
{
/// <summary>
/// Full name
/// </summary>
[Display(Name = "全名")]
public string? Name { get; set; }
/// <summary>
/// Birth Date
/// </summary>
[Display(Name = "生日")]
public DateTime? DOB { get; set; }
[Display(Name = "识别码")]
public string? UUID { get; set; }
[Display(Name = "外联")]
public string? provider { get; set; }
[Display(Name = "税号")]
public string? TaxNumber { get; set; }
[Display(Name = "街道地址")]
public string? Street { get; set; }
[Display(Name = "邮编")]
public string? Zip { get; set; }
[Display(Name = "县")]
public string? County { get; set; }
[Display(Name = "城市")]
public string? City { get; set; }
[Display(Name = "省份")]
public string? Province { get; set; }
[Display(Name = "国家")]
public string? Country { get; set; }
[Display(Name = "类型")]
public string? UserRole { get; set; }
}
}

Binary file not shown.

View File

@@ -1,4 +1,6 @@
using FreeSql;
using FreeSql.DataAnnotations;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@@ -26,11 +28,22 @@ public class RoleUser1 : BaseEntity<RoleUser1>
public User1 User1 { get; set; }
}
public class IdentityUser1
{
[Column(IsIdentity = true)]
public int Id { get; set; }
[MaxLength(32)]
public string Username { get; set; }
[MaxLength(64), Column(InsertValueSql = "'defaultname'")]
public string Nickname { get; set; }
}
public class User1 : BaseEntity<User1, Guid>
{
public int GroupId { get; set; }
public UserGroup Group { get; set; }
public string[] Tags { get; set; }
public virtual List<Role> Roles { get; set; }
/// <summary>
@@ -54,6 +67,17 @@ public class User1 : BaseEntity<User1, Guid>
/// <summary>
/// 描述
/// </summary>
[MaxLength(4000)]
[MaxLength(2000)]
public string Description { get; set; }
}
public class IdentityTable
{
[Column(IsIdentity = true, IsPrimary = true)]
public int id { get; set; }
public string name { get; set; }
[JsonProperty, Column(DbType = "datetime")]
public DateTime? create_time { get; set; }
}

View File

@@ -0,0 +1,104 @@
using FreeSql.DataAnnotations;
using MessagePack;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
class MessagePackMapInfo
{
public Guid id { get; set; }
[MessagePackMap]
public MessagePackMap01 Info { get; set; }
}
[MessagePackObject]
public class MessagePackMap01
{
[Key(0)]
public string name { get; set; }
[Key(1)]
public string address { get;set; }
}
namespace FreeSql.DataAnnotations
{
public class MessagePackMapAttribute : Attribute { }
}
public static class FreeSqlMessagePackMapCoreExtensions
{
internal static int _isAoped = 0;
static ConcurrentDictionary<Type, bool> _dicTypes = new ConcurrentDictionary<Type, bool>();
static MethodInfo MethodMessagePackSerializerDeserialize = typeof(MessagePackSerializer).GetMethod("Deserialize", new[] { typeof(Type), typeof(ReadOnlyMemory<byte>), typeof(MessagePackSerializerOptions), typeof(CancellationToken) });
static MethodInfo MethodMessagePackSerializerSerialize = typeof(MessagePackSerializer).GetMethod("Serialize", new[] { typeof(Type), typeof(object), typeof(MessagePackSerializerOptions), typeof(CancellationToken) });
static ConcurrentDictionary<Type, ConcurrentDictionary<string, bool>> _dicMessagePackMapFluentApi = new ConcurrentDictionary<Type, ConcurrentDictionary<string, bool>>();
static object _concurrentObj = new object();
public static ColumnFluent MessagePackMap(this ColumnFluent col)
{
_dicMessagePackMapFluentApi.GetOrAdd(col._entityType, et => new ConcurrentDictionary<string, bool>())
.GetOrAdd(col._property.Name, pn => true);
return col;
}
public static void UseMessagePackMap(this IFreeSql that)
{
UseMessagePackMap(that, MessagePackSerializerOptions.Standard);
}
public static void UseMessagePackMap(this IFreeSql that, MessagePackSerializerOptions settings)
{
if (Interlocked.CompareExchange(ref _isAoped, 1, 0) == 0)
{
FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionSwitchTypeFullName.Add((LabelTarget returnTarget, Expression valueExp, Type type) =>
{
if (_dicTypes.ContainsKey(type))
return Expression.IfThenElse(
Expression.TypeIs(valueExp, type),
Expression.Return(returnTarget, valueExp),
Expression.Return(returnTarget, Expression.TypeAs(
Expression.Call(MethodMessagePackSerializerDeserialize,
Expression.Constant(type),
Expression.New(typeof(ReadOnlyMemory<byte>).GetConstructor(new[] { typeof(byte[]) }), Expression.Convert(valueExp, typeof(byte[]))),
Expression.Constant(settings, typeof(MessagePackSerializerOptions)),
Expression.Constant(default(CancellationToken), typeof(CancellationToken)))
, type))
);
return null;
});
}
that.Aop.ConfigEntityProperty += (s, e) =>
{
var isMessagePackMap = e.Property.GetCustomAttributes(typeof(MessagePackMapAttribute), false).Any() || _dicMessagePackMapFluentApi.TryGetValue(e.EntityType, out var tryjmfu) && tryjmfu.ContainsKey(e.Property.Name);
if (isMessagePackMap)
{
e.ModifyResult.MapType = typeof(byte[]);
e.ModifyResult.StringLength = -2;
if (_dicTypes.TryAdd(e.Property.PropertyType, true))
{
lock (_concurrentObj)
{
FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple[e.Property.PropertyType] = true;
FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionObjectToBytesIfThenElse.Add((LabelTarget returnTarget, Expression valueExp, Expression elseExp, Type type) =>
{
return Expression.IfThenElse(
Expression.TypeIs(valueExp, e.Property.PropertyType),
Expression.Return(returnTarget,
Expression.Call(MethodMessagePackSerializerSerialize,
Expression.Constant(e.Property.PropertyType, typeof(Type)),
Expression.Convert(valueExp, typeof(object)),
Expression.Constant(settings, typeof(MessagePackSerializerOptions)),
Expression.Constant(default(CancellationToken), typeof(CancellationToken)))
, typeof(object)),
elseExp);
});
}
}
}
};
}
}

View File

@@ -0,0 +1,54 @@
using FreeSql;
using FreeSql.DataAnnotations;
using FreeSql.Internal;
using FreeSql.Internal.Model;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
class ModAsTableImpl : IAsTable
{
public ModAsTableImpl(IFreeSql fsql)
{
AllTables = Enumerable.Range(0, 9).Select(a => $"order_{a}").ToArray();
fsql.Aop.CommandBefore += (_, e) =>
{
e.Command.CommandText = Regex.Replace(e.Command.CommandText, @"/\*astable\([^\)]+\)*\/", "1=1");
};
}
public string[] AllTables { get; }
public string GetTableNameByColumnValue(object columnValue, bool autoExpand = false)
{
var modid = (int)columnValue;
return $"order_{(modid % 10)}";
}
public string[] GetTableNamesByColumnValueRange(object columnValue1, object columnValue2)
{
throw new NotImplementedException();
}
public IAsTableTableNameRangeResult GetTableNamesBySqlWhere(string sqlWhere, List<DbParameter> dbParams, SelectTableInfo tb, CommonUtils commonUtils)
{
var match = Regex.Match(sqlWhere, @"/\*astable\([^\)]+\)*\/");
if (match.Success == false) return new IAsTableTableNameRangeResult(AllTables, null, null);
var tables = match.Groups[1].Value.Split(',').Where(a => AllTables.Contains(a)).ToArray();
if (tables.Any() == false) return new IAsTableTableNameRangeResult(AllTables, null, null);
return new IAsTableTableNameRangeResult(tables, null, null);
}
public IAsTable SetDefaultAllTables(Func<string[], string[]> audit)
{
throw new NotImplementedException();
}
public IAsTable SetTableName(int index, string tableName)
{
throw new NotImplementedException();
}
}

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -12,21 +13,36 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Confluent.Kafka" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="MessagePack" Version="3.1.1" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Extensions\FreeSql.Extensions.AggregateRoot\FreeSql.Extensions.AggregateRoot.csproj" />
<ProjectReference Include="..\..\Extensions\FreeSql.Extensions.BaseEntity\FreeSql.Extensions.BaseEntity.csproj" />
<ProjectReference Include="..\..\Extensions\FreeSql.Extensions.JsonMap\FreeSql.Extensions.JsonMap.csproj" />
<ProjectReference Include="..\..\Extensions\FreeSql.Extensions.LazyLoading\FreeSql.Extensions.LazyLoading.csproj" />
<ProjectReference Include="..\..\Extensions\FreeSql.Extensions.Linq\FreeSql.Extensions.Linq.csproj" />
<ProjectReference Include="..\..\FreeSql.Repository\FreeSql.Repository.csproj" />
<ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.ClickHouse\FreeSql.Provider.ClickHouse.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Dameng\FreeSql.Provider.Dameng.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Firebird\FreeSql.Provider.Firebird.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.MySql\FreeSql.Provider.MySql.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Odbc\FreeSql.Provider.Odbc.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Oracle\FreeSql.Provider.Oracle.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.PostgreSQL\FreeSql.Provider.PostgreSQL.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Sqlite\FreeSql.Provider.Sqlite.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.QuestDb\FreeSql.Provider.QuestDb.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.SqliteCore\FreeSql.Provider.SqliteCore.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.SqlServer\FreeSql.Provider.SqlServer.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Xugu\FreeSql.Provider.Xugu.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="XuguClient">
<HintPath>..\..\Providers\FreeSql.Provider.Xugu\lib\XuguClient\netstandard2.0\XuguClient.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@@ -4,6 +4,77 @@
<name>base_entity</name>
</assembly>
<members>
<member name="T:Densen.Models.ids.AspNetRoleClaims">
<summary>
角色声明
</summary>
</member>
<member name="T:Densen.Models.ids.AspNetRoles">
<summary>
角色定义
</summary>
</member>
<member name="T:Densen.Models.ids.AspNetUserClaims">
<summary>
用户声明
</summary>
</member>
<member name="P:Densen.Models.ids.AspNetUserClaims.AspNetUsers">
<summary>
用户
</summary>
</member>
<member name="T:Densen.Models.ids.AspNetUserLogins">
<summary>
用户登录
</summary>
</member>
<member name="P:Densen.Models.ids.AspNetUserLogins.AspNetUsers">
<summary>
用户
</summary>
</member>
<member name="T:Densen.Models.ids.AspNetUserRoles">
<summary>
角色表
<para>存储向哪些用户分配哪些角色</para>
</summary>
</member>
<member name="T:Densen.Models.ids.AspNetUsers">
<summary>
用户表
</summary>
</member>
<member name="T:Densen.Models.ids.AspNetUserTokens">
<summary>
用户令牌
</summary>
</member>
<member name="T:Densen.Models.ids.DeviceCodes">
<summary>
设备代码
</summary>
</member>
<member name="T:Densen.Models.ids.Keys">
<summary>
密钥
</summary>
</member>
<member name="T:Densen.Models.ids.PersistedGrants">
<summary>
持久化保存
</summary>
</member>
<member name="P:Densen.Identity.Models.WebAppIdentityUser.Name">
<summary>
Full name
</summary>
</member>
<member name="P:Densen.Identity.Models.WebAppIdentityUser.DOB">
<summary>
Birth Date
</summary>
</member>
<member name="P:UserGroup.GroupName">
<summary>
组名
@@ -29,6 +100,302 @@
描述
</summary>
</member>
<member name="T:base_entity.Program.TUserImg">
<summary>
用户图片2
</summary>
</member>
<member name="P:base_entity.Program.TUserImg.Id">
<summary>
主键
</summary>
</member>
<member name="P:base_entity.Program.TUserImg.EnterpriseId">
<summary>
企业
</summary>
</member>
<member name="P:base_entity.Program.TUserImg.UserId">
<summary>
用户id
</summary>
</member>
<member name="P:base_entity.Program.TUserImg.Img">
<summary>
图片
</summary>
</member>
<member name="P:base_entity.Program.TUserImg.CId">
<summary>
创建人Id
</summary>
</member>
<member name="P:base_entity.Program.TUserImg.CName">
<summary>
创建人
</summary>
</member>
<member name="P:base_entity.Program.TUserImg.CTime">
<summary>
创建日期
</summary>
</member>
<member name="P:base_entity.Program.TUserImg.CTime2">
<summary>
创建日期2
</summary>
</member>
<member name="P:base_entity.Program.IDeleteSoft.IsDeleted">
<summary>
软删除
</summary>
</member>
<member name="P:base_entity.抖店实时销售金额表.ID">
<summary>
ID
</summary>
</member>
<member name="P:base_entity.抖店实时销售金额表.店铺名称">
<summary>
店铺名称
</summary>
</member>
<member name="P:base_entity.抖店实时销售金额表.日期">
<summary>
日期
</summary>
</member>
<member name="P:base_entity.抖店实时销售金额表.品牌名称">
<summary>
品牌名称
</summary>
</member>
<member name="P:base_entity.抖店实时销售金额表.成交金额">
<summary>
成交金额
</summary>
</member>
<member name="P:base_entity.抖店实时销售金额表.更新时间">
<summary>
更新时间
</summary>
</member>
<member name="P:base_entity.MarketingRestrictions.Id">
<summary>
主键标识
</summary>
</member>
<member name="P:base_entity.MarketingRestrictions.MchtAppId">
<summary>
商户应用Id
</summary>
</member>
<member name="P:base_entity.MarketingRestrictions.Describe">
<summary>
描述
</summary>
</member>
<member name="P:base_entity.MarketingRestrictions.Status">
<summary>
状态0、关闭 1、启用
</summary>
</member>
<member name="P:base_entity.MarketingRestrictions.IsLimitUsePoints">
<summary>
是否限制使用积分0、否 1、是
</summary>
</member>
<member name="P:base_entity.MarketingRestrictions.StartTime">
<summary>
开始时间
</summary>
</member>
<member name="P:base_entity.MarketingRestrictions.EndTime">
<summary>
结束时间
</summary>
</member>
<member name="P:base_entity.MarketingRestrictions.CreatedBy">
<summary>
创建人Id
</summary>
</member>
<member name="P:base_entity.MarketingRestrictions.CreatedTime">
<summary>
创建时间
</summary>
</member>
<member name="P:base_entity.MarketingRestrictions.UpdatedBy">
<summary>
最后编辑人Id
</summary>
</member>
<member name="P:base_entity.MarketingRestrictions.UpdatedTime">
<summary>
最后编辑时间
</summary>
</member>
<member name="P:base_entity.MarketingRestrictions.Deleted">
<summary>
是否删除0、否 1、是
</summary>
</member>
<member name="P:base_entity.MarketingRestrictions.DeletedBy">
<summary>
删除人Id
</summary>
</member>
<member name="P:base_entity.MarketingRestrictions.DeletedTime">
<summary>
删除时间
</summary>
</member>
<member name="P:base_entity.CurrentDetail.RecordDate">
<summary>
创建日期
</summary>
</member>
<member name="P:base_entity.CurrentDetail.RecordHour">
<summary>
创建小时
</summary>
</member>
<member name="P:base_entity.CurrentDetail.RecordMinute">
<summary>
根据当前分钟数规整到10分钟的倍数
例如 21分=>20分
</summary>
</member>
<member name="P:base_entity.CurrentDetail.RecordTime">
<summary>
记录时间
</summary>
</member>
<member name="P:base_entity.CurrentDetail.DeviceCode">
<summary>
设备Code
</summary>
</member>
<member name="P:base_entity.CurrentDetail.TerminalSequence">
<summary>
控制器序列号
</summary>
</member>
<member name="P:base_entity.CurrentDetail.AvgValue">
<summary>
平均值
</summary>
</member>
<member name="P:base_entity.CurrentDetail.RouteNum">
<summary>
路数
</summary>
</member>
<member name="P:base_entity.CurrentDetail.PhaseTypeId">
<summary>
相类型
</summary>
</member>
<member name="P:base_entity.ProducerModel_Kafka.Sender">
<summary>
这个可以
</summary>
</member>
<member name="P:base_entity.ProducerModel_Kafka.ID">
<summary>
ID
</summary>
</member>
<member name="P:base_entity.ProducerModel_Kafka.IP">
<summary>
IP
</summary>
</member>
<member name="P:base_entity.ProducerModel_Kafka.PConfig">
<summary>
这个不行
</summary>
</member>
<member name="P:ProjectItem.Code">
<summary>
编码
</summary>
</member>
<member name="P:ProjectItem.MaxQuantity">
<summary>
实际最大用量
</summary>
</member>
<member name="P:ProjectItem.Name">
<summary>
名称
</summary>
</member>
<member name="T:Account">
<summary>
账户
</summary>
</member>
<member name="P:MemberActionDayCountModel.MemberId">
<summary>
MemberId
</summary>
</member>
<member name="P:MemberActionDayCountModel.Date">
<summary>
日期
</summary>
</member>
<member name="P:MemberActionDayCountModel.Activity">
<summary>
所有活动
</summary>
</member>
<member name="P:MemberActionDayCountModel.OnlineActivity">
<summary>
线上活动
</summary>
</member>
<member name="P:MemberActionDayCountModel.OfflineActivity">
<summary>
线下活动
</summary>
</member>
<member name="P:MemberActionDayCountModel.Pinjianhui">
<summary>
线下活动中的品鉴会
</summary>
</member>
<member name="P:MemberActionDayCountModel.Huichangyou">
<summary>
线下活动中的回场游
</summary>
</member>
<member name="P:MemberActionDayCountModel.Form">
<summary>
所有订单
</summary>
</member>
<member name="P:MemberActionDayCountModel.FormAmount">
<summary>
所有订单金额
</summary>
</member>
<member name="P:MemberActionDayCountModel.IntegralForm">
<summary>
订单中的积分订单
</summary>
</member>
<member name="P:MemberActionDayCountModel.ScanCode">
<summary>
所有扫码
</summary>
</member>
<member name="P:MemberActionDayCountModel.ScanCodeAmount">
<summary>
所有扫码金额
</summary>
</member>
<member name="T:EMSServerModel.Model.Role">
<summary>
角色表
@@ -194,5 +561,11 @@
用户导航
</summary>
</member>
<member name="T:MessagePack.GeneratedMessagePackResolver">
<summary>A MessagePack resolver that uses generated formatters for types in this assembly.</summary>
</member>
<member name="F:MessagePack.GeneratedMessagePackResolver.Instance">
<summary>An instance of this resolver that only returns formatters specifically generated for types in this assembly.</summary>
</member>
</members>
</doc>

View File

@@ -0,0 +1,25 @@
using FreeSql.DataAnnotations;
using NetTopologySuite.Geometries;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace base_entity
{
partial class Program
{
public static void test_pgsql(IFreeSql fsql)
{
var ddl = fsql.CodeFirst.GetComparisonDDLStatements<gistIndex>();
}
}
[Index("sidx_zjds_geom", nameof(Geom), IndexMethod = IndexMethod.GiST)]
class gistIndex
{
public int bb { get; set; }
public LineString Geom { get; set; }
}
}

View File

@@ -28,6 +28,10 @@ BaseEntity 是一种极简单的 CodeFirst 开发方式,特别对单表或多
> dotnet add package FreeSql.Provider.Sqlite
```csharp
BaseEntity.Initialization(fsql, null);
```
1、定义一个主键 int 并且自增的实体类型BaseEntity TKey 指定为 int/long 时,会认为主键是自增;
```csharp

View File

@@ -17,7 +17,10 @@ namespace FreeSql.Bechmarker
public static void Main(string[] args)
{
//var summaryInsert = BenchmarkRunner.Run<OrmVsInsert>();
var summarySelect = BenchmarkRunner.Run<OrmVsSelect>();
var summarySelect = BenchmarkRunner.Run<OrmVsSelect>(new BenchmarkDotNet.Configs.DebugBuildConfig
{
});
//var summaryUpdate = BenchmarkRunner.Run<OrmVsUpdate>();
Console.ReadKey();
@@ -29,7 +32,7 @@ namespace FreeSql.Bechmarker
{
public static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.SqlServer,
"Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=20",
"Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=103;TrustServerCertificate=true;Encrypt=False",
typeof(FreeSql.SqlServer.SqlServerProvider<>))
//.UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=20")
.UseAutoSyncStructure(false)
@@ -41,7 +44,7 @@ namespace FreeSql.Bechmarker
{
get => new SqlSugarClient(new ConnectionConfig()
{
ConnectionString = "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Min Pool Size=20;Max Pool Size=20",
ConnectionString = "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=102;TrustServerCertificate=true;Encrypt=False",
DbType = DbType.SqlServer,
//ConnectionString = "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Min Pool Size=20;Max Pool Size=20",
//DbType = DbType.MySql,
@@ -55,7 +58,7 @@ namespace FreeSql.Bechmarker
public DbSet<Song> Songs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Min Pool Size=21;Max Pool Size=21");
optionsBuilder.UseSqlServer("Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=101;TrustServerCertificate=true;Encrypt=False");
//optionsBuilder.UseMySql("Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Min Pool Size=21;Max Pool Size=21");
}
}
@@ -120,7 +123,7 @@ namespace FreeSql.Bechmarker
[Benchmark]
public int DapperInsert()
{
using (var conn = new SqlConnection("Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Min Pool Size=21;Max Pool Size=31"))
using (var conn = new SqlConnection("Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=100;TrustServerCertificate=true;Encrypt=False"))
{
foreach (var song in songs)
{
@@ -166,7 +169,7 @@ values('{song.Create_time.Value.ToString("yyyy-MM-dd HH:mm:ss")}',{(song.Is_dele
[Benchmark]
public int DapperUpdate()
{
using (var conn = new SqlConnection("Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Min Pool Size=21;Max Pool Size=31"))
using (var conn = new SqlConnection("Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=100;TrustServerCertificate=true;Encrypt=False"))
{
foreach (var song in songs)
{
@@ -198,7 +201,7 @@ where id = {song.Id}");
{
db.Songs.Take(1).AsNoTracking().ToList();
}
using (var conn = new SqlConnection("Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Min Pool Size=21;Max Pool Size=31"))
using (var conn = new SqlConnection("Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=100;TrustServerCertificate=true;Encrypt=False"))
{
Dapper.SqlMapper.Query<Song>(conn, $"select top {1} Id,Create_time,Is_deleted,Title,Url from dapper_song").ToList();
}
@@ -235,7 +238,7 @@ where id = {song.Id}");
db.Songs.AddRange(songs);
db.SaveChanges();
}
using (var conn = new SqlConnection("Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Min Pool Size=21;Max Pool Size=31"))
using (var conn = new SqlConnection("Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=100;TrustServerCertificate=true;Encrypt=False"))
{
foreach (var song in songs)
{
@@ -263,7 +266,7 @@ values('{song.Create_time.Value.ToString("yyyy-MM-dd HH:mm:ss")}',{(song.Is_dele
[Benchmark]
public List<Song> DapperSelete()
{
using (var conn = new SqlConnection("Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Min Pool Size=21;Max Pool Size=31"))
using (var conn = new SqlConnection("Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=100;TrustServerCertificate=true;Encrypt=False"))
{
return Dapper.SqlMapper.Query<Song>(conn, $"select top {size} Id,Create_time,Is_deleted,Title,Url from dapper_song").ToList();
}

View File

@@ -2,15 +2,15 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="Dapper" Version="2.0.78" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.0" />
<PackageReference Include="SqlSugarCore" Version="5.0.2.9" />
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
<PackageReference Include="sqlSugarCore" Version="5.1.3.38" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FreeSql;
@@ -12,9 +11,9 @@ namespace dbcontext_01.Controllers
public class ValuesController : ControllerBase
{
IFreeSql _orm;
SongContext _songContext;
CurdAfterLog _curdLog;
private readonly IFreeSql _orm;
private readonly SongContext _songContext;
private readonly CurdAfterLog _curdLog;
public ValuesController(SongContext songContext, IFreeSql orm1, CurdAfterLog curdLog)
{
_songContext = songContext;
@@ -25,10 +24,12 @@ namespace dbcontext_01.Controllers
// GET api/values
[HttpGet]
async public Task<string> Get()
public async Task<string> Get()
{
_orm.SetDbContextOptions(opt => {
opt.OnEntityChange = changeReport => {
_orm.SetDbContextOptions(opt =>
{
opt.OnEntityChange = changeReport =>
{
Console.WriteLine(changeReport);
};
});
@@ -136,7 +137,7 @@ namespace dbcontext_01.Controllers
using (var uow = _orm.CreateUnitOfWork())
{
var reposSong = uow.GetRepository<Song, int>();
reposSong.Where(a => a.Id > 10).ToList();
//查询结果,进入 states

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
@@ -12,7 +12,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,3 +1,4 @@
using efcore_to_freesql.Entitys;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
@@ -14,6 +15,15 @@ namespace efcore_to_freesql.DBContexts
{
base.OnModelCreating(modelBuilder);
Fsql.CodeFirst.ConfigEntity(modelBuilder.Model); //ͬ<><CDAC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
//<2F><><EFBFBD>õ<EFBFBD><C3B5><EFBFBD>
Fsql.CodeFirst.ApplyConfiguration(new SongConfiguration());
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
//Fsql.CodeFirst.ApplyConfigurationsFromAssembly(typeof(SongConfiguration).Assembly);
Fsql.CodeFirst.SyncStructure<Song>();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{

View File

@@ -1,5 +1,6 @@
using efcore_to_freesql.Entitys;
using FreeSql;
using FreeSql.Extensions.EfCoreFluentApi;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using System;
@@ -10,7 +11,7 @@ using System.Threading.Tasks;
public static class CodeFirstExtensions
{
public static void ConfigEntity(this ICodeFirst codeFirst, IModel efmodel)
public static void ConfigEntity(this ICodeFirst codeFirst, IMutableModel efmodel)
{
foreach (var type in efmodel.GetEntityTypes())
@@ -178,4 +179,27 @@ public static class CodeFirstExtensions
cf.SyncStructure<SongType>();
cf.SyncStructure<Song>();
}
}
}
public class SongConfiguration : FreeSql.Extensions.EfCoreFluentApi.IEntityTypeConfiguration<Song>
{
public void Configure(EfCoreTableFluent<Song> eb)
{
eb.ToTable("tb_song_config");
eb.Ignore(a => a.Field1);
eb.Property(a => a.Title).HasColumnType("varchar(50)").IsRequired();
eb.Property(a => a.Url).HasMaxLength(100);
eb.Property(a => a.RowVersion).IsRowVersion();
eb.Property(a => a.CreateTime).HasDefaultValueSql("current_timestamp");
eb.HasKey(a => a.Id);
eb.HasIndex(a => a.Title).IsUnique().HasName("idx_tb_song1111");
//一对多、多对一
eb.HasOne(a => a.Type).HasForeignKey(a => a.TypeId).WithMany(a => a.Songs);
//多对多
eb.HasMany(a => a.Tags).WithMany(a => a.Songs, typeof(Song_tag));
}
}

View File

@@ -28,7 +28,7 @@ namespace efcore_to_freesql
.Build();
//Fsql.CodeFirst.EfCoreFluentApiTestGeneric();
Fsql.CodeFirst.EfCoreFluentApiTestDynamic();
//Fsql.CodeFirst.EfCoreFluentApiTestDynamic();
BaseDBContext.Fsql = Fsql;

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,4 +1,6 @@
using FreeSql.Internal;
using Dapper;
using FreeSql.Internal;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using SqlSugar;
using System;
@@ -12,57 +14,158 @@ using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using FreeSql;
using FreeSql.Internal.CommonProvider;
namespace orm_vs
{
class Program
{
static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
//.UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=20")
.UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=20")
//.UseConnectionString(FreeSql.DataType.PostgreSQL, "Host=192.168.164.10;Port=5432;Username=postgres;Password=123456;Database=tedb;Pooling=true;Maximum Pool Size=20")
.UseAutoSyncStructure(false)
.UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Integrated Security=True;Initial Catalog=tedb1;Pooling=true;Max Pool Size=21;TrustServerCertificate=true")
//.UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=21;AllowLoadLocalInfile=true;")
//.UseConnectionString(FreeSql.DataType.PostgreSQL, "Host=127.0.0.1;Port=5432;Username=postgres;Password=123456;Database=tedb;Pooling=true;Maximum Pool Size=21")
.UseAutoSyncStructure(false)
.UseNoneCommandParameter(true)
//.UseConfigEntityFromDbFirst(true)
.Build();
static SqlSugarClient sugar
{
get => new SqlSugarClient(new ConnectionConfig()
get
{
//ConnectionString = "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Min Pool Size=20;Max Pool Size=20",
//DbType = DbType.SqlServer,
ConnectionString = "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Min Pool Size=20;Max Pool Size=20",
DbType = DbType.MySql,
//ConnectionString = "Host=192.168.164.10;Port=5432;Username=postgres;Password=123456;Database=tedb;Pooling=true;Maximum Pool Size=21",
//DbType = DbType.PostgreSQL,
IsAutoCloseConnection = true,
InitKeyType = InitKeyType.Attribute
});
var db = new SqlSugarClient(new ConnectionConfig()
{
ConnectionString = "Data Source=.;Integrated Security=True;Initial Catalog=tedb1;Pooling=true;Min Pool Size=20;Max Pool Size=20;TrustServerCertificate=true",
DbType = DbType.SqlServer,
//ConnectionString = "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Min Pool Size=20;Max Pool Size=20;AllowLoadLocalInfile=true;",
//DbType = DbType.MySql,
//ConnectionString = "Host=127.0.0.1;Port=5432;Username=postgres;Password=123456;Database=tedb;Pooling=true;Maximum Pool Size=20",
//DbType = DbType.PostgreSQL,
IsAutoCloseConnection = true,
InitKeyType = InitKeyType.Attribute
});
//db.Aop.OnLogExecuting = (sql, pars) =>
//{
// Console.WriteLine(sql);//输出sql,查看执行sql
//};
return db;
}
}
class SongContext : DbContext
{
public DbSet<Song> Songs { get; set; }
public DbSet<PatientExamination_2022> PatientExamination_2022s { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
//optionsBuilder.UseSqlServer(@"Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Min Pool Size=21;Max Pool Size=21");
optionsBuilder.UseMySql("Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Min Pool Size=21;Max Pool Size=21");
//optionsBuilder.UseNpgsql("Host=192.168.164.10;Port=5432;Username=postgres;Password=123456;Database=tedb;Pooling=true;Maximum Pool Size=21");
}
optionsBuilder.UseSqlServer(@"Data Source=.;Integrated Security=True;Initial Catalog=tedb1;Pooling=true;Min Pool Size=19;Max Pool Size=19;TrustServerCertificate=true");
//var connectionString = "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Min Pool Size=19;Max Pool Size=19";
//optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
//optionsBuilder.UseNpgsql("Host=127.0.0.1;Port=5432;Username=postgres;Password=123456;Database=tedb;Pooling=true;Maximum Pool Size=19");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Song>()
.Property(a => a.create_time)
.HasConversion(a => int.Parse(a.ToString()), a => new DateTime(a));
}
}
//CREATE TABLE [dbo].[PatientExamination_2022] (
// [Id] uniqueidentifier NOT NULL,
// [CreateTime] datetime NOT NULL,
// [ExamKindId] uniqueidentifier NOT NULL,
// [ExamKindName] nvarchar(255) COLLATE Chinese_PRC_CI_AS NULL,
// [PatientGuid] uniqueidentifier NOT NULL,
// [PatientName] nvarchar(255) COLLATE Chinese_PRC_CI_AS NULL,
// [AnesthesiaType] int NOT NULL,
// [DiaRoomId] uniqueidentifier NULL,
// [DiaRoomName] nvarchar(255) COLLATE Chinese_PRC_CI_AS NULL,
// [QueueIndex] int NOT NULL,
// [QueueNum] int NOT NULL,
// [OrderDateTime] datetime NOT NULL,
// [TimeType] int NOT NULL,
// [SignInTime] datetime NULL,
// [StartCheckTime] datetime NULL,
// [EndCheckTime] datetime NULL,
// [VerifyTime] datetime NULL,
// [ReportTime] datetime NULL,
// [ExaminationState] int NOT NULL
//)
[Table("PatientExamination_2022")]
class PatientExamination_2022
{
public Guid Id { get; set; }
public DateTime CreateTime { get; set; }
public Guid ExamKindId { get; set; }
public string ExamKindName { get; set; }
public Guid PatientGuid { get; set; }
public string PatientName { get; set; }
public int AnesthesiaType { get; set; }
public Guid? DiaRoomId { get; set; }
public string DiaRoomName { get; set; }
public int QueueIndex { get; set; }
public int QueueNum { get; set; }
public DateTime OrderDateTime { get; set; }
public int TimeType { get; set; }
public DateTime? SignInTime { get; set; }
public DateTime? StartCheckTime { get; set; }
public DateTime? EndCheckTime { get; set; }
public DateTime? VerifyTime { get; set; }
public DateTime? ReportTime { get; set; }
public int ExaminationState { get; set; }
}
static void TestFreeSqlSelectPatientExamination_2022()
{
var list = fsql.Select<PatientExamination_2022>().Limit(40000).ToList();
//var list = fsql.Ado.Query<PatientExamination_2022>("select top 40000 * from PatientExamination_2022");
}
static void TestEfSelectPatientExamination_2022()
{
using (var ctx = new SongContext())
{
var list = ctx.PatientExamination_2022s.Take(40000).AsNoTracking().ToList();
}
}
static void TestSqlSugarSelectPatientExamination_2022()
{
var list = sugar.Queryable<PatientExamination_2022>().Take(40000).ToList();
}
static void TestDapperSelectPatientExamination_2022()
{
using (var conn = new SqlConnection("Data Source=.;Integrated Security=True;Initial Catalog=tedb1;Pooling=true;Min Pool Size=21;Max Pool Size=22"))
{
var list = conn.Query<PatientExamination_2022>("select top 40000 * from PatientExamination_2022");
}
}
static DbConnection fsqlConn = null;
static void Main(string[] args)
{
//var count = 0;
//var sws = new List<long>();
//Console.WriteLine("观察查询4万条记录内存按 Enter 进入下一次,按任易键即出程序。。。");
////while(Console.ReadKey().Key == ConsoleKey.Enter)
////using (var fcon = fsql.Ado.MasterPool.Get())
////{
// //fsqlConn = fcon.Value;
// for (var a = 0; a < 80; a++)
// {
// Stopwatch sw = Stopwatch.StartNew();
// TestFreeSqlSelectPatientExamination_2022();
// //TestEfSelectPatientExamination_2022();
// //TestSqlSugarSelectPatientExamination_2022();
// //TestDapperSelectPatientExamination_2022();
// sw.Stop();
// sws.Add(sw.ElapsedMilliseconds);
// Console.WriteLine($"第 {++count} 次查询4万条记录, {sw.ElapsedMilliseconds}ms平均 {(long)sws.Average()}ms");
// }
////}
//Console.ReadKey();
//fsql.Dispose();
//return;
//fsql.CodeFirst.SyncStructure(typeof(Song), typeof(Song_tag), typeof(Tag));
//sugar.CodeFirst.InitTables(typeof(Song), typeof(Song_tag), typeof(Tag));
//sugar创建表失败SqlSugar.SqlSugarException: Sequence contains no elements
@@ -81,7 +184,6 @@ namespace orm_vs
var sb = new StringBuilder();
var time = new Stopwatch();
var sql222 = fsql.Select<Song>().Where(a => DateTime.Now.Subtract(a.create_time.Value).TotalHours > 0).ToSql();
#region ET test
////var t31 = fsql.Select<xxx>().ToList();
@@ -372,23 +474,10 @@ namespace orm_vs
#endregion
var testlist1 = fsql.Select<Song>().OrderBy(a => a.id).ToList();
var testlist2 = new List<Song>();
fsql.Select<Song>().OrderBy(a => a.id).ToChunk(2, fetch =>
sugar.Aop.OnLogExecuted = (s, e) =>
{
testlist2.AddRange(fetch.Object);
});
var testlist22 = new List<object>();
fsql.Select<Song, Song_tag>().LeftJoin((a, b) => a.id == b.song_id).ToChunk((a, b) => new { a.title, a.create_time, b.tag_id }, 2, fetch =>
{
testlist22.AddRange(fetch.Object);
});
//sugar.Aop.OnLogExecuted = (s, e) =>
//{
// Trace.WriteLine(s);
//};
Trace.WriteLine(s);
};
//测试前清空数据
fsql.Delete<Song>().Where(a => a.id > 0).ExecuteAffrows();
sugar.Deleteable<Song>().Where(a => a.id > 0).ExecuteCommand();
@@ -437,10 +526,10 @@ namespace orm_vs
sb.Clear();
Console.WriteLine("更新:");
Update(sb, 100, 1);
Update(sb, 10, 1);
Console.Write(sb.ToString());
sb.Clear();
Update(sb, 100, 10);
Update(sb, 10, 10);
Console.Write(sb.ToString());
sb.Clear();
@@ -487,21 +576,21 @@ namespace orm_vs
sw.Stop();
sb.AppendLine($"EFCore Select {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms .net5.0无效");
sw.Restart();
using (var conn = fsql.Ado.MasterPool.Get())
{
for (var a = 0; a < forTime; a++)
Dapper.SqlMapper.Query<Song>(conn.Value, $"select top {size} * from freesql_song").ToList();
}
sw.Stop();
sb.AppendLine($"Dapper Select {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms\r\n");
//sw.Restart();
//using (var conn = fsql.Ado.MasterPool.Get())
//{
// for (var a = 0; a < forTime; a++)
// Dapper.SqlMapper.Query<Song>(conn.Value, $"select * from freesql_song limit {size}").ToList();
//}
//sw.Stop();
//sb.AppendLine($"Dapper Select {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms\r\n");
}
static void Insert(StringBuilder sb, int forTime, int size)
{
var songs = Enumerable.Range(0, size).Select(a => new Song
{
create_time = DateTime.Now,
create_time = DateTime.Now.ToString(),
is_deleted = false,
title = $"Insert_{a}",
url = $"Url_{a}"
@@ -536,6 +625,7 @@ namespace orm_vs
try
{
for (var a = 0; a < forTime; a++)
//sugar.Fastest<Song>().BulkCopy(songs.ToList());
sugar.Insertable(songs.ToArray()).ExecuteCommand();
}
catch (Exception ex)
@@ -571,7 +661,61 @@ namespace orm_vs
fsql.Update<Song>().SetSource(songs).ExecuteAffrows();
}
sw.Stop();
sb.AppendLine($"FreeSql Update {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms");
sb.AppendLine($"FreeSql Update1 {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms");
songs = fsql.Select<Song>().Limit(size).ToList();
sw.Restart();
for (var a = 0; a < forTime; a++)
{
fsql.Update<Song>().SetSource(songs).ExecuteSqlBulkCopy();
}
sw.Stop();
sb.AppendLine($"FreeSql BulkCopyUpdate {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms");
// songs = fsql.Select<Song>().Limit(size).ToList();
// sw.Restart();
// for (var a = 0; a < forTime; a++)
// {
// //fsql.Update<Song>().SetSource(songs).ExecuteAffrows();
// var iou = fsql.InsertOrUpdate<Song>() as InsertOrUpdateProvider<Song>;
// var dbsql = new StringBuilder();
// var dbparms = new List<DbParameter>();
// iou.WriteSourceSelectUnionAll(songs, dbsql, dbparms);
// var sql = $@"update freesql_song a
//inner join ( {dbsql} ) b on b.id = a.id
//set a.create_time = b.create_time, a.is_deleted = b.is_deleted, a.title = b.title, a.url = b.url";
// fsql.Ado.ExecuteNonQuery(System.Data.CommandType.Text, sql, dbparms.ToArray());
// }
// sw.Stop();
// sb.AppendLine($"FreeSql Update2(update inner join) {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms");
// songs = fsql.Select<Song>().Limit(size).ToList();
// sw.Restart();
// for (var a = 0; a < forTime; a++)
// {
// var isdroped = false;
// var tempTableName = $"#Temp_freesql_song";
// fsql.Ado.ExecuteNonQuery($"select * into {tempTableName} from [freesql_song] where 1=2");
// try
// {
// fsql.Insert(songs).AsTable(tempTableName).ExecuteMySqlBulkCopy();
// var sql = $@"update freesql_song a
//inner join {tempTableName} b on b.id = a.id;
//set a.create_time = b.create_time, a.is_deleted = b.is_deleted, a.title = b.title, a.url = b.url
//; drop table {tempTableName}; ";
// fsql.Ado.ExecuteNonQuery(System.Data.CommandType.Text, sql);
// isdroped = true;
// }
// finally
// {
// if (isdroped == false)
// fsql.Ado.ExecuteNonQuery($"drop table {tempTableName}");
// }
// }
// sw.Stop();
// sb.AppendLine($"FreeSql Update3(update inner join #temp) {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms");
songs = sugar.Queryable<Song>().Take(size).ToList();
sw.Restart();
@@ -579,7 +723,7 @@ namespace orm_vs
try
{
for (var a = 0; a < forTime; a++)
sugar.Updateable(songs).ExecuteCommand();
sugar.Fastest<Song>().BulkUpdate(songs);
}
catch (Exception ex)
{
@@ -588,21 +732,21 @@ namespace orm_vs
sw.Stop();
sb.AppendLine($"SqlSugar Update {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms" + (sugarEx != null ? $"成绩无效,错误:{sugarEx.Message}" : ""));
using (var db = new SongContext())
{
songs = db.Songs.Take(size).AsNoTracking().ToList();
}
sw.Restart();
for (var a = 0; a < forTime; a++)
{
//using (var db = new SongContext())
//{
// songs = db.Songs.Take(size).AsNoTracking().ToList();
//}
//sw.Restart();
//for (var a = 0; a < forTime; a++)
//{
using (var db = new SongContext())
{
//db.Configuration.AutoDetectChangesEnabled = false;
//db.Songs.UpdateRange(songs.ToArray());
//db.SaveChanges();
}
}
// using (var db = new SongContext())
// {
// //db.Configuration.AutoDetectChangesEnabled = false;
// //db.Songs.UpdateRange(songs.ToArray());
// //db.SaveChanges();
// }
//}
sw.Stop();
sb.AppendLine($"EFCore Update {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms .net5.0无效\r\n");
}
@@ -618,7 +762,7 @@ namespace orm_vs
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id { get; set; }
public DateTime? create_time { get; set; }
public string create_time { get; set; }
public bool? is_deleted { get; set; }
public string title { get; set; }
public string url { get; set; }

View File

@@ -2,28 +2,20 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.0.35" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.0" />
<PackageReference Include="sqlSugarCore" Version="5.0.1.2" />
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
<PackageReference Include="sqlSugarCore" Version="5.1.3.38" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.MySql\FreeSql.Provider.MySql.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.MySqlConnector\FreeSql.Provider.MySqlConnector.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.PostgreSQL\FreeSql.Provider.PostgreSQL.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.SqlServer\FreeSql.Provider.SqlServer.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.EntityFrameworkCore">
<HintPath>..\..\..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.entityframeworkcore\2.2.0\lib\netstandard2.0\Microsoft.EntityFrameworkCore.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@@ -14,6 +14,7 @@ namespace orm_vs
static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=20")
//.UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=20")
.UseConnectionString(FreeSql.DataType.Sqlite, "data source=test1.db;max pool size=5")
.UseAutoSyncStructure(false)
.UseNoneCommandParameter(true)
//.UseConfigEntityFromDbFirst(true)

View File

@@ -53,6 +53,10 @@
<Project>{28c6a39c-7ae7-4210-b7b0-0970216637a8}</Project>
<Name>FreeSql.Provider.MySql</Name>
</ProjectReference>
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Sqlite\FreeSql.Provider.Sqlite.csproj">
<Project>{559b6369-1868-4a06-a590-f80ba7b80a1b}</Project>
<Name>FreeSql.Provider.Sqlite</Name>
</ProjectReference>
<ProjectReference Include="..\..\Providers\FreeSql.Provider.SqlServer\FreeSql.Provider.SqlServer.csproj">
<Project>{b61aac9e-59e9-4f47-bbe3-97ac24112efe}</Project>
<Name>FreeSql.Provider.SqlServer</Name>
@@ -63,7 +67,7 @@
<Version>1.50.2</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>12.0.3</Version>
<Version>13.0.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

View File

@@ -10,7 +10,7 @@ using System.Threading.Tasks;
namespace restful.Controllers
{
public class SongRepository : GuidRepository<Song>
public class SongRepository : BaseRepository<Song>
{
public SongRepository(IFreeSql fsql) : base(fsql)
{
@@ -43,11 +43,11 @@ namespace restful.Controllers
Console.Write(reposTest.Select.ToSql());
_songRepository = repos4;
//test code
var curd1 = fsql.GetRepository<Song, int>();
var curd2 = fsql.GetRepository<Song, string>();
var curd3 = fsql.GetRepository<Song, Guid>();
var curd4 = fsql.GetGuidRepository<Song>();
Console.WriteLine(reposSong.Select.ToSql());

View File

@@ -59,12 +59,7 @@ namespace repository_01
services.AddControllersWithViews();
services.AddSingleton<IFreeSql>(Fsql);
services.AddFreeRepository(filter =>
{
filter
//.Apply<Song>("test", a => a.Title == DateTime.Now.ToString() + System.Threading.Thread.CurrentThread.ManagedThreadId)
.Apply<ISoftDelete>("softdelete", a => a.IsDeleted == false);
}, this.GetType().Assembly);
services.AddFreeRepository(this.GetType().Assembly);
}
public void Configure(IApplicationBuilder app)

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,68 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public static class JObjectExtensions
{
/// <summary>
/// 将JObject转化成字典
/// </summary>
/// <param name="json"></param>
/// <returns></returns>
public static Dictionary<string, object> ToDictionary(this JToken json)
{
var propertyValuePairs = json.ToObject<Dictionary<string, object>>();
ProcessJObjectProperties(propertyValuePairs);
ProcessJArrayProperties(propertyValuePairs);
return propertyValuePairs;
}
private static void ProcessJObjectProperties(Dictionary<string, object> propertyValuePairs)
{
var objectPropertyNames = (from property in propertyValuePairs
let propertyName = property.Key
let value = property.Value
where value is JObject
select propertyName).ToList();
objectPropertyNames.ForEach(propertyName => propertyValuePairs[propertyName] = ToDictionary((JObject)propertyValuePairs[propertyName]));
}
private static void ProcessJArrayProperties(Dictionary<string, object> propertyValuePairs)
{
var arrayPropertyNames = (from property in propertyValuePairs
let propertyName = property.Key
let value = property.Value
where value is JArray
select propertyName).ToList();
arrayPropertyNames.ForEach(propertyName => propertyValuePairs[propertyName] = ToArray((JArray)propertyValuePairs[propertyName]));
}
/// <summary>
///
/// </summary>
/// <param name="array"></param>
/// <returns></returns>
public static object[] ToArray(this JArray array)
{
return array.ToObject<object[]>().Select(ProcessArrayEntry).ToArray();
}
private static object ProcessArrayEntry(object value)
{
if (value is JObject)
{
return ToDictionary((JObject)value);
}
if (value is JArray)
{
return ToArray((JArray)value);
}
return value;
}
}

View File

@@ -0,0 +1,305 @@

using FreeSql;
using FreeSql.DataAnnotations;
using FreeSql.Extensions.ZeroEntity;
using FreeSql.Internal.Model;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Data;
using System.Text.Json;
using (var fsql = new FreeSqlBuilder()
.UseConnectionString(DataType.Sqlite, "data source=111.db")
.UseConnectionString(DataType.MySql, "Server=47.108.219.26;Port=3306;Database=sample;Uid=root;Pwd=xdhl123Qwe;")
.UseAutoSyncStructure(true)
.UseNoneCommandParameter(true)
.UseMonitorCommand(cmd => Console.WriteLine(cmd.CommandText + "\r\n"))
.Build())
{
var schemas = JsonConvert.DeserializeObject<TableDescriptor[]>(File.ReadAllText(@"C:\Users\28810\Downloads\schema.json"));
var context = new ZeroDbContext(fsql);
context.LoadSchemasAndNavigates("Student", tableName => schemas.First(c => c.Name == tableName));
//context.SyncStructure();
var stu = context.Select.Where("id", 38).First();
var json = JsonConvert.SerializeObject(Helper.GetTestDesc());
var dyctx = new ZeroDbContext(fsql, JsonConvert.DeserializeObject<TableDescriptor[]>(@"
[
{
""Name"":""User"",
""Comment"":""用户表"",
""Columns"": [
{""Name"":""Id"",""IsPrimary"":true,""IsIdentity"":true,""MapType"":""System.Int32""},
{""Name"":""Name"",""MapType"":""System.String""}
],
""Navigates"":[
{""Name"":""Ext"",""Type"":""OneToOne"",""RelTable"":""UserExt""},
{""Name"":""Claims"",""Type"":""OneToMany"",""RelTable"":""UserClaim"",""Bind"":""UserId""},
{""Name"":""Roles"",""Type"":""ManyToMany"",""RelTable"":""Role"",""ManyToMany"":""UserRole""}
],
""Indexes"":[]
},
{
""Name"":""UserExt"",
""Comment"":""用户扩展信息表"",
""Columns"":[
{""Name"":""UserId"",""IsPrimary"":true,""MapType"":""System.Int32""},
],
""Navigates"":[
{""Name"":""Remarks"",""Type"":""OneToMany"",""RelTable"":""UserExtRemarks"",""Bind"":""UserId""},
],
},
{
""Name"":""UserExtRemarks"",
""Comment"":""用户扩展信息表-子表"",
""Columns"":[
{""Name"":""RemarkId"",""IsPrimary"":true,""MapType"":""System.Guid""},
{""Name"":""UserId"",""MapType"":""System.Int32""},
{""Name"":""Remark"",""MapType"":""System.String""},
],
},
{
""Name"":""UserClaim"",
""Comment"":""一对多测试表"",
""Columns"":[
{""Name"":""Id"",""IsPrimary"":true,""IsIdentity"":true,""MapType"":""System.Int32""},
{""Name"":""UserId"",""MapType"":""System.Int32""},
{""Name"":""ClaimName"",""MapType"":""System.String""},
],
},
{
""Name"":""Role"",
""Comment"":""权限表"",
""Columns"":[
{""Name"":""Id"",""IsPrimary"":true,""IsIdentity"":true,""MapType"":""System.Int32""},
{""Name"":""Name"",""MapType"":""System.String""}
],
""Navigates"":[
{""Name"":""Users"",""Type"":""ManyToMany"",""RelTable"":""User"",""ManyToMany"":""UserRole""}
],
""Indexes"":[]
},
{
""Name"":""UserRole"",
""Comment"":""多对多中间表"",
""Columns"":[
{""Name"":""UserId"",""IsPrimary"":true,""MapType"":""System.Int32""},
{""Name"":""RoleId"",""IsPrimary"":true,""MapType"":""System.Int32""}
],
""Navigates"":[
{""Name"":""User"",""Type"":""ManyToOne"",""RelTable"":""User"",""Bind"":""UserId""},
{""Name"":""Role"",""Type"":""ManyToOne"",""RelTable"":""Role"",""Bind"":""RoleId""}
]
}
]
"));
var dyrt3 = dyctx.SelectNoTracking("User")
.Include("Ext.Remarks", then => then.Where("remark", "like", "error"))
.Include("Roles", then => then.Include("Users",
then => then.Include("Ext.Remarks")))
.ToList();
var dyrt2 = dyctx.SelectNoTracking("User")
.LeftJoin("UserExt", "UserId", "User.Id")
.LeftJoin("UserExt", "UserId", "User.Id")
//.IncludeAll()
.WhereExists(q => q.From("UserClaim")
.WhereColumns("userid", "=", "user.id")
.WhereExists(q2 => q2.From("User")
.WhereColumns("id", "=", "UserClaim.userid")))
.ToList();
var dyrt1 = dyctx.Select.ToList();
dyctx.Delete(dyrt1);
var itemJson = JsonConvert.SerializeObject(new Dictionary<string, object>
{
["Name"] = "user1",
["Ext"] = new Dictionary<string, object>
{
},
["Claims"] = new List<Dictionary<string, object>>
{
new Dictionary<string, object>
{
["ClaimName"] = "claim1"
},
new Dictionary<string, object>
{
["ClaimName"] = "claim2"
},
new Dictionary<string, object>
{
["ClaimName"] = "claim3"
},
},
["Roles"] = new List<Dictionary<string, object>>
{
new Dictionary<string, object>
{
["Name"] = "role1"
},
new Dictionary<string, object>
{
["Name"] = "role2"
},
},
});
var item = JsonConvert.DeserializeObject<Dictionary<string, object>>(@"
{
""Name"":""user1"",
""Ext"":{
""Remarks"":[{""Remark"":""remark1""},{""Remark"":""remark2""}]
},
""Claims"":[{""ClaimName"":""claim1""},{""ClaimName"":""claim2""},{""ClaimName"":""claim3""}],
""Roles"":[{""Name"":""role1""},{""Name"":""role2""}]
}");
var item2 = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(@"
{
""Name"":""user1"",
""Ext"":{
""Remarks"":[{""Remark"":""remark1""},{""Remark"":""remark2""}]
},
""Claims"":[{""ClaimName"":""claim1""},{""ClaimName"":""claim2""},{""ClaimName"":""claim3""}],
""Roles"":[{""Name"":""role1""},{""Name"":""role2""}]
}");
dyctx.Insert(item2);
var item3 = JsonConvert.DeserializeObject<Dictionary<string, object>>(@"
{
""Id"":1,
""Name"":""user1111"",
""Ext"":{},
""Claims"":[{""Id"":1,""ClaimName"":""claim1111""},{""Id"":""3"",""ClaimName"":""claim3222222""},{""ClaimName"":""claim0000""}],
""Roles"":[{""Name"":""role111100001""},{""Id"":2,""Name"":""role2""}]
}");
item3["Id"] = item2["Id"];
dyctx.Update(item3);
dyctx.Delete(item3);
Task.Run(async () =>
{
var users = await fsql.Select<User>().IncludeMany(a => a.Roles).ToListAsync();
}).Wait();
}
[Table(DisableSyncStructure = true)]
class userdto
{
public int Id { get; set; }
public bool? isdeleted { get; set; }
}
class User
{
[Column(IsIdentity = true)]
public int Id { get; set; } //Id、UserId、User_id
public string Name { get; set; }
public UserExt Ext { get; set; }
[Navigate(nameof(UserClaim.UserId))]
public List<UserClaim> Claims { get; set; }
public List<Role> Roles { get; set; }
}
class UserExt
{
public int UserId { get; set; }
public User User { get; set; }
}
class UserClaim
{
[Column(IsIdentity = true)]
public int Id { get; set; }
public int UserId { get; set; }
public string ClaimName { get; set; }
}
class Role
{
[Column(IsIdentity = true)]
public int Id { get; set; }
public string Name { get; set; }
public List<User> Users { get; set; }
}
class UserRole
{
public int UserId { get; set; }
public User User { get; set; }
public int RoleId { get; set; }
public Role Role { get; set; }
}
static class Helper
{
public static TableDescriptor[] GetTestDesc()
{
return new[]
{
new TableDescriptor
{
Name = "User",
Columns =
{
new TableDescriptor.ColumnDescriptor{ Name = "Id", MapType = typeof(int), IsPrimary = true, IsIdentity = true },
new TableDescriptor.ColumnDescriptor{ Name = "Name", MapType = typeof(string), StringLength = 100 },
},
Navigates =
{
new TableDescriptor.NavigateDescriptor { Name = "Ext", RelTable = "UserExt", Type = TableDescriptor.NavigateType.OneToOne },
new TableDescriptor.NavigateDescriptor { Name = "Roles", RelTable = "Role", Type = TableDescriptor.NavigateType.ManyToMany, ManyToMany = "UserRole" },
}
},
new TableDescriptor
{
Name = "UserExt",
Columns =
{
new TableDescriptor.ColumnDescriptor{ Name = "UserId", MapType = typeof(int), IsPrimary = true },
new TableDescriptor.ColumnDescriptor{ Name = "Name", MapType = typeof(string), StringLength = 100 },
},
Navigates =
{
new TableDescriptor.NavigateDescriptor { Name = "User", RelTable = "User", Type = TableDescriptor.NavigateType.OneToOne },
}
},
new TableDescriptor
{
Name = "Role",
Columns =
{
new TableDescriptor.ColumnDescriptor{ Name = "Id", MapType = typeof(int), IsPrimary = true, IsIdentity = true },
new TableDescriptor.ColumnDescriptor{ Name = "Name", MapType = typeof(string), StringLength = 50 },
},
Navigates =
{
new TableDescriptor.NavigateDescriptor { Name = "Users", RelTable = "User", Type = TableDescriptor.NavigateType.ManyToMany, ManyToMany = "UserRole" },
}
},
new TableDescriptor
{
Name = "UserRole",
Columns =
{
new TableDescriptor.ColumnDescriptor{ Name = "UserId", MapType = typeof(int), IsPrimary = true },
new TableDescriptor.ColumnDescriptor{ Name = "RoleId", MapType = typeof(int), IsPrimary = true },
},
Navigates =
{
new TableDescriptor.NavigateDescriptor { Name = "User", RelTable = "User", Type = TableDescriptor.NavigateType.ManyToOne, Bind = "UserId" },
new TableDescriptor.NavigateDescriptor { Name = "Role", RelTable = "Role", Type = TableDescriptor.NavigateType.ManyToOne, Bind = "RoleId" },
}
},
};
}
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Extensions\FreeSql.Extensions.ZeroEntity\FreeSql.Extensions.ZeroEntity.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.MySql\FreeSql.Provider.MySql.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Sqlite\FreeSql.Provider.Sqlite.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,34 @@
using System;
using System.Linq;
namespace FreeSql.DataAnnotations
{
/// <summary>
/// 设置 AggregateRootRepository 边界范围<para></para>
/// 在边界范围之内的规则 <para></para>
/// 1、OneToOne/OneToMany/ManyToMany(中间表) 可以查询、可以增删改<para></para>
/// 2、ManyToOne/ManyToMany外部表/PgArrayToMany 只可以查询,不支持增删改(会被忽略)<para></para>
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class AggregateRootBoundaryAttribute : Attribute
{
public string Name { get; set; }
/// <summary>
/// 边界是否终止
/// </summary>
public bool Break { get; set; }
/// <summary>
/// 边界是否终止向下探测
/// </summary>
public bool BreakThen { get; set; }
public AggregateRootBoundaryAttribute(string name)
{
this.Name = name;
}
public AggregateRootBoundaryAttribute()
{
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
namespace FreeSql.Internal.Model
{
public class AggregateRootTrackingChangeInfo
{
public List<NativeTuple<Type, object>> InsertLog { get; } = new List<NativeTuple<Type, object>>();
public List<NativeTuple<Type, object, object, List<string>>> UpdateLog { get; } = new List<NativeTuple<Type, object, object, List<string>>>();
public List<NativeTuple<Type, object[]>> DeleteLog { get; } = new List<NativeTuple<Type, object[]>>();
}
}

View File

@@ -0,0 +1,274 @@
using FreeSql.Extensions.EntityUtil;
using FreeSql.Internal.Model;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace FreeSql
{
public interface IAggregateRootRepository<TEntity>: IBaseRepository<TEntity> where TEntity : class
{
IBaseRepository<TEntity> ChangeBoundary(string name);
}
public partial class AggregateRootRepository<TEntity> : IAggregateRootRepository<TEntity> where TEntity : class
{
readonly IBaseRepository<TEntity> _repository;
public AggregateRootRepository(IFreeSql fsql)
{
if (fsql == null) throw new ArgumentNullException(nameof(fsql));
_repository = fsql.GetRepository<TEntity>();
_repository.DbContextOptions.EnableCascadeSave = false;
}
public AggregateRootRepository(IFreeSql fsql, UnitOfWorkManager uowManager) : this(uowManager?.Orm ?? fsql)
{
uowManager?.Binding(_repository);
}
public void Dispose()
{
DisposeChildRepositorys();
_repository.FlushState();
_repository.Dispose();
FlushState();
}
string _boundaryName = "";
public IBaseRepository<TEntity> ChangeBoundary(string name)
{
DisposeChildRepositorys();
_repository.FlushState();
FlushState();
_boundaryName = string.Concat(name).Trim();
return this;
}
public IFreeSql Orm => _repository.Orm;
public IUnitOfWork UnitOfWork { get => _repository.UnitOfWork; set => _repository.UnitOfWork = value; }
public DbContextOptions DbContextOptions
{
get => _repository.DbContextOptions;
set
{
if (value == null) throw new ArgumentNullException(nameof(DbContextOptions));
_repository.DbContextOptions = value;
_repository.DbContextOptions.EnableCascadeSave = false;
}
}
public RepositoryDataFilter DataFilter => _repository.DataFilter;
public void AsType(Type entityType) => _repository.AsType(entityType);
Func<Type, string, string> _asTableRule;
public void AsTable(Func<string, string> rule)
{
_repository.AsTable(rule);
if (rule == null)
{
_asTableRule = null;
return;
}
_asTableRule = (t, old) => t == EntityType ? rule(old) : null;
}
public void AsTable(Func<Type, string, string> rule)
{
_repository.AsTable(rule);
_asTableRule = rule;
}
public Type EntityType => _repository.EntityType;
public void Attach(TEntity entity)
{
var state = CreateEntityState(entity);
if (_states.ContainsKey(state.Key))
_states[state.Key] = state;
else
_states.Add(state.Key, state);
}
public void Attach(IEnumerable<TEntity> entity)
{
foreach (var item in entity)
Attach(item);
}
public IBaseRepository<TEntity> AttachOnlyPrimary(TEntity data) => _repository.AttachOnlyPrimary(data);
public Dictionary<string, object[]> CompareState(TEntity newdata)
{
if (newdata == null) return null;
var _table = Orm.CodeFirst.GetTableByEntity(EntityType);
if (_table.Primarys.Any() == false) throw new Exception(DbContextErrorStrings.Incomparable_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, newdata)));
var key = Orm.GetEntityKeyString(EntityType, newdata, false);
if (string.IsNullOrEmpty(key)) throw new Exception(DbContextErrorStrings.Incomparable_PrimaryKey_NotSet(Orm.GetEntityString(EntityType, newdata)));
if (_states.TryGetValue(key, out var oldState) == false || oldState == null) throw new Exception($"不可对比,数据未被跟踪:{Orm.GetEntityString(EntityType, newdata)}");
AggregateRootTrackingChangeInfo tracking = new AggregateRootTrackingChangeInfo();
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, oldState.Value, newdata, null, tracking);
var result = new Dictionary<string, object[]>();
if (tracking.InsertLog.Any()) result.Add("Insert", tracking.InsertLog.Select(a => new object[] { a.Item1, a.Item2 }).ToArray());
if (tracking.DeleteLog.Any()) result.Add("Delete", tracking.DeleteLog.Select(a => new object[] { a.Item1, a.Item2 }).ToArray());
if (tracking.UpdateLog.Any()) result.Add("Update", tracking.UpdateLog.Select(a => new object[] { a.Item1, a.Item2, a.Item3, a.Item4 }).ToArray());
return result;
}
public void FlushState()
{
DisposeChildRepositorys();
_repository.FlushState();
_states.Clear();
}
public IUpdate<TEntity> UpdateDiy => _repository.UpdateDiy;
public ISelect<TEntity> Where(Expression<Func<TEntity, bool>> exp) => Select.Where(exp);
public ISelect<TEntity> WhereIf(bool condition, Expression<Func<TEntity, bool>> exp) => Select.WhereIf(condition, exp);
readonly Dictionary<Type, IBaseRepository<object>> _childRepositorys = new Dictionary<Type, IBaseRepository<object>>();
protected virtual IFreeSql GetChildFreeSql(Type type) => Orm;
IBaseRepository<object> GetChildRepository(Type type)
{
if (_childRepositorys.TryGetValue(type, out var repo) == false)
{
repo = GetChildFreeSql(type).GetRepository<object>();
repo.AsType(type);
_childRepositorys.Add(type, repo);
}
repo.UnitOfWork = UnitOfWork;
repo.DbContextOptions = DbContextOptions;
repo.DbContextOptions.EnableCascadeSave = false;
(repo as BaseRepository<object>).DataFilter = DataFilter;
repo.AsTable(_asTableRule);
return repo;
}
void DisposeChildRepositorys()
{
foreach (var repo in _childRepositorys.Values)
{
repo.FlushState();
repo.Dispose();
}
_childRepositorys.Clear();
}
#region
protected Dictionary<string, EntityState> _states = new Dictionary<string, EntityState>();
protected class EntityState
{
public EntityState(TEntity value, string key)
{
this.Value = value;
this.Key = key;
this.Time = DateTime.Now;
}
public TEntity OldValue { get; set; }
public TEntity Value { get; set; }
public string Key { get; set; }
public DateTime Time { get; set; }
}
EntityState CreateEntityState(TEntity data)
{
if (data == null) throw new ArgumentNullException(nameof(data));
var key = Orm.GetEntityKeyString(EntityType, data, false);
var state = new EntityState((TEntity)EntityType.CreateInstanceGetDefaultValue(), key);
AggregateRootUtils.MapEntityValue(_boundaryName, Orm, EntityType, data, state.Value);
return state;
}
bool? ExistsInStates(object data)
{
if (data == null) throw new ArgumentNullException(nameof(data));
var key = Orm.GetEntityKeyString(EntityType, data, false);
if (string.IsNullOrEmpty(key)) return null;
return _states.ContainsKey(key);
}
#endregion
#region
/// <summary>
/// 默认:创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性)<para></para>
/// 重写:使用
/// </summary>
public virtual ISelect<TEntity> Select => SelectAggregateRoot;
/// <summary>
/// 创建查询对象(纯净)<para></para>
/// _<para></para>
/// 聚合根内关系较复杂时,获取 Include/IncludeMany 字符串代码,方便二次开发<para></para>
/// string code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(null, fsql, typeof(Order))
/// </summary>
protected ISelect<TEntity> SelectDiy => _repository.Select.TrackToList(SelectAggregateRootTracking);
/// <summary>
/// 创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性)
/// </summary>
/// <returns></returns>
protected ISelect<TEntity> SelectAggregateRoot
{
get
{
var query = _repository.Select.TrackToList(SelectAggregateRootTracking);
query = AggregateRootUtils.GetAutoIncludeQuery(_boundaryName, query);
return query;
}
}
/// <summary>
/// ISelect.TrackToList 委托,数据返回后自动 Attach
/// </summary>
/// <param name="list"></param>
protected void SelectAggregateRootTracking(object list)
{
if (list == null) return;
if (list is IEnumerable<TEntity> entities)
{
Attach(entities);
return;
}
var ie = list as IEnumerable;
if (ie == null) return;
var isfirst = true;
foreach (var item in ie)
{
if (item == null) continue;
if (isfirst)
{
isfirst = false;
var itemType = item.GetType();
if (itemType == typeof(object)) return;
if (itemType.FullName.Contains("FreeSqlLazyEntity__")) itemType = itemType.BaseType;
if (Orm.CodeFirst.GetTableByEntity(itemType)?.Primarys.Any() != true) return;
if (itemType.GetConstructor(Type.EmptyTypes) == null) return;
}
if (item is TEntity item2) Attach(item2);
else return;
}
}
//void SelectAggregateRootNavigateReader<T1>(ISelect<T1> currentQuery, Type entityType, string navigatePath, Stack<Type> ignores)
//{
// if (ignores.Any(a => a == entityType)) return;
// ignores.Push(entityType);
// var table = Orm.CodeFirst.GetTableByEntity(entityType);
// if (table == null) return;
// if (!string.IsNullOrWhiteSpace(navigatePath)) navigatePath = $"{navigatePath}.";
// foreach (var tr in table.GetAllTableRef())
// {
// var tbref = tr.Value;
// if (tbref.Exception != null) continue;
// var navigateExpression = $"{navigatePath}{tr.Key}";
// switch (tbref.RefType)
// {
// case TableRefType.OneToOne:
// if (ignores.Any(a => a == tbref.RefEntityType)) break;
// currentQuery.IncludeByPropertyName(navigateExpression);
// SelectAggregateRootNavigateReader(currentQuery, tbref.RefEntityType, navigateExpression, ignores);
// break;
// case TableRefType.OneToMany:
// var ignoresCopy = new Stack<Type>(ignores.ToArray());
// currentQuery.IncludeByPropertyName(navigateExpression, then =>
// SelectAggregateRootNavigateReader(then, tbref.RefEntityType, "", ignoresCopy)); //variable 'then' of type 'FreeSql.ISelect`1[System.Object]' referenced from scope '', but it is not defined
// break;
// case TableRefType.ManyToMany:
// currentQuery.IncludeByPropertyName(navigateExpression);
// break;
// case TableRefType.PgArrayToMany:
// break;
// case TableRefType.ManyToOne:
// break;
// }
// }
// ignores.Pop();
//}
#endregion
}
}

View File

@@ -0,0 +1,322 @@
#if net40
#else
using FreeSql.Extensions.EntityUtil;
using FreeSql.Internal;
using FreeSql.Internal.Model;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace FreeSql
{
partial class AggregateRootRepository<TEntity>
{
#region InsertAsync
async public virtual Task<TEntity> InsertAsync(TEntity entity, CancellationToken cancellationToken = default) => (await InsertAsync(new[] { entity }, cancellationToken)).FirstOrDefault();
async public virtual Task<List<TEntity>> InsertAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default)
{
var repos = new Dictionary<Type, object>();
try
{
var ret = await InsertWithinBoundaryStaticAsync(_boundaryName, _repository, GetChildRepository, entitys, null, cancellationToken);
Attach(ret);
return ret;
}
finally
{
DisposeChildRepositorys();
_repository.FlushState();
}
}
async Task<List<T1>> InsertWithinBoundaryStaticAsync<T1>(string boundaryName, IBaseRepository<T1> rootRepository, Func<Type, IBaseRepository<object>> getChildRepository, IEnumerable<T1> rootEntitys, int[] affrows, CancellationToken cancellationToken) where T1 : class
{
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
Dictionary<Type, IBaseRepository<object>> repos = new Dictionary<Type, IBaseRepository<object>>();
var localAffrows = 0;
try
{
rootRepository.DbContextOptions.EnableCascadeSave = false;
return await LocalInsertAsync(rootRepository, rootEntitys, true);
}
finally
{
if (affrows != null) affrows[0] = localAffrows;
}
bool LocalCanInsert(Type entityType, object entity, bool isadd)
{
var stateKey = rootRepository.Orm.GetEntityKeyString(entityType, entity, false);
if (string.IsNullOrEmpty(stateKey)) return true;
if (ignores.TryGetValue(entityType, out var stateKeys) == false)
{
if (isadd)
{
ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
stateKeys.Add(stateKey, true);
}
return true;
}
if (stateKeys.ContainsKey(stateKey) == false)
{
if (isadd) stateKeys.Add(stateKey, true);
return true;
}
return false;
}
async Task<List<T2>> LocalInsertAsync<T2>(IBaseRepository<T2> repository, IEnumerable<T2> entitys, bool cascade) where T2 : class
{
var table = repository.Orm.CodeFirst.GetTableByEntity(repository.EntityType);
if (table.Primarys.Any(col => col.Attribute.IsIdentity))
{
foreach (var entity in entitys)
repository.Orm.ClearEntityPrimaryValueWithIdentity(repository.EntityType, entity);
}
var ret = await repository.InsertAsync(entitys, cancellationToken);
localAffrows += ret.Count;
foreach (var entity in entitys) LocalCanInsert(repository.EntityType, entity, true);
if (cascade == false) return ret;
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
{
var tbref = tr.Value;
if (tbref.Exception != null) continue;
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
var boundaryAttr = AggregateRootUtils.GetPropertyBoundaryAttribute(prop, boundaryName);
if (boundaryAttr?.Break == true) continue;
switch (tbref.RefType)
{
case TableRefType.OneToOne:
var otoList = ret.Select(entity =>
{
var otoItem = table.GetPropertyValue(entity, prop.Name);
if (LocalCanInsert(tbref.RefEntityType, otoItem, false) == false) return null;
AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otoItem);
return otoItem;
}).Where(entity => entity != null).ToArray();
if (otoList.Any())
{
var repo = getChildRepository(tbref.RefEntityType);
await LocalInsertAsync(repo, otoList, boundaryAttr?.BreakThen != true);
}
break;
case TableRefType.OneToMany:
var otmList = ret.Select(entity =>
{
var otmEach = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
if (otmEach == null) return null;
var otmItems = new List<object>();
foreach (var otmItem in otmEach)
{
if (LocalCanInsert(tbref.RefEntityType, otmItem, false) == false) continue;
otmItems.Add(otmItem);
}
AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otmItems);
return otmItems;
}).Where(entity => entity != null).SelectMany(entity => entity).ToArray();
if (otmList.Any())
{
var repo = getChildRepository(tbref.RefEntityType);
await LocalInsertAsync(repo, otmList, boundaryAttr?.BreakThen != true);
}
break;
case TableRefType.ManyToMany:
var mtmMidList = new List<object>();
ret.ForEach(entity =>
{
var mids = AggregateRootUtils.GetManyToManyObjects(repository.Orm, table, tbref, entity, prop);
if (mids != null) mtmMidList.AddRange(mids);
});
if (mtmMidList.Any())
{
var repo = getChildRepository(tbref.RefMiddleEntityType);
await LocalInsertAsync(repo, mtmMidList, false);
}
break;
case TableRefType.PgArrayToMany:
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
break;
}
}
return ret;
}
}
#endregion
async public virtual Task<TEntity> InsertOrUpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
{
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
if (entity == null) throw new ArgumentNullException(nameof(entity));
var table = Orm.CodeFirst.GetTableByEntity(EntityType);
if (table.Primarys.Any() == false) throw new Exception(DbContextErrorStrings.CannotAdd_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, entity)));
var flagExists = ExistsInStates(entity);
if (flagExists == false)
{
var olddata = await Select.WhereDynamic(entity).FirstAsync(cancellationToken);
flagExists = olddata != null;
}
if (flagExists == true)
{
var affrows = await UpdateAsync(entity, cancellationToken);
return entity;
}
if (table.Primarys.Where(a => a.Attribute.IsIdentity).Count() == table.Primarys.Length)
Orm.ClearEntityPrimaryValueWithIdentity(EntityType, entity);
return await InsertAsync(entity, cancellationToken);
}
public virtual Task<int> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => UpdateAsync(new[] { entity }, cancellationToken);
async public virtual Task<int> UpdateAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default)
{
var tracking = new AggregateRootTrackingChangeInfo();
foreach (var entity in entitys)
{
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以更新数据 {Orm.GetEntityString(EntityType, entity)}");
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, null, tracking);
}
var affrows = await SaveTrackingChangeAsync(tracking, cancellationToken);
foreach (var entity in entitys)
Attach(entity);
return affrows;
}
public virtual Task<int> DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) => DeleteWithinBoundaryAsync(new[] { entity }, null, cancellationToken);
public virtual Task<int> DeleteAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default) => DeleteWithinBoundaryAsync(entitys, null, cancellationToken);
async public virtual Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) => await DeleteWithinBoundaryAsync(await SelectAggregateRoot.Where(predicate).ToListAsync(cancellationToken), null, cancellationToken);
async public virtual Task<List<object>> DeleteCascadeByDatabaseAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
{
var deletedOutput = new List<object>();
await DeleteWithinBoundaryAsync(await SelectAggregateRoot.Where(predicate).ToListAsync(cancellationToken), deletedOutput, cancellationToken);
return deletedOutput;
}
async Task<int> DeleteWithinBoundaryAsync(IEnumerable<TEntity> entitys, List<object> deletedOutput, CancellationToken cancellationToken)
{
var tracking = new AggregateRootTrackingChangeInfo();
foreach (var entity in entitys)
{
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, entity, null, null, tracking);
_states.Remove(stateKey);
}
var affrows = 0;
for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
{
if (tracking.DeleteLog[a].Item2.Any() == false) continue;
var delete = Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1);
if (_asTableRule != null) delete.AsTable(old => _asTableRule(tracking.DeleteLog[a].Item1, old));
affrows += await delete.WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrowsAsync(cancellationToken);
if (deletedOutput != null) deletedOutput.AddRange(tracking.DeleteLog[a].Item2);
UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
new DbContext.EntityChangeReport.ChangeInfo
{
Type = DbContext.EntityChangeType.Delete,
EntityType = tracking.DeleteLog[a].Item1,
Object = x
}));
}
return affrows;
}
async public virtual Task SaveManyAsync(TEntity entity, string propertyName, CancellationToken cancellationToken = default)
{
var tracking = new AggregateRootTrackingChangeInfo();
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以保存数据 {Orm.GetEntityString(EntityType, entity)}");
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, propertyName, tracking);
await SaveTrackingChangeAsync(tracking, cancellationToken);
Attach(entity); //应该只存储 propertyName 内容
}
async Task<int> SaveTrackingChangeAsync(AggregateRootTrackingChangeInfo tracking, CancellationToken cancellationToken)
{
var affrows = 0;
DisposeChildRepositorys();
var insertLogDict = tracking.InsertLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.InsertLog.Where(b => b.Item1 == a.Key).Select(b => b.Item2).ToArray());
foreach (var il in insertLogDict)
{
var repo = GetChildRepository(il.Key);
var affrowsOut = new int[1];
await InsertWithinBoundaryStaticAsync(_boundaryName, repo, GetChildRepository, il.Value, affrowsOut, cancellationToken);
affrows += affrowsOut[0];
}
for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
{
if (tracking.DeleteLog[a].Item2.Any() == false) continue;
var delete = Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1);
if (_asTableRule != null) delete.AsTable(old => _asTableRule(tracking.DeleteLog[a].Item1, old));
affrows += await delete.WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrowsAsync(cancellationToken);
UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
new DbContext.EntityChangeReport.ChangeInfo
{
Type = DbContext.EntityChangeType.Delete,
EntityType = tracking.DeleteLog[a].Item1,
Object = x
}));
}
if (_repository.DbContextOptions.AuditValue != null)
{
foreach (var log in tracking.UpdateLog)
{
var table = Orm.CodeFirst.GetTableByEntity(log.Item1);
_repository.DbContextOptions.AuditValue(new DbContextAuditValueEventArgs(Aop.AuditValueType.Update, log.Item1, log.Item3));
log.Item4.Clear();
foreach (var col in table.ColumnsByCs.Values)
{
if (table.ColumnsByCsIgnore.ContainsKey(col.CsName)) continue;
if (table.ColumnsByCs.ContainsKey(col.CsName))
{
if (col.Attribute.IsVersion) continue;
var propvalBefore = table.GetPropertyValue(log.Item2, col.CsName);
var propvalAfter = table.GetPropertyValue(log.Item3, col.CsName);
if (AggregateRootUtils.CompareEntityPropertyValue(col.CsType, propvalBefore, propvalAfter) == false) log.Item4.Add(col.CsName);
continue;
}
}
}
}
var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key && b.Item4.Any()).Select(b => new
{
BeforeObject = b.Item2,
AfterObject = b.Item3,
UpdateColumns = b.Item4,
UpdateColumnsString = string.Join(",", b.Item4.OrderBy(c => c))
}).ToArray());
var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a =>
a.Value.GroupBy(b => b.UpdateColumnsString).ToDictionary(b => b.Key, b => a.Value.Where(c => c.UpdateColumnsString == b.Key).ToArray()));
foreach (var dl in updateLogDict2)
{
foreach (var dl2 in dl.Value)
{
var update = Orm.Update<object>().AsType(dl.Key);
if (_asTableRule != null) update.AsTable(old => _asTableRule(dl.Key, old));
affrows += await update
.SetSource(dl2.Value.Select(a => a.AfterObject).ToArray())
.UpdateColumns(dl2.Value.First().UpdateColumns.ToArray())
.ExecuteAffrowsAsync(cancellationToken);
UnitOfWork?.EntityChangeReport?.Report.AddRange(dl2.Value.Select(x =>
new DbContext.EntityChangeReport.ChangeInfo
{
Type = DbContext.EntityChangeType.Update,
EntityType = dl.Key,
Object = x.AfterObject,
BeforeObject = x.BeforeObject
}));
}
}
DisposeChildRepositorys();
return affrows;
}
}
}
#endif

View File

@@ -0,0 +1,371 @@
using FreeSql.Extensions.EntityUtil;
using FreeSql.Internal;
using FreeSql.Internal.Model;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace FreeSql
{
partial class AggregateRootRepository<TEntity>
{
public virtual void SaveMany(TEntity entity, string propertyName)
{
var tracking = new AggregateRootTrackingChangeInfo();
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以保存数据 {Orm.GetEntityString(EntityType, entity)}");
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, propertyName, tracking);
SaveTrackingChange(tracking);
Attach(entity); //应该只存储 propertyName 内容
}
#region BeginEdit/EndEdit
List<TEntity> _dataEditing;
ConcurrentDictionary<string, EntityState> _statesEditing = new ConcurrentDictionary<string, EntityState>();
public virtual void BeginEdit(List<TEntity> data)
{
if (data == null) return;
var table = Orm.CodeFirst.GetTableByEntity(EntityType);
if (table.Primarys.Any() == false) throw new Exception(DbContextErrorStrings.CannotEdit_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, data.First())));
_statesEditing.Clear();
_dataEditing = data;
foreach (var item in data)
{
var key = Orm.GetEntityKeyString(EntityType, item, false);
if (string.IsNullOrEmpty(key)) continue;
_statesEditing.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) =>
{
AggregateRootUtils.MapEntityValue(_boundaryName, Orm, EntityType, item, ov.Value);
ov.Time = DateTime.Now;
return ov;
});
}
}
public virtual int EndEdit(List<TEntity> data = null)
{
if (data == null) data = _dataEditing;
if (data == null) return 0;
var tracking = new AggregateRootTrackingChangeInfo();
try
{
var addList = new List<TEntity>();
var ediList = new List<TEntity>();
foreach (var item in data)
{
var key = Orm.GetEntityKeyString(EntityType, item, false);
if (_statesEditing.TryRemove(key, out var state) == false)
{
tracking.InsertLog.Add(NativeTuple.Create(EntityType, (object)item));
continue;
}
_states[key] = state;
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, item, null, tracking);
}
foreach (var item in _statesEditing.Values.OrderBy(a => a.Time))
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, item, null, null, tracking);
return SaveTrackingChange(tracking);
}
finally
{
_dataEditing = null;
_statesEditing.Clear();
}
}
#endregion
#region Insert
public virtual TEntity Insert(TEntity entity) => Insert(new[] { entity }).FirstOrDefault();
public virtual List<TEntity> Insert(IEnumerable<TEntity> entitys)
{
var repos = new Dictionary<Type, object>();
try
{
var ret = InsertWithinBoundaryStatic(_boundaryName, _repository, GetChildRepository, entitys, out var affrows);
Attach(ret);
return ret;
}
finally
{
DisposeChildRepositorys();
_repository.FlushState();
}
}
static List<T1> InsertWithinBoundaryStatic<T1>(string boundaryName, IBaseRepository<T1> rootRepository, Func<Type, IBaseRepository<object>> getChildRepository, IEnumerable<T1> rootEntitys, out int affrows) where T1 : class {
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
Dictionary<Type, IBaseRepository<object>> repos = new Dictionary<Type, IBaseRepository<object>>();
var localAffrows = 0;
try
{
rootRepository.DbContextOptions.EnableCascadeSave = false;
return LocalInsert(rootRepository, rootEntitys, true);
}
finally
{
affrows = localAffrows;
}
bool LocalCanInsert(Type entityType, object entity, bool isadd)
{
var stateKey = rootRepository.Orm.GetEntityKeyString(entityType, entity, false);
if (string.IsNullOrEmpty(stateKey)) return true;
if (ignores.TryGetValue(entityType, out var stateKeys) == false)
{
if (isadd)
{
ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
stateKeys.Add(stateKey, true);
}
return true;
}
if (stateKeys.ContainsKey(stateKey) == false)
{
if (isadd) stateKeys.Add(stateKey, true);
return true;
}
return false;
}
List<T2> LocalInsert<T2>(IBaseRepository<T2> repository, IEnumerable<T2> entitys, bool cascade) where T2 : class
{
var table = repository.Orm.CodeFirst.GetTableByEntity(repository.EntityType);
if (table.Primarys.Any(col => col.Attribute.IsIdentity))
{
foreach (var entity in entitys)
repository.Orm.ClearEntityPrimaryValueWithIdentity(repository.EntityType, entity);
}
var ret = repository.Insert(entitys);
localAffrows += ret.Count;
foreach (var entity in entitys) LocalCanInsert(repository.EntityType, entity, true);
if (cascade == false) return ret;
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
{
var tbref = tr.Value;
if (tbref.Exception != null) continue;
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
var boundaryAttr = AggregateRootUtils.GetPropertyBoundaryAttribute(prop, boundaryName);
if (boundaryAttr?.Break == true) continue;
switch (tbref.RefType)
{
case TableRefType.OneToOne:
var otoList = ret.Select(entity =>
{
var otoItem = table.GetPropertyValue(entity, prop.Name);
if (LocalCanInsert(tbref.RefEntityType, otoItem, false) == false) return null;
AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otoItem);
return otoItem;
}).Where(entity => entity != null).ToArray();
if (otoList.Any())
{
var repo = getChildRepository(tbref.RefEntityType);
LocalInsert(repo, otoList, boundaryAttr?.BreakThen != true);
}
break;
case TableRefType.OneToMany:
var otmList = ret.Select(entity =>
{
var otmEach = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
if (otmEach == null) return null;
var otmItems = new List<object>();
foreach (var otmItem in otmEach)
{
if (LocalCanInsert(tbref.RefEntityType, otmItem, false) == false) continue;
otmItems.Add(otmItem);
}
AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otmItems);
return otmItems;
}).Where(entity => entity != null).SelectMany(entity => entity).ToArray();
if (otmList.Any())
{
var repo = getChildRepository(tbref.RefEntityType);
LocalInsert(repo, otmList, boundaryAttr?.BreakThen != true);
}
break;
case TableRefType.ManyToMany:
var mtmMidList = new List<object>();
ret.ForEach(entity =>
{
var mids = AggregateRootUtils.GetManyToManyObjects(repository.Orm, table, tbref, entity, prop);
if (mids != null) mtmMidList.AddRange(mids);
});
if (mtmMidList.Any())
{
var repo = getChildRepository(tbref.RefMiddleEntityType);
LocalInsert(repo, mtmMidList, false);
}
break;
case TableRefType.PgArrayToMany:
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
break;
}
}
return ret;
}
}
#endregion
public virtual TEntity InsertOrUpdate(TEntity entity)
{
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
if (entity == null) throw new ArgumentNullException(nameof(entity));
var table = Orm.CodeFirst.GetTableByEntity(EntityType);
if (table.Primarys.Any() == false) throw new Exception(DbContextErrorStrings.CannotAdd_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, entity)));
var flagExists = ExistsInStates(entity);
if (flagExists == false)
{
var olddata = Select.WhereDynamic(entity).First();
flagExists = olddata != null;
}
if (flagExists == true)
{
var affrows = Update(entity);
return entity;
}
if (table.Primarys.Where(a => a.Attribute.IsIdentity).Count() == table.Primarys.Length)
Orm.ClearEntityPrimaryValueWithIdentity(EntityType, entity);
return Insert(entity);
}
public virtual int Update(TEntity entity) => Update(new[] { entity });
public virtual int Update(IEnumerable<TEntity> entitys)
{
var tracking = new AggregateRootTrackingChangeInfo();
foreach(var entity in entitys)
{
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以更新数据 {Orm.GetEntityString(EntityType, entity)}");
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, null, tracking);
}
var affrows = SaveTrackingChange(tracking);
foreach (var entity in entitys)
Attach(entity);
return affrows;
}
public virtual int Delete(TEntity entity) => DeleteWithinBoundary(new[] { entity }, null);
public virtual int Delete(IEnumerable<TEntity> entitys) => DeleteWithinBoundary(entitys, null);
public virtual int Delete(Expression<Func<TEntity, bool>> predicate) => DeleteWithinBoundary(SelectAggregateRoot.Where(predicate).ToList(), null);
public virtual List<object> DeleteCascadeByDatabase(Expression<Func<TEntity, bool>> predicate)
{
var deletedOutput = new List<object>();
DeleteWithinBoundary(SelectAggregateRoot.Where(predicate).ToList(), deletedOutput);
return deletedOutput;
}
int DeleteWithinBoundary(IEnumerable<TEntity> entitys, List<object> deletedOutput)
{
var tracking = new AggregateRootTrackingChangeInfo();
foreach (var entity in entitys)
{
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, entity, null, null, tracking);
_states.Remove(stateKey);
}
var affrows = 0;
for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
{
if (tracking.DeleteLog[a].Item2.Any() == false) continue;
var delete = Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1);
if (_asTableRule != null) delete.AsTable(old => _asTableRule(tracking.DeleteLog[a].Item1, old));
affrows += delete.WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrows();
if (deletedOutput != null) deletedOutput.AddRange(tracking.DeleteLog[a].Item2);
UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
new DbContext.EntityChangeReport.ChangeInfo
{
Type = DbContext.EntityChangeType.Delete,
EntityType = tracking.DeleteLog[a].Item1,
Object = x
}));
}
return affrows;
}
int SaveTrackingChange(AggregateRootTrackingChangeInfo tracking)
{
var affrows = 0;
DisposeChildRepositorys();
var insertLogDict = tracking.InsertLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.InsertLog.Where(b => b.Item1 == a.Key).Select(b => b.Item2).ToArray());
foreach (var il in insertLogDict)
{
var repo = GetChildRepository(il.Key);
InsertWithinBoundaryStatic(_boundaryName, repo, GetChildRepository, il.Value, out var affrowsOut);
affrows += affrowsOut;
}
for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
{
if (tracking.DeleteLog[a].Item2.Any() == false) continue;
var delete = Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1);
if (_asTableRule != null) delete.AsTable(old => _asTableRule(tracking.DeleteLog[a].Item1, old));
affrows += delete.WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrows();
UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
new DbContext.EntityChangeReport.ChangeInfo
{
Type = DbContext.EntityChangeType.Delete,
EntityType = tracking.DeleteLog[a].Item1,
Object = x
}));
}
if (_repository.DbContextOptions.AuditValue != null)
{
foreach (var log in tracking.UpdateLog)
{
var table = Orm.CodeFirst.GetTableByEntity(log.Item1);
_repository.DbContextOptions.AuditValue(new DbContextAuditValueEventArgs(Aop.AuditValueType.Update, log.Item1, log.Item3));
log.Item4.Clear();
foreach (var col in table.ColumnsByCs.Values)
{
if (table.ColumnsByCsIgnore.ContainsKey(col.CsName)) continue;
if (table.ColumnsByCs.ContainsKey(col.CsName))
{
if (col.Attribute.IsVersion) continue;
var propvalBefore = table.GetPropertyValue(log.Item2, col.CsName);
var propvalAfter = table.GetPropertyValue(log.Item3, col.CsName);
if (AggregateRootUtils.CompareEntityPropertyValue(col.CsType, propvalBefore, propvalAfter) == false) log.Item4.Add(col.CsName);
continue;
}
}
}
}
var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key && b.Item4.Any()).Select(b => new
{
BeforeObject = b.Item2,
AfterObject = b.Item3,
UpdateColumns = b.Item4,
UpdateColumnsString = string.Join(",", b.Item4.OrderBy(c => c))
}).ToArray());
var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a =>
a.Value.GroupBy(b => b.UpdateColumnsString).ToDictionary(b => b.Key, b => a.Value.Where(c => c.UpdateColumnsString == b.Key).ToArray()));
foreach (var dl in updateLogDict2)
{
foreach (var dl2 in dl.Value)
{
var update = Orm.Update<object>().AsType(dl.Key);
if (_asTableRule != null) update.AsTable(old => _asTableRule(dl.Key, old));
affrows += update
.SetSource(dl2.Value.Select(a => a.AfterObject).ToArray())
.UpdateColumns(dl2.Value.First().UpdateColumns.ToArray())
.ExecuteAffrows();
UnitOfWork?.EntityChangeReport?.Report.AddRange(dl2.Value.Select(x =>
new DbContext.EntityChangeReport.ChangeInfo
{
Type = DbContext.EntityChangeType.Update,
EntityType = dl.Key,
Object = x.AfterObject,
BeforeObject = x.BeforeObject
}));
}
}
DisposeChildRepositorys();
return affrows;
}
}
}

View File

@@ -0,0 +1,675 @@
using FreeSql;
using FreeSql.DataAnnotations;
using FreeSql.Extensions.EntityUtil;
using FreeSql.Internal;
using FreeSql.Internal.CommonProvider;
using FreeSql.Internal.Model;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
namespace FreeSql
{
public class AggregateRootUtils
{
static ConcurrentDictionary<PropertyInfo, ConcurrentDictionary<string, AggregateRootBoundaryAttribute>> _dicGetPropertyBoundaryAttribute = new ConcurrentDictionary<PropertyInfo, ConcurrentDictionary<string, AggregateRootBoundaryAttribute>>();
public static AggregateRootBoundaryAttribute GetPropertyBoundaryAttribute(PropertyInfo prop, string boundaryName)
{
if (boundaryName == null) return null;
return _dicGetPropertyBoundaryAttribute.GetOrAdd(prop, tp => new ConcurrentDictionary<string, AggregateRootBoundaryAttribute>())
.GetOrAdd(boundaryName, bn =>
{
var attrs = prop.GetCustomAttributes(typeof(AggregateRootBoundaryAttribute), false);
if (attrs == null || attrs.Any() == false) return null;
return attrs.Select(a => a as AggregateRootBoundaryAttribute).Where(a => a.Name == bn).FirstOrDefault();
});
}
public static void CompareEntityValue(string boundaryName, IFreeSql fsql, Type rootEntityType, object rootEntityBefore, object rootEntityAfter, string rootNavigatePropertyName, AggregateRootTrackingChangeInfo tracking)
{
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
LocalCompareEntityValue(rootEntityType, rootEntityBefore, rootEntityAfter, rootNavigatePropertyName, true);
ignores.Clear();
void LocalCompareEntityValue(Type entityType, object entityBefore, object entityAfter, string navigatePropertyName, bool cascade)
{
if (entityType == null) entityType = entityBefore?.GetType() ?? entityAfter?.GetType();
if (entityBefore != null)
{
var stateKey = $":before://{fsql.GetEntityKeyString(entityType, entityBefore, false)}";
if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
if (stateKeys.ContainsKey(stateKey)) return;
stateKeys.Add(stateKey, true);
}
if (entityAfter != null)
{
var stateKey = $":after://{fsql.GetEntityKeyString(entityType, entityAfter, false)}";
if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
if (stateKeys.ContainsKey(stateKey)) return;
stateKeys.Add(stateKey, true);
}
var table = fsql.CodeFirst.GetTableByEntity(entityType);
if (table == null) return;
if (entityBefore == null && entityAfter == null) return;
if (entityBefore == null && entityAfter != null)
{
tracking.InsertLog.Add(NativeTuple.Create(entityType, entityAfter));
return;
}
if (entityBefore != null && entityAfter == null)
{
tracking.DeleteLog.Add(NativeTuple.Create(entityType, new[] { entityBefore }));
NavigateReader(boundaryName, fsql, entityType, entityBefore, (path, tr, ct, stackvs) =>
{
var dellist = stackvs.Last() as object[] ?? new[] { stackvs.Last() };
tracking.DeleteLog.Add(NativeTuple.Create(ct, dellist));
});
return;
}
var changes = new List<string>();
foreach (var col in table.ColumnsByCs.Values)
{
if (table.ColumnsByCsIgnore.ContainsKey(col.CsName)) continue;
if (table.ColumnsByCs.ContainsKey(col.CsName))
{
if (col.Attribute.IsVersion) continue;
if (col.Attribute.CanUpdate == false) continue;
var propvalBefore = table.GetPropertyValue(entityBefore, col.CsName);
var propvalAfter = table.GetPropertyValue(entityAfter, col.CsName);
//if (object.Equals(propvalBefore, propvalAfter) == false) changes.Add(col.CsName);
if (CompareEntityPropertyValue(col.CsType, propvalBefore, propvalAfter) == false) changes.Add(col.CsName);
continue;
}
}
if (changes.Any()) tracking.UpdateLog.Add(NativeTuple.Create(entityType, entityBefore, entityAfter, changes));
if (cascade == false) return;
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
{
var tbref = tr.Value;
if (tbref.Exception != null) continue;
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
if (boundaryAttr?.Break == true) continue;
if (navigatePropertyName != null && prop.Name != navigatePropertyName) continue;
var propvalBefore = table.GetPropertyValue(entityBefore, prop.Name);
var propvalAfter = table.GetPropertyValue(entityAfter, prop.Name);
switch (tbref.RefType)
{
case TableRefType.OneToOne:
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore);
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter);
LocalCompareEntityValue(tbref.RefEntityType, propvalBefore, propvalAfter, null, boundaryAttr?.BreakThen != true);
break;
case TableRefType.OneToMany:
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore);
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter);
LocalCompareEntityValueCollection(tbref.RefEntityType, propvalBefore as IEnumerable, propvalAfter as IEnumerable, boundaryAttr?.BreakThen != true);
break;
case TableRefType.ManyToMany:
var middleValuesBefore = GetManyToManyObjects(fsql, table, tbref, entityBefore, prop);
var middleValuesAfter = GetManyToManyObjects(fsql, table, tbref, entityAfter, prop);
LocalCompareEntityValueCollection(tbref.RefMiddleEntityType, middleValuesBefore as IEnumerable, middleValuesAfter as IEnumerable, false);
break;
case TableRefType.PgArrayToMany:
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
break;
}
}
}
void LocalCompareEntityValueCollection(Type elementType, IEnumerable collectionBefore, IEnumerable collectionAfter, bool cascade)
{
if (collectionBefore == null && collectionAfter == null) return;
if (collectionBefore == null && collectionAfter != null)
{
foreach (var item in collectionAfter)
tracking.InsertLog.Add(NativeTuple.Create(elementType, item));
return;
}
if (collectionBefore != null && collectionAfter == null)
{
//foreach (var item in collectionBefore)
//{
// changelog.DeleteLog.Add(NativeTuple.Create(elementType, new[] { item }));
// NavigateReader(boundaryName, fsql, elementType, item, (path, tr, ct, stackvs) =>
// {
// var dellist = stackvs.Last() as object[] ?? new [] { stackvs.Last() };
// changelog.DeleteLog.Add(NativeTuple.Create(ct, dellist));
// });
//}
return;
}
var table = fsql.CodeFirst.GetTableByEntity(elementType);
Dictionary<string, object> dictBefore = new Dictionary<string, object>();
Dictionary<string, object> dictAfter = new Dictionary<string, object>();
foreach (var item in collectionBefore)
{
var key = fsql.GetEntityKeyString(elementType, item, false);
if (!string.IsNullOrEmpty(key)) dictBefore.Add(key, item);
}
foreach (var item in collectionAfter)
{
var key = fsql.GetEntityKeyString(elementType, item, false);
if (!string.IsNullOrEmpty(key))
{
if (dictAfter.ContainsKey(key) == false)
dictAfter.Add(key, item);
else if (key == "0" && table.Primarys.Length == 1 &&
new[] { typeof(long), typeof(int) }.Contains(table.Primarys[0].CsType))
tracking.InsertLog.Add(NativeTuple.Create(elementType, item));
}
else tracking.InsertLog.Add(NativeTuple.Create(elementType, item));
}
foreach (var key in dictBefore.Keys.ToArray())
{
if (dictAfter.ContainsKey(key) == false)
{
var value = dictBefore[key];
tracking.DeleteLog.Add(NativeTuple.Create(elementType, new[] { value }));
NavigateReader(boundaryName, fsql, elementType, value, (path, tr, ct, stackvs) =>
{
var dellist = stackvs.Last() as object[] ?? new[] { stackvs.Last() };
tracking.DeleteLog.Add(NativeTuple.Create(ct, dellist));
});
dictBefore.Remove(key);
}
}
foreach (var key in dictAfter.Keys.ToArray())
{
if (dictBefore.ContainsKey(key) == false)
{
tracking.InsertLog.Add(NativeTuple.Create(elementType, dictAfter[key]));
dictAfter.Remove(key);
}
}
foreach (var key in dictBefore.Keys)
LocalCompareEntityValue(elementType, dictBefore[key], dictAfter[key], null, cascade);
}
}
static ConcurrentDictionary<Type, bool> _dicCompareEntityPropertyValue = new ConcurrentDictionary<Type, bool>
{
[typeof(string)] = true,
[typeof(DateTime)] = true,
[typeof(DateTime?)] = true,
[typeof(DateTimeOffset)] = true,
[typeof(DateTimeOffset?)] = true,
[typeof(TimeSpan)] = true,
[typeof(TimeSpan?)] = true,
};
public static bool CompareEntityPropertyValue(Type type, object propvalBefore, object propvalAfter)
{
if (propvalBefore == null && propvalAfter == null) return true;
if (type.IsNumberType() ||
_dicCompareEntityPropertyValue.ContainsKey(type) ||
type.IsEnum ||
type.IsValueType ||
type.NullableTypeOrThis().IsEnum) return object.Equals(propvalBefore, propvalAfter);
if (propvalBefore == null && propvalAfter != null) return false;
if (propvalBefore != null && propvalAfter == null) return false;
if (FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple.ContainsKey(type)) {
if (type.FullName.StartsWith("Newtonsoft."))
return object.Equals(propvalBefore.ToString(), propvalAfter.ToString());
if (typeof(IDictionary).IsAssignableFrom(type))
{
var dictBefore = (propvalBefore as IDictionary);
var dictAfter = (propvalAfter as IDictionary);
if (dictBefore.Count != dictAfter.Count) return false;
foreach (var key in dictBefore.Keys)
{
if (dictAfter.Contains(key) == false) return false;
var valBefore = dictBefore[key];
var valAfter = dictAfter[key];
if (valBefore == null && valAfter == null) continue;
if (valBefore == null && valAfter != null) return false;
if (valBefore != null && valAfter == null) return false;
if (CompareEntityPropertyValue(valBefore.GetType(), valBefore, valAfter) == false) return false;
}
return true;
}
if (type.IsArrayOrList())
{
var enumableBefore = propvalBefore as IEnumerable;
var enumableAfter = propvalAfter as IEnumerable;
var itorBefore = enumableBefore.GetEnumerator();
var itorAfter = enumableAfter.GetEnumerator();
while (true)
{
var moveNextBefore = itorBefore.MoveNext();
var moveNextAfter = itorAfter.MoveNext();
if (moveNextBefore != moveNextAfter) return false;
if (moveNextBefore == false) break;
var currentBefore = itorBefore.Current;
var currentAfter = itorAfter.Current;
if (currentBefore == null && enumableAfter == null) continue;
if (currentBefore == null && currentAfter != null) return false;
if (currentBefore != null && currentAfter == null) return false;
if (CompareEntityPropertyValue(currentBefore.GetType(), currentBefore, currentAfter) == false) return false;
}
return true;
}
if (type.FullName.StartsWith("System.") ||
type.FullName.StartsWith("Npgsql.") ||
type.FullName.StartsWith("NetTopologySuite."))
return object.Equals(propvalBefore, propvalAfter);
if (type.IsClass)
{
foreach (var prop in type.GetProperties())
{
var valBefore = prop.GetValue(propvalBefore, new object[0]);
var valAfter = prop.GetValue(propvalAfter, new object[0]);
if (CompareEntityPropertyValue(prop.PropertyType, valBefore, valAfter) == false) return false;
}
return true;
}
}
return object.Equals(propvalBefore, propvalAfter);
}
public static void NavigateReader(string boundaryName, IFreeSql fsql, Type rootType, object rootEntity, Action<string, TableRef, Type, List<object>> callback)
{
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
var statckPath = new Stack<string>();
var stackValues = new List<object>();
statckPath.Push("_");
stackValues.Add(rootEntity);
LocalNavigateReader(rootType, rootEntity);
ignores.Clear();
void LocalNavigateReader(Type entityType, object entity)
{
if (entity == null) return;
if (entityType == null) entityType = entity.GetType();
var table = fsql.CodeFirst.GetTableByEntity(entityType);
if (table == null) return;
var stateKey = fsql.GetEntityKeyString(entityType, entity, false);
if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
if (stateKeys.ContainsKey(stateKey)) return;
stateKeys.Add(stateKey, true);
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
{
var tbref = tr.Value;
if (tbref.Exception != null) continue;
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
if (boundaryAttr?.Break == true) continue;
switch (tbref.RefType)
{
case TableRefType.OneToOne:
var propval = table.GetPropertyValue(entity, prop.Name);
if (propval == null) continue;
statckPath.Push(prop.Name);
stackValues.Add(propval);
SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propval);
callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues);
if (boundaryAttr?.BreakThen != true)
LocalNavigateReader(tbref.RefEntityType, propval);
stackValues.RemoveAt(stackValues.Count - 1);
statckPath.Pop();
break;
case TableRefType.OneToMany:
var propvalOtm = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
if (propvalOtm == null) continue;
SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propvalOtm);
var propvalOtmList = new List<object>();
foreach (var val in propvalOtm)
propvalOtmList.Add(val);
statckPath.Push($"{prop.Name}[]");
stackValues.Add(propvalOtmList.ToArray());
callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues);
if (boundaryAttr?.BreakThen != true)
foreach (var val in propvalOtm)
LocalNavigateReader(tbref.RefEntityType, val);
stackValues.RemoveAt(stackValues.Count - 1);
statckPath.Pop();
break;
case TableRefType.ManyToMany:
var middleValues = GetManyToManyObjects(fsql, table, tbref, entity, prop)?.ToArray();
if (middleValues == null) continue;
statckPath.Push($"{prop.Name}[]");
stackValues.Add(middleValues);
callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefMiddleEntityType, stackValues);
stackValues.RemoveAt(stackValues.Count - 1);
statckPath.Pop();
break;
case TableRefType.PgArrayToMany:
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
break;
}
}
}
}
public static void MapEntityValue(string boundaryName, IFreeSql fsql, Type rootEntityType, object rootEntityFrom, object rootEntityTo)
{
var isDict = rootEntityTo?.GetType() == typeof(Dictionary<string, object>);
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
LocalMapEntityValue(rootEntityType, rootEntityFrom, rootEntityTo, true);
ignores.Clear();
void LocalMapEntityValue(Type entityType, object entityFrom, object entityTo, bool cascade)
{
if (entityFrom == null || entityTo == null) return;
if (entityType == null) entityType = entityFrom.GetType();
var table = fsql.CodeFirst.GetTableByEntity(entityType);
if (table == null) return;
var stateKey = fsql.GetEntityKeyString(entityType, entityFrom, false);
if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
if (stateKeys.ContainsKey(stateKey)) return;
stateKeys.Add(stateKey, true);
foreach (var prop in table.Properties.Values)
{
if (table.ColumnsByCs.ContainsKey(prop.Name) || table.ColumnsByCsIgnore.ContainsKey(prop.Name))
{
//与 EntityUtilExtensions.MapEntityValue 同步修改规则Ignore 也需要 Map
if (isDict) (entityTo as Dictionary<string, object>)[prop.Name] = table.GetPropertyValue(entityFrom, prop.Name);
else if (prop.CanWrite) table.SetPropertyValue(entityTo, prop.Name, table.GetPropertyValue(entityFrom, prop.Name));
continue;
}
if (cascade == false) continue;
var tbref = table.GetTableRef(prop.Name, false, false);
if (tbref == null) continue;
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
if (boundaryAttr?.Break == true) continue;
var propvalFrom = EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, entityType, entityFrom, prop.Name);
if (propvalFrom == null)
{
if (isDict) (entityTo as Dictionary<string, object>)[prop.Name] = null;
else EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, null);
continue;
}
switch (tbref.RefType)
{
case TableRefType.OneToOne:
var propvalTo = isDict ? new Dictionary<string, object>() : tbref.RefEntityType.CreateInstanceGetDefaultValue();
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityFrom, propvalFrom);
LocalMapEntityValue(tbref.RefEntityType, propvalFrom, propvalTo, boundaryAttr?.BreakThen != true);
if (isDict) (entityTo as Dictionary<string, object>)[prop.Name] = null;
else EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTo);
break;
case TableRefType.OneToMany:
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityFrom, propvalFrom);
LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom as IEnumerable, prop, boundaryAttr?.BreakThen != true);
break;
case TableRefType.ManyToMany:
LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom as IEnumerable, prop, false);
break;
case TableRefType.PgArrayToMany:
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
break;
}
}
}
void LocalMapEntityValueCollection(Type entityType, object entityFrom, object entityTo, TableRef tbref, IEnumerable propvalFrom, PropertyInfo prop, bool cascade)
{
if (isDict)
{
var propvalTo = new List<Dictionary<string,object>>();
foreach (var fromItem in propvalFrom)
{
var toItem = new Dictionary<string, object>();
LocalMapEntityValue(tbref.RefEntityType, fromItem, toItem, cascade);
propvalTo.Add(toItem);
}
(entityTo as Dictionary<string, object>)[prop.Name] = propvalTo;
}
else
{
var propvalTo = typeof(List<>).MakeGenericType(tbref.RefEntityType).CreateInstanceGetDefaultValue();
var propvalToIList = propvalTo as IList;
foreach (var fromItem in propvalFrom)
{
var toItem = tbref.RefEntityType.CreateInstanceGetDefaultValue();
LocalMapEntityValue(tbref.RefEntityType, fromItem, toItem, cascade);
propvalToIList.Add(toItem);
}
var propvalType = prop.PropertyType.GetGenericTypeDefinition();
if (propvalType == typeof(List<>) || propvalType == typeof(ICollection<>))
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTo);
else if (propvalType == typeof(ObservableCollection<>))
{
//var propvalTypeOcCtor = typeof(ObservableCollection<>).MakeGenericType(tbref.RefEntityType).GetConstructor(new[] { typeof(List<>).MakeGenericType(tbref.RefEntityType) });
var propvalTypeOc = Activator.CreateInstance(typeof(ObservableCollection<>).MakeGenericType(tbref.RefEntityType), new object[] { propvalTo });
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTypeOc);
}
}
}
}
static ConcurrentDictionary<string, ConcurrentDictionary<Type, ConcurrentDictionary<Type, Action<ISelect0>>>> _dicGetAutoIncludeQuery = new ConcurrentDictionary<string, ConcurrentDictionary<Type, ConcurrentDictionary<Type, Action<ISelect0>>>>();
public static ISelect<TEntity> GetAutoIncludeQuery<TEntity>(string boundaryName, ISelect<TEntity> select)
{
var select0p = select as Select0Provider;
var table0Type = select0p._tables[0].Table.Type;
var func = _dicGetAutoIncludeQuery.GetOrAdd(boundaryName ?? "", bn => new ConcurrentDictionary<Type, ConcurrentDictionary<Type, Action<ISelect0>>>())
.GetOrAdd(typeof(TEntity), t => new ConcurrentDictionary<Type, Action<ISelect0>>())
.GetOrAdd(table0Type, t =>
{
var parmExp1 = Expression.Parameter(typeof(ISelect0));
var parmNavigateParameterExp = Expression.Parameter(typeof(TEntity), "a");
var parmQueryExp = Expression.Convert(parmExp1, typeof(ISelect<>).MakeGenericType(typeof(TEntity)));
var exp = LocalGetAutoIncludeQuery(parmQueryExp, 1, t, parmNavigateParameterExp, parmNavigateParameterExp, new Stack<Type>());
return Expression.Lambda<Action<ISelect0>>(exp, parmExp1).Compile();
});
func(select);
return select;
Expression LocalGetAutoIncludeQuery(Expression queryExp, int depth, Type entityType, ParameterExpression navigateParameterExp, Expression navigatePathExp, Stack<Type> ignores)
{
if (ignores.Any(a => a == entityType)) return queryExp;
ignores.Push(entityType);
var table = select0p._orm.CodeFirst.GetTableByEntity(entityType);
if (table == null) return queryExp;
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
{
var tbref = tr.Value;
if (tbref.Exception != null) continue;
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
if (boundaryAttr?.Break == true) continue;
Expression navigateExp = Expression.MakeMemberAccess(navigatePathExp, prop);
//var lambdaAlias = (char)((byte)'a' + (depth - 1));
switch (tbref.RefType)
{
case TableRefType.OneToOne:
if (ignores.Any(a => a == tbref.RefEntityType)) break;
LocalInclude(tbref, navigateExp);
if (boundaryAttr?.BreakThen != true)
queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores);
break;
case TableRefType.OneToMany:
LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen != true);
break;
case TableRefType.ManyToMany:
LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen == false);
break;
case TableRefType.PgArrayToMany:
if (boundaryAttr?.Break == false)
LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen == false);
break;
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
if (boundaryAttr?.Break == false)
{
LocalInclude(tbref, navigateExp);
if (boundaryAttr?.BreakThen == false)
queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores);
}
break;
}
}
ignores.Pop();
return queryExp;
void LocalInclude(TableRef tbref, Expression exp)
{
var incMethod = queryExp.Type.GetMethod("Include");
if (incMethod == null) throw new Exception(CoreErrorStrings.RunTimeError_Reflection_IncludeMany.Replace("IncludeMany", "Include"));
queryExp = Expression.Call(queryExp, incMethod.MakeGenericMethod(tbref.RefEntityType),
Expression.Lambda(typeof(Func<,>).MakeGenericType(entityType, tbref.RefEntityType), exp, navigateParameterExp));
}
void LocalIncludeMany(TableRef tbref, Expression exp, bool isthen)
{
var funcType = typeof(Func<,>).MakeGenericType(entityType, typeof(IEnumerable<>).MakeGenericType(tbref.RefEntityType));
var navigateSelector = Expression.Lambda(funcType, exp, navigateParameterExp);
var incMethod = queryExp.Type.GetMethod("IncludeMany");
if (incMethod == null) throw new Exception(CoreErrorStrings.RunTimeError_Reflection_IncludeMany);
LambdaExpression navigateThen = null;
var navigateThenType = typeof(Action<>).MakeGenericType(typeof(ISelect<>).MakeGenericType(tbref.RefEntityType));
var thenParameter = Expression.Parameter(typeof(ISelect<>).MakeGenericType(tbref.RefEntityType), "then");
Expression paramQueryExp = thenParameter;
var paramNavigateParameterExp = Expression.Parameter(tbref.RefEntityType, string.Concat((char)((byte)'a' + (depth - 1))));
if (isthen) paramQueryExp = LocalGetAutoIncludeQuery(paramQueryExp, depth + 1, tbref.RefEntityType, paramNavigateParameterExp, paramNavigateParameterExp, ignores);
navigateThen = Expression.Lambda(navigateThenType, paramQueryExp, thenParameter);
queryExp = Expression.Call(queryExp, incMethod.MakeGenericMethod(tbref.RefEntityType), navigateSelector, navigateThen);
}
}
}
public static string GetAutoIncludeQueryStaicCode(string boundaryName, IFreeSql fsql, Type rootEntityType)
{
return $"//fsql.Select<{rootEntityType.Name}>()\r\nSelectDiy{LocalGetAutoIncludeQueryStaicCode(1, rootEntityType, "", new Stack<Type>())}";
string LocalGetAutoIncludeQueryStaicCode(int depth, Type entityType, string navigatePath, Stack<Type> ignores)
{
var code = new StringBuilder();
if (ignores.Any(a => a == entityType)) return null;
ignores.Push(entityType);
var table = fsql.CodeFirst.GetTableByEntity(entityType);
if (table == null) return null;
if (!string.IsNullOrWhiteSpace(navigatePath)) navigatePath = $"{navigatePath}.";
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
{
var tbref = tr.Value;
if (tbref.Exception != null) continue;
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
if (boundaryAttr?.Break == true) continue;
var navigateExpression = $"{navigatePath}{tr.Key}";
var depthTab = "".PadLeft(depth * 4);
var lambdaAlias = (char)((byte)'a' + (depth - 1));
var lambdaStr = $"{lambdaAlias} => {lambdaAlias}.";
switch (tbref.RefType)
{
case TableRefType.OneToOne:
if (ignores.Any(a => a == tbref.RefEntityType)) break;
code.Append("\r\n").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")");
if (boundaryAttr?.BreakThen != true)
code.Append(LocalGetAutoIncludeQueryStaicCode(depth, tbref.RefEntityType, navigateExpression, ignores));
break;
case TableRefType.OneToMany:
code.Append("\r\n").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression);
if (boundaryAttr?.BreakThen != true)
{
var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack<Type>(ignores.ToArray()));
if (thencode.Length > 0) code.Append(", then => then").Append(thencode);
}
code.Append(")");
break;
case TableRefType.ManyToMany:
code.Append("\r\n").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression);
if (boundaryAttr?.BreakThen == false)
{
var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack<Type>(ignores.ToArray()));
if (thencode.Length > 0) code.Append(", then => then").Append(thencode);
}
code.Append(")");
break;
case TableRefType.PgArrayToMany:
code.Append("\r\n").Append(boundaryAttr != null ? "" : "//").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression);
if (boundaryAttr?.BreakThen == false)
{
var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack<Type>(ignores.ToArray()));
if (thencode.Length > 0) code.Append(", then => then").Append(thencode);
}
code.Append(")");
break;
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
code.Append("\r\n").Append(boundaryAttr != null ? "" : "//").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")");
if (boundaryAttr?.BreakThen == false)
code.Append(LocalGetAutoIncludeQueryStaicCode(depth, tbref.RefEntityType, navigateExpression, ignores));
break;
}
}
ignores.Pop();
return code.ToString();
}
}
public static List<object> GetManyToManyObjects(IFreeSql fsql, TableInfo table, TableRef tbref, object entity, PropertyInfo prop)
{
if (tbref.RefType != TableRefType.ManyToMany) return null;
var rights = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
if (rights == null) return null;
var middles = new List<object>();
var leftpkvals = new object[tbref.Columns.Count];
for (var x = 0; x < tbref.Columns.Count; x++)
leftpkvals[x] = Utils.GetDataReaderValue(tbref.MiddleColumns[x].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, table.Type, entity, tbref.Columns[x].CsName));
foreach (var right in rights)
{
var midval = tbref.RefMiddleEntityType.CreateInstanceGetDefaultValue();
for (var x = 0; x < tbref.Columns.Count; x++)
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, tbref.RefMiddleEntityType, midval, tbref.MiddleColumns[x].CsName, leftpkvals[x]);
for (var x = tbref.Columns.Count; x < tbref.MiddleColumns.Count; x++)
{
var refcol = tbref.RefColumns[x - tbref.Columns.Count];
var refval = EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, tbref.RefEntityType, right, refcol.CsName);
if (refval == refcol.CsType.CreateInstanceGetDefaultValue()) throw new Exception($"ManyToMany 关联对象的主键属性({tbref.RefEntityType.DisplayCsharp()}.{refcol.CsName})不能为空");
refval = Utils.GetDataReaderValue(tbref.MiddleColumns[x].CsType, refval);
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, tbref.RefMiddleEntityType, midval, tbref.MiddleColumns[x].CsName, refval);
}
middles.Add(midval);
}
return middles;
}
public static void SetNavigateRelationshipValue(IFreeSql orm, TableRef tbref, Type leftType, object leftItem, object rightItem)
{
switch (tbref.RefType)
{
case TableRefType.OneToOne:
if (rightItem == null) return;
for (var idx = 0; idx < tbref.Columns.Count; idx++)
{
var colval = Utils.GetDataReaderValue(tbref.RefColumns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName));
EntityUtilExtensions.SetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightItem, tbref.RefColumns[idx].CsName, colval);
}
break;
case TableRefType.OneToMany:
if (rightItem == null) return;
var rightEachOtm = rightItem as IEnumerable;
if (rightEachOtm == null) break;
var leftColValsOtm = new object[tbref.Columns.Count];
for (var idx = 0; idx < tbref.Columns.Count; idx++)
leftColValsOtm[idx] = Utils.GetDataReaderValue(tbref.RefColumns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName));
foreach (var rightEle in rightEachOtm)
for (var idx = 0; idx < tbref.Columns.Count; idx++)
EntityUtilExtensions.SetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightEle, tbref.RefColumns[idx].CsName, leftColValsOtm[idx]);
break;
case TableRefType.ManyToOne:
for (var idx = 0; idx < tbref.RefColumns.Count; idx++)
{
var colval = rightItem == null ?
tbref.Columns[idx].CsType.CreateInstanceGetDefaultValue() :
Utils.GetDataReaderValue(tbref.Columns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightItem, tbref.RefColumns[idx].CsName));
EntityUtilExtensions.SetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName, colval);
}
break;
}
}
}
}

View File

@@ -0,0 +1,12 @@
using FreeSql;
using System;
using System.Linq;
using System.Linq.Expressions;
public static class FreeSqlAggregateRootRepositoryGlobalExtensions
{
public static IBaseRepository<TEntity> GetAggregateRootRepository<TEntity>(this IFreeSql that) where TEntity : class
{
return new AggregateRootRepository<TEntity>(that);
}
}

View File

@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>FreeSql;ncc;YeXiangQin</Authors>
<Description>FreeSql 扩展包,聚合根(实现室).</Description>
<PackageProjectUrl>https://github.com/dotnetcore/FreeSql/wiki/%E8%81%9A%E5%90%88%E6%A0%B9%EF%BC%88%E5%AE%9E%E9%AA%8C%E5%AE%A4%EF%BC%89</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnetcore/FreeSql/wiki/%E8%81%9A%E5%90%88%E6%A0%B9%EF%BC%88%E5%AE%9E%E9%AA%8C%E5%AE%A4%EF%BC%89</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageTags>FreeSql;ORM</PackageTags>
<PackageId>$(AssemblyName)</PackageId>
<PackageIcon>logo.png</PackageIcon>
<Title>$(AssemblyName)</Title>
<IsPackable>true</IsPackable>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<Version>3.5.210-preview20250626</Version>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="../../readme.md" Pack="true" PackagePath="\" />
<None Include="../../logo.png" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\FreeSql.DbContext\FreeSql.DbContext.csproj" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<DocumentationFile>FreeSql.Extensions.AggregateRoot.xml</DocumentationFile>
<WarningLevel>3</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net40'">
<DefineConstants>net40</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>FreeSql.Extensions.AggregateRoot</name>
</assembly>
<members>
<member name="T:FreeSql.DataAnnotations.AggregateRootBoundaryAttribute">
<summary>
设置 AggregateRootRepository 边界范围<para></para>
在边界范围之内的规则 <para></para>
1、OneToOne/OneToMany/ManyToMany(中间表) 可以查询、可以增删改<para></para>
2、ManyToOne/ManyToMany外部表/PgArrayToMany 只可以查询,不支持增删改(会被忽略)<para></para>
</summary>
</member>
<member name="P:FreeSql.DataAnnotations.AggregateRootBoundaryAttribute.Break">
<summary>
边界是否终止
</summary>
</member>
<member name="P:FreeSql.DataAnnotations.AggregateRootBoundaryAttribute.BreakThen">
<summary>
边界是否终止向下探测
</summary>
</member>
<member name="P:FreeSql.AggregateRootRepository`1.Select">
<summary>
默认:创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性)<para></para>
重写:使用
</summary>
</member>
<member name="P:FreeSql.AggregateRootRepository`1.SelectDiy">
<summary>
创建查询对象(纯净)<para></para>
_<para></para>
聚合根内关系较复杂时,获取 Include/IncludeMany 字符串代码,方便二次开发<para></para>
string code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(null, fsql, typeof(Order))
</summary>
</member>
<member name="P:FreeSql.AggregateRootRepository`1.SelectAggregateRoot">
<summary>
创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性)
</summary>
<returns></returns>
</member>
<member name="M:FreeSql.AggregateRootRepository`1.SelectAggregateRootTracking(System.Object)">
<summary>
ISelect.TrackToList 委托,数据返回后自动 Attach
</summary>
<param name="list"></param>
</member>
</members>
</doc>

Binary file not shown.

View File

@@ -1,17 +1,24 @@
using FreeSql;
#if NET40
using FreeSql.DataAnnotations;
using System;
#else
using FreeSql.DataAnnotations;
using System;
using System.Data;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
#endif
// ReSharper disable once CheckNamespace
namespace FreeSql
{
/// <summary>
/// Entity base class, including CreateTime/UpdateTime/IsDeleted, the CRUD methods, and ID primary key definition.
/// <para></para>
/// 包括 CreateTime/UpdateTime/IsDeleted、CRUD 方法、以及 ID 主键定义 的实体基类
/// <para></para>
/// When TKey is int/long, the Id is set to be an auto-incremented primary key
/// <para></para>
/// 当 TKey 为 int/long 时Id 主键被设为自增值主键
/// </summary>
/// <typeparam name="TEntity"></typeparam>
@@ -21,25 +28,26 @@ namespace FreeSql
{
static BaseEntity()
{
var tkeyType = typeof(TKey)?.NullableTypeOrThis();
if (tkeyType == typeof(int) || tkeyType == typeof(long))
BaseEntity.ConfigEntity(typeof(TEntity), t => t.Property("Id").IsIdentity(true));
var keyType = typeof(TKey).NullableTypeOrThis();
if (keyType == typeof(int) || keyType == typeof(long))
ConfigEntity(typeof(TEntity), t => t.Property("Id").IsIdentity(true));
}
/// <summary>
/// Primary key <br />
/// 主键
/// </summary>
[Column(Position = 1)]
public virtual TKey Id { get; set; }
#if net40
#else
#if !NET40
/// <summary>
/// Get data based on the value of the primary key <br />
/// 根据主键值获取数据
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
async public static Task<TEntity> FindAsync(TKey id)
public static async Task<TEntity> FindAsync(TKey id)
{
var item = await Select.WhereDynamic(id).FirstAsync();
(item as BaseEntity<TEntity>)?.Attach();
@@ -48,6 +56,7 @@ namespace FreeSql
#endif
/// <summary>
/// Get data based on the value of the primary key <br />
/// 根据主键值获取数据
/// </summary>
/// <param name="id"></param>
@@ -61,6 +70,8 @@ namespace FreeSql
}
/// <summary>
/// Entity base class, including CreateTime/UpdateTime/IsDeleted, and sync/async CRUD methods.
/// <para></para>
/// 包括 CreateTime/UpdateTime/IsDeleted、以及 CRUD 异步和同步方法的实体基类
/// </summary>
/// <typeparam name="TEntity"></typeparam>
@@ -69,88 +80,100 @@ namespace FreeSql
{
bool UpdateIsDeleted(bool value)
{
if (this.Repository == null)
if (Repository == null)
return Orm.Update<TEntity>(this as TEntity)
.WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction())
.Set(a => (a as BaseEntity).IsDeleted, this.IsDeleted = value).ExecuteAffrows() == 1;
.Set(a => (a as BaseEntity).IsDeleted, IsDeleted = value)
.ExecuteAffrows() == 1;
this.IsDeleted = value;
this.Repository.UnitOfWork = _resolveUow?.Invoke();
return this.Repository.Update(this as TEntity) == 1;
IsDeleted = value;
Repository.UnitOfWork = _resolveUow?.Invoke();
return Repository.Update(this as TEntity) == 1;
}
/// <summary>
/// To delete data <br />
/// 删除数据
/// </summary>
/// <param name="physicalDelete">是否物理删除</param>
/// <param name="physicalDelete">To flag whether to delete the physical level of the data</param>
/// <returns></returns>
public virtual bool Delete(bool physicalDelete = false)
{
if (physicalDelete == false) return this.UpdateIsDeleted(true);
if (this.Repository == null)
return Orm.Delete<TEntity>(this as TEntity).ExecuteAffrows() == 1;
if (physicalDelete == false)
return UpdateIsDeleted(true);
this.Repository.UnitOfWork = _resolveUow?.Invoke();
return this.Repository.Delete(this as TEntity) == 1;
if (Repository == null)
return Orm.Delete<TEntity>(this as TEntity)
.WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction())
.ExecuteAffrows() == 1;
Repository.UnitOfWork = _resolveUow?.Invoke();
return Repository.Delete(this as TEntity) == 1;
}
/// <summary>
/// To recover deleted data <br />
/// 恢复删除的数据
/// </summary>
/// <returns></returns>
public virtual bool Restore() => this.UpdateIsDeleted(false);
public virtual bool Restore() => UpdateIsDeleted(false);
/// <summary>
/// To update data <br />
/// 更新数据
/// </summary>
/// <returns></returns>
public virtual bool Update()
{
this.UpdateTime = DateTime.Now;
if (this.Repository == null)
UpdateTime = DateTime.Now;
if (Repository == null)
return Orm.Update<TEntity>()
.WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction())
.SetSource(this as TEntity).ExecuteAffrows() == 1;
.SetSource(this as TEntity)
.ExecuteAffrows() == 1;
this.Repository.UnitOfWork = _resolveUow?.Invoke();
return this.Repository.Update(this as TEntity) == 1;
Repository.UnitOfWork = _resolveUow?.Invoke();
return Repository.Update(this as TEntity) == 1;
}
/// <summary>
/// To insert data <br />
/// 插入数据
/// </summary>
public virtual TEntity Insert()
{
this.CreateTime = DateTime.Now;
if (this.Repository == null)
this.Repository = Orm.GetRepository<TEntity>();
this.Repository.UnitOfWork = _resolveUow?.Invoke();
return this.Repository.Insert(this as TEntity);
CreateTime = DateTime.Now;
if (Repository == null)
Repository = Orm.GetRepository<TEntity>();
Repository.UnitOfWork = _resolveUow?.Invoke();
return Repository.Insert(this as TEntity);
}
/// <summary>
/// To insert or update data <br />
/// 更新或插入
/// </summary>
/// <returns></returns>
public virtual TEntity Save()
{
this.UpdateTime = DateTime.Now;
if (this.Repository == null)
this.Repository = Orm.GetRepository<TEntity>();
this.Repository.UnitOfWork = _resolveUow?.Invoke();
return this.Repository.InsertOrUpdate(this as TEntity);
UpdateTime = DateTime.Now;
if (Repository == null)
Repository = Orm.GetRepository<TEntity>();
Repository.UnitOfWork = _resolveUow?.Invoke();
return Repository.InsertOrUpdate(this as TEntity);
}
/// <summary>
/// To completely save the navigation properties of the entity in the form of sub-tables. <br />
/// 【完整】保存导航属性,子表
/// </summary>
/// <param name="navigatePropertyName">导航属性名</param>
/// <param name="navigatePropertyName">Navigation property name</param>
public virtual void SaveMany(string navigatePropertyName)
{
if (this.Repository == null)
this.Repository = Orm.GetRepository<TEntity>();
this.Repository.UnitOfWork = _resolveUow?.Invoke();
this.Repository.SaveMany(this as TEntity, navigatePropertyName);
if (Repository == null)
Repository = Orm.GetRepository<TEntity>();
Repository.UnitOfWork = _resolveUow?.Invoke();
Repository.SaveMany(this as TEntity, navigatePropertyName);
}
}
}

View File

@@ -1,14 +1,23 @@

using FreeSql;
#if NET40
using FreeSql.DataAnnotations;
#else
using FreeSql.DataAnnotations;
using System;
using System.Threading.Tasks;
#endif
// ReSharper disable once CheckNamespace
namespace FreeSql
{
/// <summary>
/// Entity base class, including CreateTime/UpdateTime/IsDeleted, the async CRUD methods, and ID primary key definition.
/// <para></para>
/// 包括 CreateTime/UpdateTime/IsDeleted、CRUD 异步方法、以及 ID 主键定义 的实体基类
/// <para></para>
/// When TKey is int/long, the Id is set to be an auto-incremented primary key
/// <para></para>
/// 当 TKey 为 int/long 时Id 主键被设为自增值主键
/// </summary>
/// <typeparam name="TEntity"></typeparam>
@@ -18,128 +27,141 @@ namespace FreeSql
{
static BaseEntityAsync()
{
var tkeyType = typeof(TKey)?.NullableTypeOrThis();
if (tkeyType == typeof(int) || tkeyType == typeof(long))
BaseEntity.ConfigEntity(typeof(TEntity), t => t.Property("Id").IsIdentity(true));
var keyType = typeof(TKey).NullableTypeOrThis();
if (keyType == typeof(int) || keyType == typeof(long))
ConfigEntity(typeof(TEntity), t => t.Property("Id").IsIdentity(true));
}
/// <summary>
/// Primary key <br />
/// 主键
/// </summary>
[Column(Position = 1)]
[Column(Position = 1)]
public virtual TKey Id { get; set; }
#if net40
#else
#if !NET40
/// <summary>
/// Get data based on the value of the primary key <br />
/// 根据主键值获取数据
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
async public static Task<TEntity> FindAsync(TKey id)
public static async Task<TEntity> FindAsync(TKey id)
{
var item = await Select.WhereDynamic(id).FirstAsync();
(item as BaseEntity<TEntity>)?.Attach();
return item;
}
#endif
}
/// <summary>
/// Entity base class, including CreateTime/UpdateTime/IsDeleted, and async CRUD methods.
/// <para></para>
/// 包括 CreateTime/UpdateTime/IsDeleted、以及 CRUD 异步方法的实体基类
/// </summary>
/// <typeparam name="TEntity"></typeparam>
[Table(DisableSyncStructure = true)]
public abstract class BaseEntityAsync<TEntity> : BaseEntityReadOnly<TEntity> where TEntity : class
{
#if net40
#else
#if !NET40
async Task<bool> UpdateIsDeletedAsync(bool value)
{
if (this.Repository == null)
if (Repository == null)
return await Orm.Update<TEntity>(this as TEntity)
.WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction())
.Set(a => (a as BaseEntity).IsDeleted, this.IsDeleted = value).ExecuteAffrowsAsync() == 1;
.Set(a => (a as BaseEntity).IsDeleted, IsDeleted = value)
.ExecuteAffrowsAsync() == 1;
this.IsDeleted = value;
this.Repository.UnitOfWork = _resolveUow?.Invoke();
return await this.Repository.UpdateAsync(this as TEntity) == 1;
IsDeleted = value;
Repository.UnitOfWork = _resolveUow?.Invoke();
return await Repository.UpdateAsync(this as TEntity) == 1;
}
/// <summary>
/// To delete data <br />
/// 删除数据
/// </summary>
/// <param name="physicalDelete">是否物理删除</param>
/// <param name="physicalDelete">To flag whether to delete the physical level of the data</param>
/// <returns></returns>
async public virtual Task<bool> DeleteAsync(bool physicalDelete = false)
public virtual async Task<bool> DeleteAsync(bool physicalDelete = false)
{
if (physicalDelete == false) return await this.UpdateIsDeletedAsync(true);
if (this.Repository == null)
return await Orm.Delete<TEntity>(this as TEntity).ExecuteAffrowsAsync() == 1;
if (physicalDelete == false)
return await UpdateIsDeletedAsync(true);
this.Repository.UnitOfWork = _resolveUow?.Invoke();
return await this.Repository.DeleteAsync(this as TEntity) == 1;
if (Repository == null)
return await Orm.Delete<TEntity>(this as TEntity)
.WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction())
.ExecuteAffrowsAsync() == 1;
Repository.UnitOfWork = _resolveUow?.Invoke();
return await Repository.DeleteAsync(this as TEntity) == 1;
}
/// <summary>
/// To recover deleted data <br />
/// 恢复删除的数据
/// </summary>
/// <returns></returns>
public virtual Task<bool> RestoreAsync() => this.UpdateIsDeletedAsync(false);
public virtual Task<bool> RestoreAsync() => UpdateIsDeletedAsync(false);
/// <summary>
/// To update data <br />
/// 更新数据
/// </summary>
/// <returns></returns>
async public virtual Task<bool> UpdateAsync()
public virtual async Task<bool> UpdateAsync()
{
this.UpdateTime = DateTime.Now;
if (this.Repository == null)
UpdateTime = DateTime.Now;
if (Repository == null)
return await Orm.Update<TEntity>()
.WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction())
.SetSource(this as TEntity).ExecuteAffrowsAsync() == 1;
.SetSource(this as TEntity)
.ExecuteAffrowsAsync() == 1;
this.Repository.UnitOfWork = _resolveUow?.Invoke();
return await this.Repository.UpdateAsync(this as TEntity) == 1;
Repository.UnitOfWork = _resolveUow?.Invoke();
return await Repository.UpdateAsync(this as TEntity) == 1;
}
/// <summary>
/// To insert data <br />
/// 插入数据
/// </summary>
public virtual Task<TEntity> InsertAsync()
{
this.CreateTime = DateTime.Now;
if (this.Repository == null)
this.Repository = Orm.GetRepository<TEntity>();
this.Repository.UnitOfWork = _resolveUow?.Invoke();
return this.Repository.InsertAsync(this as TEntity);
CreateTime = DateTime.Now;
if (Repository == null)
Repository = Orm.GetRepository<TEntity>();
Repository.UnitOfWork = _resolveUow?.Invoke();
return Repository.InsertAsync(this as TEntity);
}
/// <summary>
/// To insert or update data <br />
/// 更新或插入
/// </summary>
/// <returns></returns>
public virtual Task<TEntity> SaveAsync()
{
this.UpdateTime = DateTime.Now;
if (this.Repository == null)
this.Repository = Orm.GetRepository<TEntity>();
this.Repository.UnitOfWork = _resolveUow?.Invoke();
return this.Repository.InsertOrUpdateAsync(this as TEntity);
UpdateTime = DateTime.Now;
if (Repository == null)
Repository = Orm.GetRepository<TEntity>();
Repository.UnitOfWork = _resolveUow?.Invoke();
return Repository.InsertOrUpdateAsync(this as TEntity);
}
/// <summary>
/// To completely save the navigation properties of the entity in the form of sub-tables. <br />
/// 【完整】保存导航属性,子表
/// </summary>
/// <param name="navigatePropertyName">导航属性名</param>
/// <param name="navigatePropertyName">Navigation property name</param>
public virtual Task SaveManyAsync(string navigatePropertyName)
{
if (this.Repository == null)
this.Repository = Orm.GetRepository<TEntity>();
this.Repository.UnitOfWork = _resolveUow?.Invoke();
return this.Repository.SaveManyAsync(this as TEntity, navigatePropertyName);
if (Repository == null)
Repository = Orm.GetRepository<TEntity>();
Repository.UnitOfWork = _resolveUow?.Invoke();
return Repository.SaveManyAsync(this as TEntity, navigatePropertyName);
}
#endif
}
}
}

View File

@@ -1,104 +1,107 @@

using FreeSql.DataAnnotations;
using FreeSql.DataAnnotations;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
// ReSharper disable CheckNamespace
// ReSharper disable InconsistentNaming
// ReSharper disable InconsistentlySynchronizedField
namespace FreeSql
{
/// <summary>
/// Entity base class, including CreateTime/UpdateTime/IsDeleted.
/// <para></para>
/// 包括 CreateTime/UpdateTime/IsDeleted 的实体基类
/// </summary>
[Table(DisableSyncStructure = true)]
public abstract class BaseEntity
{
internal static IFreeSql _ormPriv;
/// <summary>
/// 全局 IFreeSql orm 对象
/// </summary>
public static IFreeSql Orm => _ormPriv ?? throw new Exception(@"使用前请初始化 BaseEntity.Initialization(new FreeSqlBuilder()
.UseAutoSyncStructure(true)
.UseConnectionString(DataType.Sqlite, ""data source=test.db;max pool size=5"")
.Build());");
static Func<IFreeSql> _resoleOrm;
internal static Func<IUnitOfWork> _resolveUow;
/// <summary>
/// 初始化BaseEntity
/// BaseEntity.Initialization(new FreeSqlBuilder()
/// <para></para>
/// .UseAutoSyncStructure(true)
/// <para></para>
/// .UseConnectionString(DataType.Sqlite, "data source=test.db;max pool size=5")
/// <para></para>
/// .Build());
/// </summary>
/// <param name="fsql">IFreeSql orm 对象</param>
/// <param name="resolveUow">工作单元(事务)委托,如果不使用事务请传 null<para></para>解释由于AsyncLocal平台兼容不好所以交给外部管理</param>
public static void Initialization(IFreeSql fsql, Func<IUnitOfWork> resolveUow)
public static IFreeSql Orm => _resoleOrm?.Invoke() ?? throw new Exception(CoreErrorStrings.S_BaseEntity_Initialization_Error);
public static void Initialization(IFreeSql fsql, Func<IUnitOfWork> resolveUow) => Initialization(() => fsql, resolveUow);
public static void Initialization(Func<IFreeSql> resoleOrm, Func<IUnitOfWork> resolveUow)
{
_ormPriv = fsql;
_ormPriv.Aop.CurdBefore += (s, e) => Trace.WriteLine($"\r\n线程{Thread.CurrentThread.ManagedThreadId}: {e.Sql}\r\n");
_resoleOrm = resoleOrm;
_resolveUow = resolveUow;
if (_configEntityQueues.Any())
{
lock (_configEntityLock)
{
while (_configEntityQueues.TryDequeue(out var cei))
_ormPriv.CodeFirst.ConfigEntity(cei.EntityType, cei.Fluent);
Orm.CodeFirst.ConfigEntity(cei.EntityType, cei.Fluent);
}
}
_resolveUow = resolveUow;
}
class ConfigEntityInfo
{
public Type EntityType;
public Action<TableFluent> Fluent;
}
static ConcurrentQueue<ConfigEntityInfo> _configEntityQueues = new ConcurrentQueue<ConfigEntityInfo>();
static object _configEntityLock = new object();
static readonly ConcurrentQueue<ConfigEntityInfo> _configEntityQueues = new ConcurrentQueue<ConfigEntityInfo>();
static readonly object _configEntityLock = new object();
internal static void ConfigEntity(Type entityType, Action<TableFluent> fluent)
{
lock (_configEntityLock)
{
if (_ormPriv == null)
if (_resoleOrm?.Invoke() == null)
_configEntityQueues.Enqueue(new ConfigEntityInfo { EntityType = entityType, Fluent = fluent });
else
_ormPriv.CodeFirst.ConfigEntity(entityType, fluent);
Orm.CodeFirst.ConfigEntity(entityType, fluent);
}
}
/// <summary>
/// Created time <br />
/// 创建时间
/// </summary>
[Column(Position = -4)]
public virtual DateTime CreateTime { get; set; } = DateTime.Now;
/// <summary>
/// Updated time <br />
/// 更新时间
/// </summary>
[Column(Position = -3)]
public virtual DateTime UpdateTime { get; set; }
/// <summary>
/// Logical Delete <br />
/// 逻辑删除
/// </summary>
[Column(Position = -2)]
public virtual bool IsDeleted { get; set; }
/// <summary>
/// Sort <br />
/// 排序
/// </summary>
[Column(Position = -1)]
public virtual int Sort { get; set; }
}
/// <summary>
/// A readonly entity base class, including CreateTime/UpdateTime/IsDeleted.
/// <para></para>
/// 包括 CreateTime/UpdateTime/IsDeleted 的只读实体基类
/// </summary>
/// <typeparam name="TEntity"></typeparam>
[Table(DisableSyncStructure = true)]
public abstract class BaseEntityReadOnly<TEntity> : BaseEntity where TEntity : class
{
/// <summary>
/// To query data <br />
/// 查询数据
/// </summary>
/// <returns></returns>
@@ -116,16 +119,20 @@ namespace FreeSql
static void TrackToList(object list)
{
if (list == null) return;
var ls = list as IList<TEntity>;
if (ls == null)
{
var ie = list as IEnumerable;
if (ie == null) return;
var isFirst = true;
IBaseRepository<TEntity> berepo = null;
IBaseRepository<TEntity> baseRepo = null;
foreach (var item in ie)
{
if (item == null) return;
if (isFirst)
{
isFirst = false;
@@ -133,42 +140,53 @@ namespace FreeSql
if (itemType == typeof(object)) return;
if (itemType.FullName.Contains("FreeSqlLazyEntity__")) itemType = itemType.BaseType;
if (Orm.CodeFirst.GetTableByEntity(itemType)?.Primarys.Any() != true) return;
if (itemType.GetConstructor(Type.EmptyTypes) == null) return;
if (item is BaseEntity<TEntity> == false) return;
}
var beitem = item as BaseEntity<TEntity>;
if (beitem != null)
if (item is BaseEntity<TEntity> entity)
{
if (berepo == null) berepo = Orm.GetRepository<TEntity>();
beitem.Repository = berepo;
beitem.Attach();
if (baseRepo == null) baseRepo = Orm.GetRepository<TEntity>();
entity.Repository = baseRepo;
entity.Attach();
}
}
return;
}
if (ls.Any() == false) return;
if (ls.FirstOrDefault() is BaseEntity<TEntity> == false) return;
if (Orm.CodeFirst.GetTableByEntity(typeof(TEntity))?.Primarys.Any() != true) return;
IBaseRepository<TEntity> repo = null;
foreach (var item in ls)
{
var beitem = item as BaseEntity<TEntity>;
if (beitem != null)
if (item is BaseEntity<TEntity> entity)
{
if (repo == null) repo = Orm.GetRepository<TEntity>();
beitem.Repository = repo;
beitem.Attach();
entity.Repository = repo;
entity.Attach();
}
}
}
/// <summary>
/// 查询条件Where(a => a.Id > 10)支持导航对象查询Where(a => a.Author.Email == "2881099@qq.com")
/// Query conditions <br />
/// 查询条件Where(a => a.Id> 10)
/// <para></para>
/// Support navigation object query <br />
/// 支持导航对象查询Where(a => a.Author.Email == "2881099@qq.com")
/// </summary>
/// <param name="exp">lambda表达式</param>
/// <returns></returns>
public static ISelect<TEntity> Where(Expression<Func<TEntity, bool>> exp) => Select.Where(exp);
/// <summary>
/// 查询条件Where(true, a => a.Id > 10)支导航对象查询Where(true, a => a.Author.Email == "2881099@qq.com")
/// Query conditions <br />
/// 查询条件Where(true, a => a.Id > 10)
/// <para></para>
/// Support navigation object query <br />
/// 支导航对象查询Where(true, a => a.Author.Email == "2881099@qq.com")
/// </summary>
/// <param name="condition">true 时生效</param>
/// <param name="exp">lambda表达式</param>
@@ -176,21 +194,22 @@ namespace FreeSql
public static ISelect<TEntity> WhereIf(bool condition, Expression<Func<TEntity, bool>> exp) => Select.WhereIf(condition, exp);
/// <summary>
/// Repository object. <br />
/// 仓储对象
/// </summary>
protected IBaseRepository<TEntity> Repository { get; set; }
/// <summary>
/// 附加实体,在更新数据时,只更新变化的部分
/// To Attach entities. When updating data, only the changed part is updated. <br />
/// 附加实体。在更新数据时,只更新变化的部分
/// </summary>
public TEntity Attach()
{
if (this.Repository == null)
this.Repository = Orm.GetRepository<TEntity>();
if (Repository == null)
Repository = Orm.GetRepository<TEntity>();
var item = this as TEntity;
this.Repository.Attach(item);
Repository.Attach(item);
return item;
}
}
}
}

View File

@@ -1,41 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks>
<Version>2.5.100</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>FreeSql;ncc;YeXiangQin</Authors>
<Description>BaseEntity 是一种极简单的 CodeFirst 开发方式特别对单表或多表CRUD利用继承节省了每个实体类的重复属性创建时间、ID等字段软件删除等功能进行 crud 操作时不必时常考虑仓储的使用.</Description>
<PackageProjectUrl>https://github.com/2881099/FreeSql/tree/master/Extensions/FreeSql.Extensions.BaseEntity</PackageProjectUrl>
<RepositoryUrl>https://github.com/2881099/FreeSql/tree/master/Extensions/FreeSql.Extensions.BaseEntity</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageTags>FreeSql;ORM;BaseEntity</PackageTags>
<PackageId>$(AssemblyName)</PackageId>
<PackageIcon>logo.png</PackageIcon>
<Title>$(AssemblyName)</Title>
<IsPackable>true</IsPackable>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>FreeSql;ncc;YeXiangQin</Authors>
<Description>BaseEntity 是一种极简单的 CodeFirst 开发方式特别对单表或多表CRUD利用继承节省了每个实体类的重复属性创建时间、ID等字段软件删除等功能进行 crud 操作时不必时常考虑仓储的使用.</Description>
<PackageProjectUrl>https://github.com/dotnetcore/FreeSql/tree/master/Extensions/FreeSql.Extensions.BaseEntity</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnetcore/FreeSql/tree/master/Extensions/FreeSql.Extensions.BaseEntity</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageTags>FreeSql;ORM;BaseEntity</PackageTags>
<PackageId>$(AssemblyName)</PackageId>
<PackageIcon>logo.png</PackageIcon>
<Title>$(AssemblyName)</Title>
<IsPackable>true</IsPackable>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<LangVersion>latest</LangVersion>
<Version>3.5.210-preview20250626</Version>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="../../logo.png" Pack="true" PackagePath="\" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<DocumentationFile>FreeSql.Extensions.BaseEntity.xml</DocumentationFile>
<WarningLevel>3</WarningLevel>
</PropertyGroup>
<ItemGroup>
<None Include="../../readme.md" Pack="true" PackagePath="\"/>
<None Include="../../logo.png" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\FreeSql.DbContext\FreeSql.DbContext.csproj" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<DocumentationFile>FreeSql.Extensions.BaseEntity.xml</DocumentationFile>
<WarningLevel>3</WarningLevel>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\FreeSql.DbContext\FreeSql.DbContext.csproj" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net40'">
<DefineConstants>net40</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net40'">
<DefineConstants>net40</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -6,8 +6,12 @@
<members>
<member name="T:FreeSql.BaseEntity`2">
<summary>
Entity base class, including CreateTime/UpdateTime/IsDeleted, the CRUD methods, and ID primary key definition.
<para></para>
包括 CreateTime/UpdateTime/IsDeleted、CRUD 方法、以及 ID 主键定义 的实体基类
<para></para>
When TKey is int/long, the Id is set to be an auto-incremented primary key
<para></para>
当 TKey 为 int/long 时Id 主键被设为自增值主键
</summary>
<typeparam name="TEntity"></typeparam>
@@ -15,11 +19,13 @@
</member>
<member name="P:FreeSql.BaseEntity`2.Id">
<summary>
Primary key <br />
主键
</summary>
</member>
<member name="M:FreeSql.BaseEntity`2.FindAsync(`1)">
<summary>
Get data based on the value of the primary key <br />
根据主键值获取数据
</summary>
<param name="id"></param>
@@ -27,6 +33,7 @@
</member>
<member name="M:FreeSql.BaseEntity`2.Find(`1)">
<summary>
Get data based on the value of the primary key <br />
根据主键值获取数据
</summary>
<param name="id"></param>
@@ -34,50 +41,62 @@
</member>
<member name="T:FreeSql.BaseEntity`1">
<summary>
Entity base class, including CreateTime/UpdateTime/IsDeleted, and sync/async CRUD methods.
<para></para>
包括 CreateTime/UpdateTime/IsDeleted、以及 CRUD 异步和同步方法的实体基类
</summary>
<typeparam name="TEntity"></typeparam>
</member>
<member name="M:FreeSql.BaseEntity`1.Delete(System.Boolean)">
<summary>
To delete data <br />
删除数据
</summary>
<param name="physicalDelete">是否物理删除</param>
<param name="physicalDelete">To flag whether to delete the physical level of the data</param>
<returns></returns>
</member>
<member name="M:FreeSql.BaseEntity`1.Restore">
<summary>
To recover deleted data <br />
恢复删除的数据
</summary>
<returns></returns>
</member>
<member name="M:FreeSql.BaseEntity`1.Update">
<summary>
To update data <br />
更新数据
</summary>
<returns></returns>
</member>
<member name="M:FreeSql.BaseEntity`1.Insert">
<summary>
To insert data <br />
插入数据
</summary>
</member>
<member name="M:FreeSql.BaseEntity`1.Save">
<summary>
To insert or update data <br />
更新或插入
</summary>
<returns></returns>
</member>
<member name="M:FreeSql.BaseEntity`1.SaveMany(System.String)">
<summary>
To completely save the navigation properties of the entity in the form of sub-tables. <br />
【完整】保存导航属性,子表
</summary>
<param name="navigatePropertyName">导航属性名</param>
<param name="navigatePropertyName">Navigation property name</param>
</member>
<member name="T:FreeSql.BaseEntityAsync`2">
<summary>
Entity base class, including CreateTime/UpdateTime/IsDeleted, the async CRUD methods, and ID primary key definition.
<para></para>
包括 CreateTime/UpdateTime/IsDeleted、CRUD 异步方法、以及 ID 主键定义 的实体基类
<para></para>
When TKey is int/long, the Id is set to be an auto-incremented primary key
<para></para>
当 TKey 为 int/long 时Id 主键被设为自增值主键
</summary>
<typeparam name="TEntity"></typeparam>
@@ -85,11 +104,13 @@
</member>
<member name="P:FreeSql.BaseEntityAsync`2.Id">
<summary>
Primary key <br />
主键
</summary>
</member>
<member name="M:FreeSql.BaseEntityAsync`2.FindAsync(`1)">
<summary>
Get data based on the value of the primary key <br />
根据主键值获取数据
</summary>
<param name="id"></param>
@@ -97,106 +118,118 @@
</member>
<member name="T:FreeSql.BaseEntityAsync`1">
<summary>
Entity base class, including CreateTime/UpdateTime/IsDeleted, and async CRUD methods.
<para></para>
包括 CreateTime/UpdateTime/IsDeleted、以及 CRUD 异步方法的实体基类
</summary>
<typeparam name="TEntity"></typeparam>
</member>
<member name="M:FreeSql.BaseEntityAsync`1.DeleteAsync(System.Boolean)">
<summary>
To delete data <br />
删除数据
</summary>
<param name="physicalDelete">是否物理删除</param>
<param name="physicalDelete">To flag whether to delete the physical level of the data</param>
<returns></returns>
</member>
<member name="M:FreeSql.BaseEntityAsync`1.RestoreAsync">
<summary>
To recover deleted data <br />
恢复删除的数据
</summary>
<returns></returns>
</member>
<member name="M:FreeSql.BaseEntityAsync`1.UpdateAsync">
<summary>
To update data <br />
更新数据
</summary>
<returns></returns>
</member>
<member name="M:FreeSql.BaseEntityAsync`1.InsertAsync">
<summary>
To insert data <br />
插入数据
</summary>
</member>
<member name="M:FreeSql.BaseEntityAsync`1.SaveAsync">
<summary>
To insert or update data <br />
更新或插入
</summary>
<returns></returns>
</member>
<member name="M:FreeSql.BaseEntityAsync`1.SaveManyAsync(System.String)">
<summary>
To completely save the navigation properties of the entity in the form of sub-tables. <br />
【完整】保存导航属性,子表
</summary>
<param name="navigatePropertyName">导航属性名</param>
<param name="navigatePropertyName">Navigation property name</param>
</member>
<member name="T:FreeSql.BaseEntity">
<summary>
Entity base class, including CreateTime/UpdateTime/IsDeleted.
<para></para>
包括 CreateTime/UpdateTime/IsDeleted 的实体基类
</summary>
</member>
<member name="P:FreeSql.BaseEntity.Orm">
<summary>
全局 IFreeSql orm 对象
</summary>
</member>
<member name="M:FreeSql.BaseEntity.Initialization(IFreeSql,System.Func{FreeSql.IUnitOfWork})">
<summary>
初始化BaseEntity
BaseEntity.Initialization(new FreeSqlBuilder()
<para></para>
.UseAutoSyncStructure(true)
<para></para>
.UseConnectionString(DataType.Sqlite, "data source=test.db;max pool size=5")
<para></para>
.Build());
</summary>
<param name="fsql">IFreeSql orm 对象</param>
<param name="resolveUow">工作单元(事务)委托,如果不使用事务请传 null<para></para>解释由于AsyncLocal平台兼容不好所以交给外部管理</param>
</member>
<member name="P:FreeSql.BaseEntity.CreateTime">
<summary>
Created time <br />
创建时间
</summary>
</member>
<member name="P:FreeSql.BaseEntity.UpdateTime">
<summary>
Updated time <br />
更新时间
</summary>
</member>
<member name="P:FreeSql.BaseEntity.IsDeleted">
<summary>
Logical Delete <br />
逻辑删除
</summary>
</member>
<member name="P:FreeSql.BaseEntity.Sort">
<summary>
Sort <br />
排序
</summary>
</member>
<member name="T:FreeSql.BaseEntityReadOnly`1">
<summary>
A readonly entity base class, including CreateTime/UpdateTime/IsDeleted.
<para></para>
包括 CreateTime/UpdateTime/IsDeleted 的只读实体基类
</summary>
<typeparam name="TEntity"></typeparam>
</member>
<member name="P:FreeSql.BaseEntityReadOnly`1.Select">
<summary>
To query data <br />
查询数据
</summary>
<returns></returns>
</member>
<member name="M:FreeSql.BaseEntityReadOnly`1.Where(System.Linq.Expressions.Expression{System.Func{`0,System.Boolean}})">
<summary>
查询条件Where(a => a.Id > 10)支持导航对象查询Where(a => a.Author.Email == "2881099@qq.com")
Query conditions <br />
查询条件Where(a => a.Id> 10)
<para></para>
Support navigation object query <br />
支持导航对象查询Where(a => a.Author.Email == "2881099@qq.com")
</summary>
<param name="exp">lambda表达式</param>
<returns></returns>
</member>
<member name="M:FreeSql.BaseEntityReadOnly`1.WhereIf(System.Boolean,System.Linq.Expressions.Expression{System.Func{`0,System.Boolean}})">
<summary>
查询条件Where(true, a => a.Id > 10)支导航对象查询Where(true, a => a.Author.Email == "2881099@qq.com")
Query conditions <br />
查询条件Where(true, a => a.Id > 10)
<para></para>
Support navigation object query <br />
支导航对象查询Where(true, a => a.Author.Email == "2881099@qq.com")
</summary>
<param name="condition">true 时生效</param>
<param name="exp">lambda表达式</param>
@@ -204,12 +237,14 @@
</member>
<member name="P:FreeSql.BaseEntityReadOnly`1.Repository">
<summary>
Repository object. <br />
仓储对象
</summary>
</member>
<member name="M:FreeSql.BaseEntityReadOnly`1.Attach">
<summary>
附加实体,在更新数据时,只更新变化的部分
To Attach entities. When updating data, only the changed part is updated. <br />
附加实体。在更新数据时,只更新变化的部分
</summary>
</member>
</members>

View File

@@ -0,0 +1,133 @@
[中文](README.zh-CN.MD) | **English**
# Preface
I have tried ADO.NET, Dapper, EF, and Repository storage, and even wrote a generator tool myself to do common CRUD operations.
Their operation is inconvenient:
- Need to declare before use;
- Each entity class corresponds to an operation class (or DAL, DbContext, Repository).
BaseEntity is a very simple way of CodeFirst development, especially for single-table or multi-table CRUD operations. BaseEntity uses "inheritance" to save the repetitive code (creation time, ID and other fields) and functions of each entity class, and at the same time, it is not necessary to consider the use of repository when performing CURD operations.
This article will introduce a very simple CRUD operation method of BaseEntity.
# Features
- Automatically migrate the entity structure (CodeFirst) to the database;
- Directly perform CRUD operations on entities;
- Simplify user-defined entity types, eliminating hard-coded primary keys, common fields and their configuration (such as CreateTime, UpdateTime);
- Logic delete of single-table and multi-table query;
# Declaring
> dotnet add package FreeSql.Extensions.BaseEntity
> dotnet add package FreeSql.Provider.Sqlite
```csharp
BaseEntity.Initialization(fsql, null);
```
1. Define an auto-increment primary key of type `int`. When the `TKey` of `BaseEntity` is specified as `int/long`, the primary key will be considered as auto-increment;
```csharp
public class UserGroup : BaseEntity<UserGroup, int>
{
public string GroupName { get; set; }
}
```
If you don't want the primary key to be an auto-increment key, you can override the attribute:
```csharp
public class UserGroup : BaseEntity<UserGroup, int>
{
[Column(IsIdentity = false)]
public override int Id { get; set; }
public string GroupName { get; set; }
}
```
> For more information about the attributes of entities, please refer to: https://github.com/dotnetcore/FreeSql/wiki/Entity-Attributes
2. Define an entity whose primary key is Guid type, when saving data, it will automatically generate ordered and non-repeated Guid values (you don't need to specify `Guid.NewGuid()` yourself);
```csharp
public class User : BaseEntity<UserGroup, Guid>
{
public string UserName { get; set; }
}
```
# Usage of CRUD
```csharp
//Insert Data
var item = new UserGroup { GroupName = "Group One" };
item.Insert();
//Update Data
item.GroupName = "Group Two";
item.Update();
//Insert or Update Data
item.Save();
//Logic Delete
item.Delete();
//Recover Logic Delete
item.Restore();
//Get the object by the primary key
var item = UserGroup.Find(1);
//Query Data
var items = UserGroup.Where(a => a.Id > 10).ToList();
```
`{ENTITY_TYPE}.Select` returns a query object, the same as `FreeSql.ISelect`.
In the multi-table query, the logic delete condition will be attached to the query of each table.
> For more information about query data, please refer to: https://github.com/2881099/FreeSql/wiki/Query-Data
# Transaction Suggestion
Because the `AsyncLocal` platform is not compatible, the transaction is managed by the outside.
```csharp
static AsyncLocal<IUnitOfWork> _asyncUow = new AsyncLocal<IUnitOfWork>();
BaseEntity.Initialization(fsql, () => _asyncUow.Value);
```
At the beginning of `Scoped`: `_asyncUow.Value = fsql.CreateUnitOfWork();` (You can also use the `UnitOfWorkManager` object to get uow)
At the end of `Scoped`: `_asyncUow.Value = null;`
as follows:
```csharp
using (var uow = fsql.CreateUnitOfWork())
{
_asyncUow.Value = uow;
try
{
//todo ... BaseEntity internal CURD method keeps using uow transaction
}
finally
{
_asyncUow.Value = null;
}
uow.Commit();
}
```

View File

@@ -1,4 +1,6 @@
# 前言
**中文** | [English](README.MD)
# 前言
尝试过 ado.net、dapper、ef以及Repository仓储甚至自己还写过生成器工具以便做常规CRUD操作。
@@ -28,6 +30,10 @@ BaseEntity 是一种极简单的 CodeFirst 开发方式,特别对单表或多
> dotnet add package FreeSql.Provider.Sqlite
```csharp
BaseEntity.Initialization(fsql, null);
```
1、定义一个主键 int 并且自增的实体类型BaseEntity TKey 指定为 int/long 时,会认为主键是自增;
```csharp
@@ -47,7 +53,7 @@ public class UserGroup : BaseEntity<UserGroup, int>
public string GroupName { get; set; }
}
```
> 有关更多实体的特性配置请参考资料https://github.com/2881099/FreeSql/wiki/%e5%ae%9e%e4%bd%93%e7%89%b9%e6%80%a7
> 有关更多实体的特性配置请参考资料https://github.com/dotnetcore/FreeSql/wiki/%e5%ae%9e%e4%bd%93%e7%89%b9%e6%80%a7
2、定义一个主键 Guid 的实体类型,保存数据时会自动产生有序不重复的 Guid 值(不用自己指定 Guid.NewGuid()
@@ -93,6 +99,17 @@ var items = UserGroup.Where(a => a.Id > 10).ToList();
# 事务建议
1、同线程事务不支持异步
```c#
fsql.Transaction(() =>
{
//todo ...
})
```;
2、如果你是异步控
由于 AsyncLocal 平台兼容不好,所以交给外部管理事务。
```csharp
@@ -101,7 +118,7 @@ static AsyncLocal<IUnitOfWork> _asyncUow = new AsyncLocal<IUnitOfWork>();
BaseEntity.Initialization(fsql, () => _asyncUow.Value);
```
在 Scoped 开始时: _asyncUow.Value = fsql.CreateUnitOfWork(); (也可以使用 UnitOfWorkManager 对象获取 uow)
在 Scoped 开始时_asyncUow.Value = fsql.CreateUnitOfWork(); (也可以使用 UnitOfWorkManager 对象获取 uow)
在 Scoped 结束时_asyncUow.Value = null;

View File

@@ -0,0 +1,141 @@
using FreeSql;
using FreeSql.Internal;
using FreeSql.Internal.CommonProvider;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using System.Text;
using DbContext = Microsoft.EntityFrameworkCore.DbContext;
public static partial class EFModelExtensions
{
/// <summary>
/// EFCore ModelBuilder 与 FreeSql 打通实体特性配置(实现室)
/// </summary>
/// <param name="codeFirst"></param>
/// <param name="dbContextTypes"></param>
public static ICodeFirst ApplyConfigurationFromEFCore(this ICodeFirst codeFirst, params Type[] dbContextTypes)
{
var util = (codeFirst as CodeFirstProvider)._commonUtils;
var globalFilters = typeof(GlobalFilter).GetField("_filters", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(util._orm.GlobalFilter) as ConcurrentDictionary<string, GlobalFilter.Item>;
var globalFiltersIndex = 55100;
string QuoteSqlName(string name) => util.QuoteSqlName(name.Replace(".", "_-_dot_-_")).Replace("_-_dot_-_", ".");
foreach (var type in dbContextTypes)
{
if (type == null) throw new ArgumentNullException(nameof(dbContextTypes));
if (!typeof(DbContext).IsAssignableFrom(type)) throw new ArgumentException($"类型 {type.FullName} 不是 DbContext");
var dbContext = Activator.CreateInstance(type);
if (dbContext == null) throw new InvalidOperationException($"无法创建 DbContext 实例: {type.FullName}");
using (dbContext as IDisposable)
{
var dbContextModel = ((DbContext)dbContext).Model;
var defaultSchema = dbContextModel.GetDefaultSchema();
foreach (var entityEf in dbContextModel.GetEntityTypes())
{
if (entityEf.IsOwned()) continue;
var queryFilter = entityEf.GetQueryFilter();
if (queryFilter != null)
{
var globalFilterName = $"efcore_{++globalFiltersIndex}";
var globalFilterItem = new GlobalFilter.Item();
typeof(GlobalFilter.Item).GetProperty("Id").SetValue(globalFilterItem, globalFiltersIndex);
typeof(GlobalFilter.Item).GetProperty("Name").SetValue(globalFilterItem, globalFilterName);
typeof(GlobalFilter.Item).GetProperty("Where").SetValue(globalFilterItem, queryFilter);
typeof(GlobalFilter.Item).GetProperty("Only").SetValue(globalFilterItem, true);
globalFilters.TryAdd(globalFilterName, globalFilterItem);
}
codeFirst.Entity(entityEf.ClrType, entity =>
{
var schema = entityEf.GetSchema();
if (string.IsNullOrWhiteSpace(schema)) schema = defaultSchema;
var tbname = entityEf.GetTableName() ?? entityEf.GetViewName();
entity.ToTable($"{(string.IsNullOrWhiteSpace(schema) ? $"{QuoteSqlName(schema)}." : "")}{QuoteSqlName(tbname)}");
var pk = entityEf.FindPrimaryKey();
if (pk != null) entity.HasKey(string.Join(",", pk.Properties.Select(a => a.PropertyInfo.Name)));
var props = new List<string>();
foreach (var propEf in entityEf.GetProperties())
{
if (propEf.PropertyInfo == null) continue;
props.Add(propEf.PropertyInfo.Name);
var prop = entity.Property(propEf.PropertyInfo.Name);
prop.HasColumnName(propEf.GetColumnName());
prop.HasColumnType(propEf.GetColumnType());
var isIdentity = propEf.ValueGenerated == ValueGenerated.OnAdd &&
propEf.IsKey() &&
(propEf.ClrType == typeof(int) || propEf.ClrType == typeof(long));
if (isIdentity)
{
foreach (var anno in propEf.GetAnnotations())
{
if (anno.Name.EndsWith("ValueGenerationStrategy") && anno.Value != null && anno.Value.Equals(2))
{
isIdentity = true;
break;
}
}
}
prop.Help().IsIdentity(isIdentity);
if (!propEf.IsColumnNullable()) prop.IsRequired();
prop.HasDefaultValueSql(propEf.GetDefaultValueSql());
var maxLen = propEf.GetMaxLength();
if (maxLen != null) prop.HasMaxLength(maxLen.Value);
var precision = propEf.GetPrecision();
var scale = propEf.GetScale();
if (precision != null && scale != null) prop.HasPrecision(precision.Value, scale.Value);
else if (precision != null) prop.HasPrecision(precision.Value);
else if (scale != null) prop.HasPrecision(20, scale.Value);
if (propEf.IsConcurrencyToken) prop.IsRowVersion();
//var position = propEf.GetColumnOrder();
//if (position != null) prop.Position((short)position.Value);
}
foreach (var prop in entityEf.ClrType.GetProperties())
{
if (props.Contains(prop.Name)) continue;
var isIgnore = false;
var setMethod = prop.GetSetMethod(true); //trytb.Type.GetMethod($"set_{p.Name}");
var tp = codeFirst.GetDbInfo(prop.PropertyType);
if (setMethod == null || (tp == null && prop.PropertyType.IsValueType)) // 属性没有 set自动忽略
isIgnore = true;
if (tp == null && isIgnore == false) continue; //导航属性
entity.Property(prop.Name).Help().IsIgnore(true);
}
var navsEf = entityEf.GetNavigations();
foreach (var navEf in navsEf)
{
if (navEf.ForeignKey.DeclaringEntityType.IsOwned()) continue;
if (navEf.IsCollection)
{
var navFluent = entity.HasMany(navEf.Name);
if (navEf.Inverse != null)
{
if (navEf.Inverse.IsCollection)
navFluent.WithMany(navEf.Inverse.Name, typeof(int));
else
navFluent.WithOne(navEf.Inverse.Name).HasForeignKey(string.Join(",", navEf.Inverse.ForeignKey.Properties.Select(a => a.Name)));
}
}
else
{
var navFluent = entity.HasOne(navEf.Name);
if (navEf.Inverse != null)
{
if (navEf.Inverse.IsCollection)
navFluent.WithMany(navEf.Inverse.Name).HasForeignKey(string.Join(",", navEf.Inverse.ForeignKey.Properties.Select(a => a.Name)));
else
navFluent.WithOne(navEf.Inverse.Name, string.Join(",", navEf.Inverse.ForeignKey.Properties.Select(a => a.Name))).HasForeignKey(string.Join(",", navEf.ForeignKey.Properties.Select(a => a.Name)));
}
}
}
});
}
}
}
return codeFirst;
}
}

View File

@@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net9.0;net8.0;net7.0;net6.0;</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>FreeSql;ncc;YeXiangQin</Authors>
<Description>FreeSql 扩展包EFCore ModelBuilder 与 FreeSql 打通实体特性配置(实现室).</Description>
<PackageProjectUrl>https://github.com/dotnetcore/FreeSql/wiki/%E8%81%9A%E5%90%88%E6%A0%B9%EF%BC%88%E5%AE%9E%E9%AA%8C%E5%AE%A4%EF%BC%89</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnetcore/FreeSql/wiki/%E8%81%9A%E5%90%88%E6%A0%B9%EF%BC%88%E5%AE%9E%E9%AA%8C%E5%AE%A4%EF%BC%89</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageTags>FreeSql;ORM</PackageTags>
<PackageId>$(AssemblyName)</PackageId>
<PackageIcon>logo.png</PackageIcon>
<Title>$(AssemblyName)</Title>
<IsPackable>true</IsPackable>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<Version>3.5.210-preview20250626</Version>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="../../readme.md" Pack="true" PackagePath="\" />
<None Include="../../logo.png" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\FreeSql.DbContext\FreeSql.DbContext.csproj" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<DocumentationFile>FreeSql.Extensions.EFModel.xml</DocumentationFile>
<WarningLevel>3</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net40'">
<DefineConstants>net40</DefineConstants>
</PropertyGroup>
</Project>

Binary file not shown.

View File

@@ -1,13 +1,12 @@
using System;
using System.Linq;
// ReSharper disable once CheckNamespace
namespace FreeSql.DataAnnotations
{
/// <summary>
/// 当实体类属性为【对象】时以JSON形式映射存储
/// When the entity class property is <see cref="object"/>, map storage in JSON format. <br />
/// 当实体类属性为【对象】时,以 JSON 形式映射存储
/// </summary>
public class JsonMapAttribute : Attribute
{
}
}
[AttributeUsage(AttributeTargets.Property)]
public class JsonMapAttribute : Attribute { }
}

View File

@@ -1,41 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks>
<Version>2.5.100</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>FreeSql;ncc;YeXiangQin</Authors>
<Description>FreeSql 扩展包可实现实体类属性为对象时以JSON形式映射存储.</Description>
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageTags>FreeSql;ORM</PackageTags>
<PackageId>$(AssemblyName)</PackageId>
<PackageIcon>logo.png</PackageIcon>
<Title>$(AssemblyName)</Title>
<IsPackable>true</IsPackable>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>FreeSql;ncc;YeXiangQin</Authors>
<Description>FreeSql 扩展包可实现实体类属性为对象时以JSON形式映射存储.</Description>
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageTags>FreeSql;ORM</PackageTags>
<PackageId>$(AssemblyName)</PackageId>
<PackageIcon>logo.png</PackageIcon>
<Title>$(AssemblyName)</Title>
<IsPackable>true</IsPackable>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<Version>3.5.210-preview20250626</Version>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="../../logo.png" Pack="true" PackagePath="\" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<DocumentationFile>FreeSql.Extensions.JsonMap.xml</DocumentationFile>
<WarningLevel>3</WarningLevel>
</PropertyGroup>
<ItemGroup>
<None Include="../../readme.md" Pack="true" PackagePath="\"/>
<None Include="../../logo.png" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<DocumentationFile>FreeSql.Extensions.JsonMap.xml</DocumentationFile>
<WarningLevel>3</WarningLevel>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
</ItemGroup>
</Project>

View File

@@ -6,11 +6,13 @@
<members>
<member name="T:FreeSql.DataAnnotations.JsonMapAttribute">
<summary>
当实体类属性为【对象】时以JSON形式映射存储
When the entity class property is <see cref="T:System.Object"/>, map storage in JSON format. <br />
当实体类属性为【对象】时,以 JSON 形式映射存储
</summary>
</member>
<member name="M:FreeSqlJsonMapCoreExtensions.UseJsonMap(IFreeSql)">
<summary>
When the entity class property is <see cref="T:System.Object"/> and the attribute is marked as <see cref="T:FreeSql.DataAnnotations.JsonMapAttribute"/>, map storage in JSON format. <br />
当实体类属性为【对象】时,并且标记特性 [JsonMap] 时该属性将以JSON形式映射存储
</summary>
<returns></returns>

View File

@@ -1,20 +1,23 @@
using FreeSql.DataAnnotations;
using FreeSql;
using FreeSql.DataAnnotations;
using FreeSql.Internal.CommonProvider;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading;
public static class FreeSqlJsonMapCoreExtensions
{
static int _isAoped = 0;
static ConcurrentDictionary<Type, bool> _dicTypes = new ConcurrentDictionary<Type, bool>();
static MethodInfo MethodJsonConvertDeserializeObject = typeof(JsonConvert).GetMethod("DeserializeObject", new[] { typeof(string), typeof(Type) });
static MethodInfo MethodJsonConvertDeserializeObject = typeof(JsonConvert).GetMethod("DeserializeObject", new[] { typeof(string), typeof(Type), typeof(JsonSerializerSettings) });
static MethodInfo MethodJsonConvertSerializeObject = typeof(JsonConvert).GetMethod("SerializeObject", new[] { typeof(object), typeof(JsonSerializerSettings) });
static ConcurrentDictionary<Type, ConcurrentDictionary<string, bool>> _dicJsonMapFluentApi = new ConcurrentDictionary<Type, ConcurrentDictionary<string, bool>>();
static object _concurrentObj = new object();
public static ColumnFluent JsonMap(this ColumnFluent col)
{
@@ -24,44 +27,287 @@ public static class FreeSqlJsonMapCoreExtensions
}
/// <summary>
/// When the entity class property is <see cref="object"/> and the attribute is marked as <see cref="JsonMapAttribute"/>, map storage in JSON format. <br />
/// 当实体类属性为【对象】时,并且标记特性 [JsonMap] 时该属性将以JSON形式映射存储
/// </summary>
/// <returns></returns>
public static void UseJsonMap(this IFreeSql that)
public static void UseJsonMap(this IFreeSql fsql)
{
UseJsonMap(that, JsonConvert.DefaultSettings?.Invoke() ?? new JsonSerializerSettings());
UseJsonMap(fsql, JsonConvert.DefaultSettings?.Invoke() ?? new JsonSerializerSettings());
}
public static void UseJsonMap(this IFreeSql that, JsonSerializerSettings settings)
public static void UseJsonMap(this IFreeSql fsql, JsonSerializerSettings settings)
{
if (Interlocked.CompareExchange(ref _isAoped, 1, 0) == 0)
{
FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionSwitchTypeFullName.Add((LabelTarget returnTarget, Expression valueExp, Type type) =>
{
if (_dicTypes.ContainsKey(type)) return Expression.Return(returnTarget, Expression.TypeAs(Expression.Call(MethodJsonConvertDeserializeObject, Expression.Convert(valueExp, typeof(string)), Expression.Constant(type)), type));
if (_dicTypes.ContainsKey(type)) return Expression.IfThenElse(
Expression.TypeIs(valueExp, type),
Expression.Return(returnTarget, valueExp),
Expression.Return(returnTarget, Expression.TypeAs(Expression.Call(MethodJsonConvertDeserializeObject, Expression.Convert(valueExp, typeof(string)), Expression.Constant(type),Expression.Constant(settings)), type))
);
return null;
});
}
that.Aop.ConfigEntityProperty += new EventHandler<FreeSql.Aop.ConfigEntityPropertyEventArgs>((s, e) =>
fsql.Aop.ConfigEntityProperty += (s, e) =>
{
var isJsonMap = e.Property.GetCustomAttributes(typeof(JsonMapAttribute), false).Any() || _dicJsonMapFluentApi.TryGetValue(e.EntityType, out var tryjmfu) && tryjmfu.ContainsKey(e.Property.Name);
if (isJsonMap)
{
e.ModifyResult.MapType = typeof(string);
e.ModifyResult.StringLength = -2;
if (_dicTypes.ContainsKey(e.Property.PropertyType) == false &&
FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple.ContainsKey(e.Property.PropertyType))
return; //基础类型使用 JsonMap 无效
if (e.ModifyResult.MapType == null)
{
e.ModifyResult.MapType = typeof(string);
e.ModifyResult.StringLength = -2;
}
if (_dicTypes.TryAdd(e.Property.PropertyType, true))
{
FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionObjectToStringIfThenElse.Add((LabelTarget returnTarget, Expression valueExp, Expression elseExp, Type type) =>
lock (_concurrentObj)
{
return Expression.IfThenElse(
Expression.TypeIs(valueExp, e.Property.PropertyType),
Expression.Return(returnTarget, Expression.Call(MethodJsonConvertSerializeObject, Expression.Convert(valueExp, typeof(object)), Expression.Constant(settings)), typeof(object)),
elseExp);
});
FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple[e.Property.PropertyType] = true;
FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionObjectToStringIfThenElse.Add((LabelTarget returnTarget, Expression valueExp, Expression elseExp, Type type) =>
{
return Expression.IfThenElse(
Expression.TypeIs(valueExp, e.Property.PropertyType),
Expression.Return(returnTarget, Expression.Call(MethodJsonConvertSerializeObject, Expression.Convert(valueExp, typeof(object)), Expression.Constant(settings)), typeof(object)),
elseExp);
});
}
}
}
});
}
}
};
switch (fsql.Ado.DataType)
{
case DataType.Sqlite:
case DataType.MySql:
case DataType.OdbcMySql:
case DataType.CustomMySql:
case DataType.SqlServer:
case DataType.OdbcSqlServer:
case DataType.CustomSqlServer:
case DataType.Oracle:
case DataType.OdbcOracle:
case DataType.CustomOracle:
case DataType.Dameng:
case DataType.DuckDB:
fsql.Aop.ParseExpression += (_, e) =>
{
//if (e.Expression is MethodCallExpression callExp)
//{
// var objExp = callExp.Object;
// var objType = objExp?.Type;
// if (objType?.FullName == "System.Byte[]") return;
// if (objType == null && callExp.Method.DeclaringType == typeof(Enumerable))
// {
// objExp = callExp.Arguments.FirstOrDefault();
// objType = objExp?.Type;
// }
// if (objType == null) objType = callExp.Method.DeclaringType;
// if (objType != null || objType.IsArrayOrList())
// {
// string left = null;
// switch (callExp.Method.Name)
// {
// case "Any":
// left = objExp == null ? null : getExp(objExp);
// if (left.StartsWith("(") || left.EndsWith(")")) left = $"array[{left.TrimStart('(').TrimEnd(')')}]";
// return $"(case when {left} is null then 0 else array_length({left},1) end > 0)";
// case "Contains":
// }
// }
//}
//处理 mysql enum -> int
switch (fsql.Ado.DataType)
{
case DataType.MySql:
case DataType.OdbcMySql:
case DataType.CustomMySql:
if (e.Expression.NodeType == ExpressionType.Equal &&
e.Expression is BinaryExpression binaryExpression)
{
var comonExp = (fsql.Select<object>() as Select0Provider)._commonExpression;
var leftExp = binaryExpression.Left;
var rightExp = binaryExpression.Right;
if (
leftExp.NodeType == ExpressionType.Convert &&
leftExp is UnaryExpression leftExpUexp &&
leftExpUexp.Operand?.Type.NullableTypeOrThis().IsEnum == true &&
rightExp.NodeType == ExpressionType.Convert &&
rightExp is UnaryExpression rightExpUexp &&
rightExpUexp.Operand?.Type.NullableTypeOrThis().IsEnum == true)
{
string leftSql = null, rightSql = null;
if (leftExpUexp.Operand.NodeType == ExpressionType.MemberAccess &&
LocalParseMemberExp(leftExpUexp.Operand as MemberExpression))
leftSql = e.Result;
if (rightExpUexp.Operand.NodeType == ExpressionType.MemberAccess &&
LocalParseMemberExp(rightExpUexp.Operand as MemberExpression))
rightSql = e.Result;
if (!string.IsNullOrEmpty(leftSql) && string.IsNullOrEmpty(rightSql) && !rightExpUexp.Operand.IsParameter())
rightSql = comonExp.formatSql(Expression.Lambda(rightExpUexp.Operand).Compile().DynamicInvoke(), typeof(int), null, null);
if (string.IsNullOrEmpty(leftSql) && !string.IsNullOrEmpty(rightSql) && !leftExpUexp.Operand.IsParameter())
leftSql = comonExp.formatSql(Expression.Lambda(leftExpUexp.Operand).Compile().DynamicInvoke(), typeof(int), null, null);
if (!string.IsNullOrEmpty(leftSql) && !string.IsNullOrEmpty(rightSql))
{
e.Result = $"{leftSql} = {rightSql}";
return;
}
e.Result = null;
return;
}
}
if (e.Expression.NodeType == ExpressionType.Call &&
e.Expression is MethodCallExpression callExp &&
callExp.Method.Name == "Contains")
{
var objExp = callExp.Object;
var objType = objExp?.Type;
if (objType?.FullName == "System.Byte[]") return;
var argIndex = 0;
if (objType == null && callExp.Method.DeclaringType == typeof(Enumerable))
{
objExp = callExp.Arguments.FirstOrDefault();
objType = objExp?.Type;
argIndex++;
if (objType == typeof(string)) return;
}
if (objType == null) objType = callExp.Method.DeclaringType;
if (objType != null || objType.IsArrayOrList())
{
var memExp = callExp.Arguments[argIndex];
if (memExp.NodeType == ExpressionType.MemberAccess &&
memExp.Type.NullableTypeOrThis().IsEnum &&
LocalParseMemberExp(memExp as MemberExpression))
{
if (!objExp.IsParameter())
{
var comonExp = (fsql.Select<object>() as Select0Provider)._commonExpression;
var rightSql = comonExp.formatSql(Expression.Lambda(objExp).Compile().DynamicInvoke(), typeof(int), null, null);
e.Result = $"{e.Result} in {rightSql.Replace(", \r\n \r\n", $") \r\n OR {e.Result} in (")}";
return;
}
e.Result = null;
return;
}
}
}
break;
}
//解析 POCO Json a.Customer.Name
if (e.Expression.NodeType == ExpressionType.MemberAccess)
LocalParseMemberExp(e.Expression as MemberExpression);
bool LocalParseMemberExp(MemberExpression memExp)
{
if (memExp == null) return false;
if (e.Expression.IsParameter() == false) return false;
var parentMemExps = new Stack<MemberExpression>();
parentMemExps.Push(memExp);
while (true)
{
switch (memExp.Expression.NodeType)
{
case ExpressionType.MemberAccess:
case ExpressionType.Parameter: break;
default: return false;
}
switch (memExp.Expression.NodeType)
{
case ExpressionType.MemberAccess:
memExp = memExp.Expression as MemberExpression;
if (memExp == null) return false;
parentMemExps.Push(memExp);
break;
case ExpressionType.Parameter:
var tb = fsql.CodeFirst.GetTableByEntity(memExp.Expression.Type);
if (tb == null) return false;
if (tb.ColumnsByCs.TryGetValue(parentMemExps.Pop().Member.Name, out var trycol) == false) return false;
if (_dicTypes.ContainsKey(trycol.CsType) == false) return false;
var result = e.FreeParse(Expression.MakeMemberAccess(memExp.Expression, tb.Properties[trycol.CsName]));
if (parentMemExps.Any() == false)
{
e.Result = result;
return true;
}
var jsonPath = "";
switch (fsql.Ado.DataType)
{
case DataType.Sqlite:
case DataType.MySql:
case DataType.OdbcMySql:
case DataType.CustomMySql:
StyleJsonExtract();
return true;
case DataType.SqlServer:
case DataType.OdbcSqlServer:
case DataType.CustomSqlServer:
case DataType.Oracle:
case DataType.OdbcOracle:
case DataType.CustomOracle:
case DataType.Dameng:
StyleJsonValue();
return true;
case DataType.DuckDB:
StyleDotAccess();
return true;
}
StylePgJson();
return true;
void StyleJsonExtract()
{
while (parentMemExps.Any())
{
memExp = parentMemExps.Pop();
jsonPath = $"{jsonPath}.{memExp.Member.Name}";
}
e.Result = $"json_extract({result},'${jsonPath}')";
}
void StyleJsonValue()
{
while (parentMemExps.Any())
{
memExp = parentMemExps.Pop();
jsonPath = $"{jsonPath}.{memExp.Member.Name}";
}
e.Result = $"json_value({result},'${jsonPath}')";
}
void StyleDotAccess()
{
while (parentMemExps.Any())
{
memExp = parentMemExps.Pop();
result = $"{result}['{memExp.Member.Name}']";
}
e.Result = result;
}
void StylePgJson()
{
while (parentMemExps.Any())
{
memExp = parentMemExps.Pop();
var opt = parentMemExps.Any() ? "->" : $"->>{(memExp.Type.IsArrayOrList() ? "/*json array*/" : "")}";
result = $"{result}{opt}'{memExp.Member.Name}'";
}
e.Result = result;
}
}
}
}
};
break;
}
}
}

View File

@@ -0,0 +1,25 @@
[中文](README.zh-CN.MD) | **English**
FreeSql extension package, map *ValueObject* to `typeof(string)`, install the extension package:
> dotnet add package FreeSql.Extensions.JsonMap
```csharp
fsql.UseJsonMap(); //Turn on function
class TestConfig
{
public int clicks { get; set; }
public string title { get; set; }
}
[Table(Name = "sysconfig")]
public class S_SysConfig<T>
{
[Column(IsPrimary = true)]
public string Name { get; set; }
[JsonMap]
public T Config { get; set; }
}
```

View File

@@ -1,4 +1,6 @@
FreeSql 扩展包,将值对象映射成 typeof(string),安装扩展包:
**中文** | [English](README.MD)
FreeSql 扩展包,将值对象映射成 `typeof(string)`,安装扩展包:
> dotnet add package FreeSql.Extensions.JsonMap
@@ -20,4 +22,4 @@ public class S_SysConfig<T>
[JsonMap]
public T Config { get; set; }
}
```
```

View File

@@ -1,46 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net45;net40</TargetFrameworks>
<Version>2.5.100</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>FreeSql;ncc;YeXiangQin</Authors>
<Description>FreeSql 扩展包,可实现【延时加载】属性.</Description>
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageTags>FreeSql;ORM</PackageTags>
<PackageId>$(AssemblyName)</PackageId>
<PackageIcon>logo.png</PackageIcon>
<Title>$(AssemblyName)</Title>
<IsPackable>true</IsPackable>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>netstandard2.1;netstandard2.0;net45;net40</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>FreeSql;ncc;YeXiangQin</Authors>
<Description>FreeSql 扩展包,可实现【延时加载】属性.</Description>
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageTags>FreeSql;ORM</PackageTags>
<PackageId>$(AssemblyName)</PackageId>
<PackageIcon>logo.png</PackageIcon>
<Title>$(AssemblyName)</Title>
<IsPackable>true</IsPackable>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<Version>3.5.210-preview20250626</Version>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="../../logo.png" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="CS-Script.Core" Version="1.2.3" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
<PackageReference Include="CS-Script.Core" Version="1.3.1" />
</ItemGroup>
<!--
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1'">
<PackageReference Include="DotNetCore.Natasha" Version="2.12.0" />
</ItemGroup>
-->
<ItemGroup>
<None Include="../../readme.md" Pack="true" PackagePath="\"/>
<None Include="../../logo.png" Pack="true" PackagePath="\" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1'">
<DefineConstants>ns20;netstandard20</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'netstandard2.2'))">
<PackageReference Include="CS-Script.Core" Version="1.3.1" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'netstandard2.0'))">
<PackageReference Include="CS-Script.Core" Version="1.2.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
</ItemGroup>
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'netstandard2.0'))">
<DefineConstants>ns20;netstandard20</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,95 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq.Expressions;
using System.Linq;
using System.Reflection;
namespace FreeSql.Extensions.Linq
{
public static class ExprHelper
{
public static object GetConstExprValue(this Expression exp)
{
if (exp.IsParameter()) return null;
var expStack = new Stack<Expression>();
var exp2 = exp;
while (true)
{
switch (exp2?.NodeType)
{
case ExpressionType.Constant:
expStack.Push(exp2);
break;
case ExpressionType.MemberAccess:
expStack.Push(exp2);
exp2 = (exp2 as MemberExpression).Expression;
if (exp2 == null) break;
continue;
case ExpressionType.Call:
return Expression.Lambda(exp).Compile().DynamicInvoke();
case ExpressionType.TypeAs:
case ExpressionType.Convert:
var oper2 = (exp2 as UnaryExpression).Operand;
if (oper2.NodeType == ExpressionType.Parameter)
{
var oper2Parm = oper2 as ParameterExpression;
expStack.Push(exp2.Type.IsAbstract || exp2.Type.IsInterface ? oper2Parm : Expression.Parameter(exp2.Type, oper2Parm.Name));
}
else
expStack.Push(oper2);
break;
}
break;
}
object firstValue = null;
switch (expStack.First().NodeType)
{
case ExpressionType.Constant:
var expStackFirst = expStack.Pop() as ConstantExpression;
firstValue = expStackFirst?.Value;
break;
case ExpressionType.MemberAccess:
var expStackFirstMem = expStack.First() as MemberExpression;
if (expStackFirstMem.Expression?.NodeType == ExpressionType.Constant)
firstValue = (expStackFirstMem.Expression as ConstantExpression)?.Value;
else
return Expression.Lambda(exp).Compile().DynamicInvoke();
break;
}
while (expStack.Any())
{
var expStackItem = expStack.Pop();
switch (expStackItem.NodeType)
{
case ExpressionType.MemberAccess:
var memExp = expStackItem as MemberExpression;
if (memExp.Member.MemberType == MemberTypes.Property)
firstValue = ((PropertyInfo)memExp.Member).GetValue(firstValue, null);
else if (memExp.Member.MemberType == MemberTypes.Field)
firstValue = ((FieldInfo)memExp.Member).GetValue(firstValue);
break;
}
}
return firstValue;
}
public static bool IsParameter(this Expression exp)
{
var test = new TestParameterExpressionVisitor();
test.Visit(exp);
return test.Result;
}
internal class TestParameterExpressionVisitor : ExpressionVisitor
{
public bool Result { get; private set; }
protected override Expression VisitParameter(ParameterExpression node)
{
if (!Result) Result = true;
return node;
}
}
}
}

View File

@@ -1,41 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks>
<Version>2.5.100</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>FreeSql;ncc;YeXiangQin</Authors>
<Description>FreeSql 扩展包,实现 linq queryable 和 linq to sql 语法进行开发.</Description>
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageTags>FreeSql;ORM</PackageTags>
<PackageId>$(AssemblyName)</PackageId>
<PackageIcon>logo.png</PackageIcon>
<Title>$(AssemblyName)</Title>
<IsPackable>true</IsPackable>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>FreeSql;ncc;YeXiangQin</Authors>
<Description>FreeSql 扩展包,实现 linq queryable 和 linq to sql 语法进行开发.</Description>
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageTags>FreeSql;ORM</PackageTags>
<PackageId>$(AssemblyName)</PackageId>
<PackageIcon>logo.png</PackageIcon>
<Title>$(AssemblyName)</Title>
<IsPackable>true</IsPackable>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<Version>3.5.210-preview20250626</Version>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="../../logo.png" Pack="true" PackagePath="\" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<DocumentationFile>FreeSql.Extensions.Linq.xml</DocumentationFile>
<WarningLevel>3</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net40'">
<DefineConstants>net40</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="../../readme.md" Pack="true" PackagePath="\"/>
<None Include="../../logo.png" Pack="true" PackagePath="\" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<DocumentationFile>FreeSql.Extensions.Linq.xml</DocumentationFile>
<WarningLevel>3</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net40'">
<DefineConstants>net40</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
</ItemGroup>
</Project>

View File

@@ -33,7 +33,7 @@ public static class FreeSqlExtensionsLinqSql
/// <returns></returns>
public static ISelect<T1> RestoreToSelect<T1>(this IQueryable<T1> that) where T1 : class
{
var queryable = that as QueryableProvider<T1, T1> ?? throw new Exception($"无法将 IQueryable<{typeof(T1).Name}> 转换为 ISelect<{typeof(T1).Name}>,因为他的实现不是 FreeSql.Extensions.Linq.QueryableProvider");
var queryable = that as QueryableProvider<T1, T1> ?? throw new Exception(CoreErrorStrings.S_CannotBeConverted_To_ISelect(typeof(T1).Name));
return queryable._select;
}
@@ -70,7 +70,7 @@ public static class FreeSqlExtensionsLinqSql
internal static void InternalJoin2<T1>(Select1Provider<T1> s1p, LambdaExpression outerKeySelector, LambdaExpression innerKeySelector, LambdaExpression resultSelector) where T1 : class
{
s1p._tables[0].Parameter = resultSelector.Parameters[0];
s1p._commonExpression.ExpressionLambdaToSql(outerKeySelector, new CommonExpression.ExpTSC { _tables = s1p._tables });
s1p._commonExpression.ExpressionLambdaToSql(outerKeySelector, new CommonExpression.ExpTSC { _tables = s1p._tables, _tableRule = s1p._tableRule });
s1p.InternalJoin(Expression.Lambda(typeof(Func<,,>).MakeGenericType(typeof(T1), innerKeySelector.Parameters[0].Type, typeof(bool)),
Expression.Equal(outerKeySelector.Body, innerKeySelector.Body),
new[] { outerKeySelector.Parameters[0], innerKeySelector.Parameters[0] }

View File

@@ -40,7 +40,10 @@ namespace FreeSql.Extensions.Linq
yield return item;
}
}
IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)GetEnumerator();
}
public Type ElementType => typeof(QueryableProvider<TCurrent, TSource>);
public Expression Expression => _expression;
@@ -118,7 +121,7 @@ namespace FreeSql.Extensions.Linq
case "Average": return tplMaxMinAvgSum("Avg");
case "Concat":
return throwCallExp(" 不支持");
return throwCallExp(CoreErrorStrings.Not_Support);
case "Contains":
if (callExp.Arguments.Count == 2)
{
@@ -138,7 +141,7 @@ namespace FreeSql.Extensions.Linq
_select.Distinct();
break;
}
return throwCallExp(" 不支持");
return throwCallExp(CoreErrorStrings.Not_Support);
case "ElementAt":
case "ElementAtOrDefault":
@@ -175,7 +178,7 @@ namespace FreeSql.Extensions.Linq
_select.InternalWhere(whereParam);
break;
}
return throwCallExp(" 不支持");
return throwCallExp(CoreErrorStrings.Not_Support);
case "Skip":
_select.Offset((int)callExp.Arguments[1].GetConstExprValue());
@@ -187,7 +190,7 @@ namespace FreeSql.Extensions.Linq
case "ToList":
if (callExp.Arguments.Count == 1)
return _select.ToList();
return throwCallExp(" 不支持");
return throwCallExp(CoreErrorStrings.Not_Support);
case "Select":
var selectParam = (callExp.Arguments[1] as UnaryExpression)?.Operand as LambdaExpression;
@@ -196,7 +199,7 @@ namespace FreeSql.Extensions.Linq
_select._selectExpression = selectParam;
break;
}
return throwCallExp(" 不支持");
return throwCallExp(CoreErrorStrings.Not_Support);
case "Join":
if (callExp.Arguments.Count == 5)
@@ -238,13 +241,13 @@ namespace FreeSql.Extensions.Linq
case "Last":
case "LastOrDefault":
return throwCallExp(" 不支持");
return throwCallExp(CoreErrorStrings.Not_Support);
case "GroupBy":
return throwCallExp(" 不支持");
return throwCallExp(CoreErrorStrings.Not_Support);
default:
return throwCallExp(" 不支持");
return throwCallExp(CoreErrorStrings.Not_Support);
}
}
if (tresult == null) return null;

View File

@@ -67,19 +67,25 @@ namespace FreeSql.Internal.CommonProvider
var index = -10000; //临时规则,不返回 as1
if (selector != null)
_comonExp.ReadAnonymousField(_select._tables, field, _map, ref index, selector, null, null, _select._whereGlobalFilter, null, false); //不走 DTO 映射,不处理 IncludeMany
_comonExp.ReadAnonymousField(_select._tables, _select._tableRule, field, _map, ref index, selector, null, null, _select._whereGlobalFilter, null, null, false); //不走 DTO 映射,不处理 IncludeMany
_field = field.ToString();
}
public override string ParseExp(Expression[] members)
{
if (members.Any() == false) return _map.DbField;
ParseExpMapResult = null;
if (members.Any() == false)
{
ParseExpMapResult = _map;
return _map.DbField;
}
var read = _map;
for (var a = 0; a < members.Length; a++)
{
read = read.Childs.Where(z => z.CsName == (members[a] as MemberExpression)?.Member.Name).FirstOrDefault();
if (read == null) return null;
}
ParseExpMapResult = read;
return read.DbField;
}
@@ -162,7 +168,7 @@ namespace FreeSql.Internal.CommonProvider
{
if (condition == false) return this;
_lambdaParameter = column?.Parameters[0];
var sql = _comonExp.ExpressionWhereLambda(null, column, this, null, null);
var sql = _comonExp.ExpressionWhereLambda(null, null, column, this, null, null);
var method = _select.GetType().GetMethod("OrderBy", new[] { typeof(string), typeof(object) });
method.Invoke(_select, new object[] { descending ? $"{sql} DESC" : sql, null });
return this;
@@ -174,7 +180,7 @@ namespace FreeSql.Internal.CommonProvider
{
if (condition == false) return this;
_lambdaParameter = exp?.Parameters[0];
var sql = _comonExp.ExpressionWhereLambda(null, exp, this, null, null);
var sql = _comonExp.ExpressionWhereLambda(null, null, exp, this, null, null);
var method = _select.GetType().GetMethod("Where", new[] { typeof(string), typeof(object) });
method.Invoke(_select, new object[] { sql, null });
return this;

View File

@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>FreeSql;ncc;YeXiangQin</Authors>
<Description>FreeSql 扩展包,实现 低代码、零实体、ZeroEntity并且支持导航属性级联保存 等功能.</Description>
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageTags>FreeSql;ORM;低代码;</PackageTags>
<PackageId>$(AssemblyName)</PackageId>
<PackageIcon>logo.png</PackageIcon>
<Title>$(AssemblyName)</Title>
<IsPackable>true</IsPackable>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<Version>3.5.210-preview20250626</Version>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="../../readme.md" Pack="true" PackagePath="\" />
<None Include="../../logo.png" Pack="true" PackagePath="\" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<DocumentationFile>FreeSql.Extensions.ZeroEntity.xml</DocumentationFile>
<WarningLevel>3</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net40'">
<DefineConstants>net40</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,142 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>FreeSql.Extensions.ZeroEntity</name>
</assembly>
<members>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.#ctor(IFreeSql,FreeSql.Extensions.ZeroEntity.TableDescriptor[],System.Boolean)">
<summary>
创建新的ZeroDbCotext实例
</summary>
<param name="orm">IfreeSql 对象</param>
<param name="schemas">动态表结构描述</param>
<param name="syncStructure">是否强制同步表结构</param>
<exception cref="T:FreeSql.Extensions.ZeroEntity.Models.SchemaValidationResult"> Schema 未验证通过时抛出验证异常</exception>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.#ctor(IFreeSql)">
<summary>
初始化一个 ZeroDbContext 对象暂不指定任何Schema
</summary>
<param name="orm"></param>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SyncTableStructure(System.String)">
<summary>
同步指定表结构
</summary>
<param name="name"></param>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.LoadSchemasAndNavigates(System.String,System.Func{System.String,FreeSql.Extensions.ZeroEntity.TableDescriptor})">
<summary>
从自定义中加载(多表)<para></para>
- tableName 以及 Navigates 所依赖表 Schema
</summary>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.LoadSchemaFromDatabase(System.String)">
<summary>
从数据库中加载(单表)<para></para>
- 不支持 Navigates<para></para>
- 不支持 Indexes IndexMethod<para></para>
- 暂支持 SqlServer/MySql decimal(10,2)(其他数据库需实现对应 IDbFirst
</summary>
</member>
<member name="P:FreeSql.Extensions.ZeroEntity.ZeroDbContext.Select">
<summary>
【有状态管理】自动 Include 查询
</summary>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectNoTracking(System.String)">
<summary>
【无状态管理】指定表查询
</summary>
</member>
<member name="P:FreeSql.Extensions.ZeroEntity.ZeroDbContext.ChangeReport.ChangeInfo.BeforeObject">
<summary>
Type = Update 的时候,获取更新之前的对象
</summary>
</member>
<member name="P:FreeSql.Extensions.ZeroEntity.ZeroDbContext.ChangeReport.Report">
<summary>
实体变化记录
</summary>
</member>
<member name="P:FreeSql.Extensions.ZeroEntity.ZeroDbContext.ChangeReport.OnChange">
<summary>
实体变化事件
</summary>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.LeftJoin(System.String,System.String[])">
<summary>
举例1LeftJoin("table1", "id", "user.id") -> LEFT JOIN [table1] b ON b.[id] = a.[id]<para></para>
举例2LeftJoin("table1", "id", "user.id", "xid", "user.xid") -> LEFT JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
</summary>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.InnerJoin(System.String,System.String[])">
<summary>
举例1InnerJoin("table1", "id", "user.id") -> INNER JOIN [table1] b ON b.[id] = a.[id]<para></para>
举例2InnerJoin("table1", "id", "user.id", "xid", "user.xid") -> INNER JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
</summary>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.RightJoin(System.String,System.String[])">
<summary>
举例1RightJoin("table1", "id", "user.id") -> RIGTH JOIN [table1] b ON b.[id] = a.[id]<para></para>
举例2RightJoin("table1", "id", "user.id", "xid", "user.xid") -> RIGTH JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
</summary>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.Where(System.Collections.Generic.IEnumerable{System.Collections.Generic.Dictionary{System.String,System.Object}})">
<summary>
WHERE [Id] IN (1,2,3)
</summary>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.WhereIn(System.String,System.Object)">
<summary>
WHERE [field] IN (1,2,3)
</summary>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.WhereNotIn(System.String,System.Object)">
<summary>
WHERE [field] NOT IN (1,2,3)
</summary>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.WhereDynamic(System.Object)">
<summary>
匿名对象Where(new { Year = 2017, CategoryId = 198, IsPublished = true })<para></para>
字典对象Where(new Dictionary&lt;string, object&gt; { ["Year"] = 2017, ["CategoryId"] = 198, ["IsPublished"] = true })<para></para>
WHERE [Year] = 2017 AND [CategoryId] = 198 AND [IsPublished] = 1<para></para>
</summary>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.Where(System.String,System.Object)">
<summary>
WHERE [field] = ..<para></para>
更全请看重载 Where(string field, string @operator, object value)
</summary>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.Where(System.String,System.String,System.Object)">
<summary>
WHERE [field] <para></para>
!=、&gt;&gt;=、&lt;&lt;=、like、!like、in、!in<para></para>
</summary>
<param name="field"></param>
<param name="operator"></param>
<param name="value"></param>
<returns></returns>
<exception cref="T:System.Exception"></exception>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.WhereColumns(System.String,System.String,System.String)">
<summary>
WHERE 字段与字段
</summary>
<param name="field1"></param>
<param name="operator"></param>
<param name="field2"></param>
<returns></returns>
<exception cref="T:System.Exception"></exception>
</member>
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.WhereExists(System.Func{FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.SubQuery,FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl})">
<summary>
WHERE exists(select ...)
</summary>
<param name="q"></param>
<returns></returns>
</member>
</members>
</doc>

View File

@@ -0,0 +1,9 @@
using System;
namespace FreeSql.Extensions.ZeroEntity.Models
{
public class SchemaValidationException : Exception
{
public SchemaValidationException(string message) : base(message) { }
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace FreeSql.Extensions.ZeroEntity.Models
{
public class SchemaValidationResult
{
public readonly static SchemaValidationResult _schemaValidationResult = new SchemaValidationResult("");
public static SchemaValidationResult SuccessedResult => _schemaValidationResult;
public SchemaValidationResult(string errorMessage)
{
ErrorMessage = errorMessage;
}
public string ErrorMessage { get; set; }
public bool Succeeded { get; set; } = false;
}
}

View File

@@ -0,0 +1,965 @@
using FreeSql.DataAnnotations;
using FreeSql.Internal;
using FreeSql.Internal.CommonProvider;
using FreeSql.Internal.Model;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using T = System.Collections.Generic.Dictionary<string, object>;
namespace FreeSql.Extensions.ZeroEntity
{
partial class ZeroDbContext
{
public class SelectImpl
{
ZeroDbContext _dbcontext;
IFreeSql _orm => _dbcontext._orm;
List<ZeroTableInfo> _tables => _dbcontext._tables;
int _mainTableIndex = -1;
List<TableAliasInfo> _tableAlias;
ISelect<TestDynamicFilterInfo> _select;
Select0Provider _selectProvider;
string _field;
Dictionary<string, string> _fieldAlias;
List<Action<DbDataReaderContext>> _fieldReader;
string _groupBy;
List<DbParameter> _params = new List<DbParameter>();
CommonUtils _common => _selectProvider._commonUtils;
bool _useStates = true;
bool _includeAll = false;
SelectImpl() { }
internal SelectImpl(ZeroDbContext dbcontext, string tableName)
{
_dbcontext = dbcontext;
var tableIndex = _tables.FindIndex(a => a.CsName.ToLower() == tableName?.ToLower());
if (tableIndex == -1) throw new Exception($"未定义表名 {tableName}");
_mainTableIndex = tableIndex;
_tableAlias = new List<TableAliasInfo>();
_select = _orm.Select<TestDynamicFilterInfo>()
.AsTable((t, old) => _tables[tableIndex].DbName);
_selectProvider = _select as Select0Provider;
_fieldAlias = new Dictionary<string, string>();
_fieldReader = new List<Action<DbDataReaderContext>>();
FlagFetchResult(_tables[_mainTableIndex], "a", "");
}
public SelectImpl NoTracking()
{
_useStates = false;
return this;
}
public SelectImpl IncludeAll()
{
var ignores = new Dictionary<string, bool>();
_includeAll = true;
LocalAutoInclude(_tables[_mainTableIndex], "a");
return this;
void LocalAutoInclude(ZeroTableInfo table, string alias, string navPath = "")
{
if (ignores.ContainsKey(table.CsName)) return;
ignores.Add(table.CsName, true);
TableAliasInfo tableAlias = null;
if (table != _tables[_mainTableIndex])
tableAlias = FlagFetchResult(table, alias.ToString(), navPath);
var leftJoins = table.Navigates.Where(a => a.Value.RefType == TableRefType.ManyToOne || a.Value.RefType == TableRefType.OneToOne).ToArray();
foreach (var join in leftJoins)
{
var joinTable = join.Value.RefTable;
if (ignores.ContainsKey(joinTable.CsName)) return;
var joinAlias = GetMaxAlias();
var joinOn = string.Join(" AND ", join.Value.RefColumns.Select((bname, idx) =>
$"{joinAlias}.{_common.QuoteSqlName(join.Value.RefTable.ColumnsByCs[bname].Attribute.Name)} = {alias}.{_common.QuoteSqlName(join.Value.Table.ColumnsByCs[join.Value.Columns[idx]].Attribute.Name)}"));
_select.LeftJoin($"{_common.QuoteSqlName(join.Value.RefTable.DbName)} {joinAlias} ON {joinOn}");
LocalAutoInclude(joinTable, joinAlias, $"{navPath}.{join.Key}");
}
if (tableAlias == null) tableAlias = _tableAlias.Where(a => a.Alias == alias).First();
var includeManys = table.Navigates.Where(a => a.Value.RefType == TableRefType.OneToMany || a.Value.RefType == TableRefType.ManyToMany).ToArray();
foreach (var includeMany in includeManys)
tableAlias.IncludeMany.Add(NativeTuple.Create(includeMany.Key, (Action<SelectImpl>)null));
}
}
public SelectImpl Include(string navigate, Action<SelectImpl> then = null)
{
var alias = _tableAlias[0];
var navPath = navigate.Split('.');
var navPath2 = "";
for (var navIdx = 0; navIdx < navPath.Length; navIdx++)
{
if (alias.Table.Navigates.TryGetValue(navPath[navIdx], out var nav) == false) throw new Exception($"{alias.Table.CsName} 未定义导航属性 {navPath[navIdx]}");
if (nav.RefType == TableRefType.OneToMany || nav.RefType == TableRefType.ManyToMany)
{
if (navIdx < navPath.Length - 1) throw new Exception($"导航属性 OneToMany/ManyToMany {navPath[navIdx]} 不能处于中间位置");
if (alias.IncludeMany.Where(a => a.Item1 == nav.NavigateKey).Any() == false)
alias.IncludeMany.Add(NativeTuple.Create(nav.NavigateKey, then));
break;
}
navPath2 = navIdx == 0 ? nav.NavigateKey : $"{navPath2}.{nav.NavigateKey}";
var navAlias = _tableAlias.Where(a => string.Join(".", a.NavPath) == navPath2).FirstOrDefault();
if (navAlias == null)
{
var joinAlias = GetMaxAlias();
var joinOn = string.Join(" AND ", nav.RefColumns.Select((bname, idx) =>
$"{joinAlias}.{_common.QuoteSqlName(nav.RefTable.ColumnsByCs[bname].Attribute.Name)} = {alias.Alias}.{_common.QuoteSqlName(nav.Table.ColumnsByCs[nav.Columns[idx]].Attribute.Name)}"));
_select.LeftJoin($"{_common.QuoteSqlName(nav.RefTable.DbName)} {joinAlias} ON {joinOn}");
navAlias = FlagFetchResult(nav.RefTable, joinAlias, navPath2);
}
alias = navAlias;
}
return this;
}
//public SelectImpl IncludeSubQuery(string resultKey, string tableName, Action<SelectImpl> then)
//{
// var query = _dbcontext.SelectNoTracking(tableName);
// query._tableAlias.AddRange(_tableAlias);
// return this;
//}
/// <summary>
/// 举例1LeftJoin("table1", "id", "user.id") -> LEFT JOIN [table1] b ON b.[id] = a.[id]<para></para>
/// 举例2LeftJoin("table1", "id", "user.id", "xid", "user.xid") -> LEFT JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
/// </summary>
public SelectImpl LeftJoin(string tableName, params string[] onFields) => Join("LEFT JOIN", tableName, onFields);
/// <summary>
/// 举例1InnerJoin("table1", "id", "user.id") -> INNER JOIN [table1] b ON b.[id] = a.[id]<para></para>
/// 举例2InnerJoin("table1", "id", "user.id", "xid", "user.xid") -> INNER JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
/// </summary>
public SelectImpl InnerJoin(string tableName, params string[] onFields) => Join("INNER JOIN", tableName, onFields);
/// <summary>
/// 举例1RightJoin("table1", "id", "user.id") -> RIGTH JOIN [table1] b ON b.[id] = a.[id]<para></para>
/// 举例2RightJoin("table1", "id", "user.id", "xid", "user.xid") -> RIGTH JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
/// </summary>
public SelectImpl RightJoin(string tableName, params string[] onFields) => Join("RIGTH JOIN", tableName, onFields);
SelectImpl Join(string joinType, string tableName, params string[] onFields)
{
if (onFields == null || onFields.Length == 0) throw new Exception($"{joinType} 参数 {nameof(onFields)} 不能为空");
if (onFields.Length % 2 != 0) throw new Exception($"{joinType} 参数 {nameof(onFields)} 数组长度必须为偶数,正确:.LeftJoin(\"table1\", \"id\", \"user.id\")");
var table = _tables.Where(a => a.CsName.ToLower() == tableName?.ToLower()).FirstOrDefault();
if (table == null) throw new Exception($"未定义表名 {tableName}");
var alias = GetMaxAlias();
var navKey = tableName;
for (var a = 2; true; a++)
{
if (_tables[_mainTableIndex].ColumnsByCs.ContainsKey(navKey))
{
navKey = $"{tableName}{a}";
continue;
}
if (_tableAlias.Any(b => b.NavPath.Length == 1 && b.NavPath.Last() == navKey))
{
navKey = $"{tableName}{a}";
continue;
}
break;
}
FlagFetchResult(table, alias, navKey);
var joinOn = new string[onFields.Length / 2];
for (var a = 0; a < onFields.Length; a += 2)
{
var field1 = ParseField(table, alias, onFields[a]);
if (field1 == null) throw new Exception($"未匹配字段名 {onFields[a]}");
var field2 = ParseField(table, alias, onFields[a + 1]);
if (field2 == null) throw new Exception($"未匹配字段名 {onFields[a + 1]}");
joinOn[a / 2] = $"{field1.Item1} = {field2.Item1}";
}
_select.RawJoin($"{joinType} {_common.QuoteSqlName(table.DbName)} {alias} ON {string.Join(" AND ", joinOn)}");
return this;
}
class TableAliasInfo
{
public string Alias { get; set; }
public ZeroTableInfo Table { get; set; }
public string[] NavPath { get; set; }
public List<NativeTuple<string, Action<SelectImpl>>> IncludeMany { get; set; } = new List<NativeTuple<string, Action<SelectImpl>>>();
}
class DbDataReaderContext
{
public DbDataReader Reader { get; set; }
public int Index { get; set; }
public T Result { get; set; }
}
string GetMaxAlias()
{
var max = (int)_tableAlias.Where(a => a.Alias.Length == 1).Max(a => a.Alias[0]);
if (max < 'a') max = 'a';
for (var a = 1; true; a++)
{
var alias = ((char)(max + a)).ToString();
if (_tableAlias.Where(b => b.Alias == alias).Any()) continue;
return alias;
}
}
TableAliasInfo FlagFetchResult(ZeroTableInfo table, string alias, string navPath)
{
var tableAlias = _tableAlias.Where(a => a.Alias == alias).FirstOrDefault();
if (tableAlias == null)
{
var navPathArray = navPath.Split('.').Where(a => string.IsNullOrWhiteSpace(a) == false).ToArray();
_tableAlias.Add(tableAlias = new TableAliasInfo
{
Alias = alias,
Table = table,
NavPath = navPathArray
});
}
var sbfield = new StringBuilder();
if (string.IsNullOrEmpty(_field) == false) sbfield.Append(", ").Append(_field);
foreach (var col in table.Columns.Values)
{
var colName = col.Attribute.Name;
for (var a = 2; true; a++)
{
if (_fieldAlias.ContainsKey(colName)) colName = $"{col.Attribute.Name}{a}";
else break;
}
_fieldAlias.Add(colName, col.Attribute.Name);
sbfield.Append(", ").Append(alias).Append(".").Append(_common.QuoteSqlName(col.Attribute.Name));
if (colName != col.Attribute.Name) sbfield.Append(_common.FieldAsAlias(colName));
}
_field = sbfield.Remove(0, 2).ToString();
sbfield.Clear();
_fieldReader.Add(dr =>
{
var pkIsNull = false;
foreach (var col in table.Columns.Values)
{
if (pkIsNull == false && col.Attribute.IsPrimary)
{
pkIsNull = dr.Reader.IsDBNull(dr.Index);
if (pkIsNull) dr.Result.Clear();
}
if (pkIsNull == false) dr.Result[col.CsName] = Utils.GetDataReaderValue(col.CsType, dr.Reader.GetValue(dr.Index));
dr.Index++;
}
});
return tableAlias;
}
T FetchResult(DbDataReader reader)
{
var fieldIndex = 0;
var result = new T();
for (var aliasIndex = 0; aliasIndex < _tableAlias.Count; aliasIndex++)
{
var navValue = result;
var drctx = new DbDataReaderContext { Index = fieldIndex, Reader = reader };
if (aliasIndex == 0)
{
drctx.Result = result;
}
else
{
var isNull = false;
for (var navidx = 0; navidx < _tableAlias[aliasIndex].NavPath.Length - 1; navidx++)
{
var navKey = _tableAlias[aliasIndex].NavPath[navidx];
if (navValue.ContainsKey(navKey) == false)
{
isNull = true;
break;
}
navValue = navValue[navKey] as T;
if (navValue == null)
{
isNull = true;
break;
}
}
if (isNull)
{
fieldIndex += _tableAlias[aliasIndex].Table.Columns.Count;
continue;
}
drctx.Result = new T();
navValue[_tableAlias[aliasIndex].NavPath.LastOrDefault()] = drctx.Result;
}
_fieldReader[aliasIndex](drctx);
fieldIndex = drctx.Index;
if (aliasIndex > 0 && drctx.Result.Any() == false) navValue.Remove(_tableAlias[aliasIndex].NavPath.LastOrDefault());
}
return result;
}
void IncludeMany(TableAliasInfo alias, NativeTuple<string, Action<SelectImpl>> navMany, List<T> list, List<string> flagIndexs = null)
{
if (list?.Any() != true) return;
if (flagIndexs == null) flagIndexs = new List<string>();
var nav = alias.Table.Navigates[navMany.Item1];
if (_includeAll && flagIndexs.Contains(nav.RefTable.CsName)) return;
flagIndexs.Add(nav.RefTable.CsName);
if (nav.RefType == TableRefType.OneToMany)
{
var subTable = nav.RefTable;
var subSelect = new SelectImpl(_dbcontext, subTable.CsName);
if (_includeAll) subSelect.IncludeAll();
Func<Dictionary<string, bool>> getWhereDic = () =>
{
var sbDic = new Dictionary<string, bool>();
for (var y = 0; y < list.Count; y++)
{
var sbWhereOne = new StringBuilder();
sbWhereOne.Append("(");
for (var z = 0; z < nav.Columns.Count; z++)
{
if (z > 0) sbWhereOne.Append(" AND ");
var refcol = nav.RefTable.ColumnsByCs[nav.RefColumns[z]];
var val = Utils.GetDataReaderValue(refcol.Attribute.MapType, list[y][nav.Columns[z]]);
sbWhereOne.Append(_common.FormatSql($"a.{_common.QuoteSqlName(refcol.Attribute.Name)}={{0}}", val));
}
sbWhereOne.Append(")");
var whereOne = sbWhereOne.ToString();
sbWhereOne.Clear();
if (sbDic.ContainsKey(whereOne) == false) sbDic.Add(whereOne, true);
}
return sbDic;
};
if (nav.Columns.Count == 1)
{
var refcol = nav.RefTable.ColumnsByCs[nav.RefColumns[0]];
var args1 = $"a.{_common.QuoteSqlName(refcol.Attribute.Name)}";
var left = _common.FormatSql("{0}", new object[] { list.Select(a => a[nav.Columns[0]]).Distinct().ToArray() });
subSelect._select.Where($"({args1} in {left.Replace(", \r\n \r\n", $") \r\n OR {args1} in (")})");
}
else
{
var sbDic = getWhereDic();
var sbWhere = new StringBuilder();
foreach (var sbd in sbDic)
sbWhere.Append(" OR ").Append(sbd.Key);
subSelect._select.Where(sbWhere.Remove(0, 4).ToString());
sbWhere.Clear();
sbDic.Clear();
}
navMany.Item2?.Invoke(subSelect);
var subList = subSelect.ToListPrivate(null, null, flagIndexs);
foreach (var item in list)
{
item[nav.NavigateKey] = subList.Where(a =>
{
for (var z = 0; z < nav.Columns.Count; z++)
if (CompareEntityPropertyValue(nav.Table.ColumnsByCs[nav.Columns[z]].Attribute.MapType, item[nav.Columns[z]], a[nav.RefColumns[z]]) == false)
return false;
return true;
}).ToList();
}
subList.Clear();
}
else if (nav.RefType == TableRefType.ManyToMany)
{
var subTable = nav.RefTable;
var subSelect = new SelectImpl(_dbcontext, subTable.CsName);
if (_includeAll) subSelect.IncludeAll();
var middleJoinOn = string.Join(" AND ", nav.RefColumns.Select((bname, idx) =>
$"midtb.{_common.QuoteSqlName(nav.RefMiddleTable.ColumnsByCs[nav.MiddleColumns[nav.Columns.Count + idx]].Attribute.Name)} = a.{_common.QuoteSqlName(nav.RefTable.ColumnsByCs[bname].Attribute.Name)}"));
subSelect._select.InnerJoin($"{_common.QuoteSqlName(nav.RefMiddleTable.DbName)} midtb ON {middleJoinOn}");
Func<Dictionary<string, bool>> getWhereDic = () =>
{
var sbDic = new Dictionary<string, bool>();
for (var y = 0; y < list.Count; y++)
{
var sbWhereOne = new StringBuilder();
sbWhereOne.Append("(");
for (var z = 0; z < nav.Columns.Count; z++)
{
if (z > 0) sbWhereOne.Append(" AND ");
var midcol = nav.RefMiddleTable.ColumnsByCs[nav.MiddleColumns[z]];
var val = Utils.GetDataReaderValue(midcol.Attribute.MapType, list[y][nav.Columns[z]]);
sbWhereOne.Append(_common.FormatSql($"midtb.{_common.QuoteSqlName(midcol.Attribute.Name)}={{0}}", val));
}
sbWhereOne.Append(")");
var whereOne = sbWhereOne.ToString();
sbWhereOne.Clear();
if (sbDic.ContainsKey(whereOne) == false) sbDic.Add(whereOne, true);
}
return sbDic;
};
if (nav.Columns.Count == 1)
{
var midcol = nav.RefMiddleTable.ColumnsByCs[nav.MiddleColumns[0]];
var args1 = $"midtb.{_common.QuoteSqlName(midcol.Attribute.Name)}";
var left = _common.FormatSql("{0}", new object[] { list.Select(a => a[nav.Columns[0]]).Distinct().ToArray() });
subSelect._select.Where($"({args1} in {left.Replace(", \r\n \r\n", $") \r\n OR {args1} in (")})");
}
else
{
var sbDic = getWhereDic();
var sbWhere = new StringBuilder();
foreach (var sbd in sbDic)
sbWhere.Append(" OR ").Append(sbd.Key);
subSelect._select.Where(sbWhere.Remove(0, 4).ToString());
sbWhere.Clear();
sbDic.Clear();
}
navMany.Item2?.Invoke(subSelect);
var subList = subSelect.ToListPrivate(
string.Join(", ", nav.MiddleColumns.Select((a, idx) => $"midtb.{_common.QuoteSqlName(nav.RefMiddleTable.ColumnsByCs[a].Attribute.Name)}{_common.FieldAsAlias($"midtb_field__{idx}")}")),
(dict, dr) =>
{
var fieldCount = dr.FieldCount - nav.MiddleColumns.Count;
for (var z = 0; z < nav.MiddleColumns.Count; z++)
dict[$"midtb_field__{z}"] = Utils.GetDataReaderValue(nav.RefMiddleTable.ColumnsByCs[nav.MiddleColumns[z]].CsType, dr.GetValue(fieldCount + z));
}, flagIndexs);
foreach (var item in list)
{
item[nav.NavigateKey] = subList.Where(a =>
{
for (var z = 0; z < nav.Columns.Count; z++)
if (CompareEntityPropertyValue(nav.Table.ColumnsByCs[nav.Columns[z]].Attribute.MapType, item[nav.Columns[z]], a[$"midtb_field__{z}"]) == false)
return false;
return true;
}).ToList();
}
foreach (var subItem in subList)
for (var z = 0; z < nav.MiddleColumns.Count; z++)
subItem.Remove($"midtb_field__{z}");
subList.Clear();
}
}
public string ToSql(string field = null)
{
if (string.IsNullOrWhiteSpace(field)) return _select.ToSql(_field);
return _select.ToSql(field);
}
List<T> ToListPrivate(string otherField, Action<T, DbDataReader> otherReader, List<string> flagIndexs = null)
{
var sql = string.IsNullOrWhiteSpace(otherField) ? this.ToSql() : this.ToSql($"{_field},{otherField}");
var ret = new List<T>();
var dbParms = _params.ToArray();
var before = new Aop.CurdBeforeEventArgs(_tables[_mainTableIndex].Type, _tables[_mainTableIndex], Aop.CurdType.Select, sql, dbParms);
_orm.Aop.CurdBeforeHandler?.Invoke(this, before);
Exception exception = null;
try
{
_orm.Ado.ExecuteReader(_dbcontext._transaction?.Connection, _dbcontext._transaction, fetch =>
{
var item = FetchResult(fetch.Object);
otherReader?.Invoke(item, fetch.Object);
ret.Add(item);
}, CommandType.Text, sql, _dbcontext._commandTimeout, dbParms);
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
var after = new Aop.CurdAfterEventArgs(before, exception, ret);
_orm.Aop.CurdAfterHandler?.Invoke(this, after);
}
foreach (var join in _tableAlias)
{
if (join.IncludeMany.Any() == false) continue;
var list = new List<T>();
if (join.Alias == "a") list = ret;
else
{
foreach (var obj in ret)
{
T item = obj;
foreach (var navKey in join.NavPath)
{
if (string.IsNullOrWhiteSpace(navKey)) continue;
item.TryGetValue(navKey, out var obj2);
item = obj2 as T;
if (item == null) break;
}
if (item != null) list.Add(item);
}
}
foreach(var navMany in join.IncludeMany)
IncludeMany(join, navMany, list, flagIndexs);
}
if (_useStates && flagIndexs == null)
foreach (var item in ret)
_dbcontext.AttachCascade(_tables[_mainTableIndex], item, true);
return ret;
}
public List<T> ToList() => ToListPrivate(null, null);
public T ToOne() => ToListPrivate(null, null).FirstOrDefault();
public T First() => ToOne();
public bool Any() => _select.Any();
public long Count() => _select.Count();
public SelectImpl Count(out long count)
{
_select.Count(out count);
return this;
}
public SelectImpl WithTransaction(DbTransaction transaction)
{
_select.WithTransaction(transaction);
return this;
}
public SelectImpl WithConnection(DbConnection connection)
{
_select.WithConnection(connection);
return this;
}
public SelectImpl CommandTimeout(int timeout)
{
_select.CommandTimeout(timeout);
return this;
}
public SelectImpl Distinct()
{
_select.Distinct();
return this;
}
public SelectImpl Master()
{
_select.Master();
return this;
}
public SelectImpl ForUpdate(bool nowait = false)
{
_select.ForUpdate(nowait);
return this;
}
NativeTuple<string, ColumnInfo, string> ParseField(ZeroTableInfo firstTable, string firstTableAlias, string property)
{
if (string.IsNullOrEmpty(property)) return null;
var field = property.Split('.').Select(a => a.Trim()).ToArray();
if (field.Length == 1)
{
if (firstTable != null && firstTable.ColumnsByCs.TryGetValue(field[0], out var col2) == true)
return NativeTuple.Create($"{firstTableAlias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2, firstTableAlias);
foreach (var ta2 in _tableAlias)
{
if (ta2.Table.ColumnsByCs.TryGetValue(field[0], out col2))
return NativeTuple.Create($"{ta2.Alias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2, ta2.Alias);
}
}
else if (field.Length == 2)
{
if (firstTable != null && firstTable.CsName.ToLower() == field[0].ToLower() && firstTable.ColumnsByCs.TryGetValue(field[1], out var col2) == true)
return NativeTuple.Create($"{firstTableAlias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2, firstTableAlias);
var ta2s = _tableAlias.Where(a => a.Table.CsName.ToLower() == field[0].ToLower()).ToArray();
if (ta2s.Length == 1 && ta2s[0].Table.ColumnsByCs.TryGetValue(field[1], out col2) == true)
return NativeTuple.Create($"{ta2s[0].Alias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2, ta2s[0].Alias);
if (ta2s.Length > 1)
{
ta2s = _tableAlias.Where(a => a.Table.CsName == field[0]).ToArray();
if (ta2s.Length == 1 && ta2s[0].Table.ColumnsByCs.TryGetValue(field[1], out col2) == true)
return NativeTuple.Create($"{ta2s[0].Alias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2, ta2s[0].Alias);
}
if (_tableAlias.Where(a => a.Alias == field[0]).FirstOrDefault()?.Table.ColumnsByCs.TryGetValue(field[1], out col2) == true)
return NativeTuple.Create($"{field[0]}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2, field[0]);
}
var navPath = string.Join(".", field.Skip(1).Take(field.Length - 1));
var ta = _tableAlias.Where(a => string.Join(".", a.NavPath) == navPath).FirstOrDefault();
if (ta?.Table.ColumnsByCs.TryGetValue(field.Last(), out var col) == true)
return NativeTuple.Create($"{ta.Alias}.{_common.QuoteSqlName(col.Attribute.Name)}", col, ta.Alias);
throw new Exception(CoreErrorStrings.Cannot_Match_Property(property));
}
/// <summary>
/// WHERE [Id] IN (1,2,3)
/// </summary>
public SelectImpl Where(IEnumerable<T> items)
{
var alias = _tableAlias.Where(a => a.Table == _tables[_mainTableIndex]).FirstOrDefault()?.Alias;
if (!string.IsNullOrWhiteSpace(alias)) alias = $"{alias}.";
var where = _common.WhereItems(_tables[_mainTableIndex].Primarys, alias, items, _selectProvider._params);
_select.Where(where);
return this;
}
/// <summary>
/// WHERE [field] IN (1,2,3)
/// </summary>
public SelectImpl WhereIn(string field, object value) => Where(field, "in", value);
public SelectImpl WhereInIf(bool condition, string field, object value) => condition ? Where(field, "in", value) : this;
/// <summary>
/// WHERE [field] NOT IN (1,2,3)
/// </summary>
public SelectImpl WhereNotIn(string field, object value) => Where(field, "!in", value);
public SelectImpl WhereNotInIf(bool condition, string field, object value) => condition ? Where(field, "!in", value) : this;
/// <summary>
/// 匿名对象Where(new { Year = 2017, CategoryId = 198, IsPublished = true })<para></para>
/// 字典对象Where(new Dictionary&lt;string, object&gt; { ["Year"] = 2017, ["CategoryId"] = 198, ["IsPublished"] = true })<para></para>
/// WHERE [Year] = 2017 AND [CategoryId] = 198 AND [IsPublished] = 1<para></para>
/// </summary>
public SelectImpl WhereDynamic(object dywhere)
{
if (dywhere == null) return this;
if (dywhere is IDictionary<string, object> dict)
{
foreach(var kv in dict)
WhereDynamicFilter(new DynamicFilterInfo { Field = kv.Key, Operator = DynamicFilterOperator.Eq, Value = kv.Value });
return this;
}
foreach (var prop in dywhere.GetType().GetProperties())
WhereDynamicFilter(new DynamicFilterInfo { Field = prop.Name, Operator = DynamicFilterOperator.Eq, Value = prop.GetValue(dywhere, null) });
return this;
}
public SelectImpl WhereDynamicIf(bool condition, object dywhere) => condition ? WhereDynamic(dywhere) : this;
/// <summary>
/// WHERE [field] = ..<para></para>
/// 更全请看重载 Where(string field, string @operator, object value)
/// </summary>
public SelectImpl Where(string field, object value) => WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.Eq, Value = value });
public SelectImpl WhereIf(bool condition, string field, object value) => condition ? Where(field, value) : this;
/// <summary>
/// WHERE [field] <para></para>
/// !=、&gt;、&gt;=、&lt;、&lt;=、like、!like、in、!in<para></para>
/// </summary>
/// <param name="field"></param>
/// <param name="operator"></param>
/// <param name="value"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public SelectImpl Where(string field, string @operator, object value)
{
switch (@operator?.ToLower().Trim())
{
case "=":
case "==":
case "eq":
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.Eq, Value = value });
case "!=":
case "<>":
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.NotEqual, Value = value });
case ">":
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.GreaterThan, Value = value });
case ">=":
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.GreaterThanOrEqual, Value = value });
case "<":
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.LessThan, Value = value });
case "<=":
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.LessThanOrEqual, Value = value });
case "like":
case "contains":
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.Contains, Value = value });
case "!like":
case "notlike":
case "not like":
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.NotContains, Value = value });
case "in":
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.Any, Value = value });
case "!in":
case "notin":
case "not in":
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.NotAny, Value = value });
}
throw new Exception($"未实现 {@operator}");
}
public SelectImpl WhereIf(bool condition, string field, string @operator, object value) => condition ? Where(field, @operator, value) : this;
/// <summary>
/// WHERE 字段与字段
/// </summary>
/// <param name="field1"></param>
/// <param name="operator"></param>
/// <param name="field2"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public SelectImpl WhereColumns(string field1, string @operator, string field2)
{
var field1Result = ParseField(null, null, field1);
if (field1Result == null) throw new Exception($"未匹配字段名 {field1}");
var field2Result = ParseField(null, null, field2);
if (field2Result == null) throw new Exception($"未匹配字段名 {field2}");
switch (@operator?.ToLower().Trim())
{
case "=":
case "==":
case "eq":
_select.Where($"{field1Result.Item1} = {field2Result.Item1}");
return this;
case "!=":
case "<>":
_select.Where($"{field1Result.Item1} <> {field2Result.Item1}");
return this;
case ">":
_select.Where($"{field1Result.Item1} > {field2Result.Item1}");
return this;
case ">=":
_select.Where($"{field1Result.Item1} >= {field2Result.Item1}");
return this;
case "<":
_select.Where($"{field1Result.Item1} < {field2Result.Item1}");
return this;
case "<=":
_select.Where($"{field1Result.Item1} <= {field2Result.Item1}");
return this;
}
throw new Exception($"未实现 {@operator}");
}
public SelectImpl WhereColumnsIf(bool condition, string field, string @operator, string field2) => condition ? WhereColumns(field, @operator, field2) : this;
public SelectImpl WhereDynamicFilter(DynamicFilterInfo filter)
{
var sql = ParseDynamicFilter(filter);
_selectProvider._where.Append(sql);
return this;
}
string ParseDynamicFilter(DynamicFilterInfo filter)
{
var replacedFilter = new DynamicFilterInfo();
var replacedMap = new List<NativeTuple<string, string, string>>();
LocalCloneFilter(filter, replacedFilter);
var oldWhere = _selectProvider._where.ToString();
var newWhere = "";
try
{
_selectProvider._where.Clear();
_select.WhereDynamicFilter(replacedFilter);
newWhere = _selectProvider._where.ToString();
}
finally
{
_selectProvider._where.Clear().Append(oldWhere);
}
foreach (var rm in replacedMap)
{
var find = $"{rm.Item3}.{_common.QuoteSqlName(rm.Item1)}";
var idx = newWhere.IndexOf(find);
if (idx != -1 && !Regex.IsMatch(newWhere.Substring(idx - 1, 1), @"[\w_]")) newWhere = $"{newWhere.Substring(0, idx)}{rm.Item2}{newWhere.Substring(idx + find.Length)}";
}
return newWhere;
void LocalCloneFilter(DynamicFilterInfo source, DynamicFilterInfo target)
{
target.Field = source.Field;
target.Operator = source.Operator;
target.Value = source.Value;
target.Logic = source.Logic;
if (string.IsNullOrWhiteSpace(source.Field) == false)
{
var parseResult = ParseField(null, null, source.Field);
if (parseResult != null)
{
if (TestDynamicFilterInfo._dictTypeToPropertyname.TryGetValue(parseResult.Item2.Attribute.MapType, out var pname))
target.Field = pname;
else
target.Field = TestDynamicFilterInfo._dictTypeToPropertyname[typeof(string)];
replacedMap.Add(NativeTuple.Create(target.Field, parseResult.Item1, parseResult.Item3));
}
}
if (source.Filters?.Any() == true)
{
target.Filters = new List<DynamicFilterInfo>();
foreach (var sourceChild in source.Filters)
{
var targetChild = new DynamicFilterInfo();
target.Filters.Add(targetChild);
LocalCloneFilter(sourceChild, targetChild);
}
}
}
}
public class SubQuery
{
internal SelectImpl _parentQuery;
internal SubQuery() { }
public SelectImpl From(string tableName)
{
var query = _parentQuery._dbcontext.SelectNoTracking(tableName);
query._selectProvider._tables[0].Alias =
query._tableAlias[0].Alias = $"sub_{_parentQuery._tableAlias[0].Alias}";
query._tableAlias.AddRange(_parentQuery._tableAlias);
return query;
}
}
SelectImpl WhereExists(Func<SubQuery, SelectImpl> q, bool notExists)
{
var query = q?.Invoke(new SubQuery { _parentQuery = this });
switch (_orm.Ado.DataType)
{
case DataType.Oracle:
case DataType.OdbcOracle:
case DataType.CustomOracle:
case DataType.Dameng:
case DataType.GBase:
query.Limit(-1);
break;
default:
query.Limit(1); //#462 ORACLE rownum <= 2 会影响索引变慢
break;
}
_selectProvider._where.Append($" AND {(notExists ? "NOT " : "")}EXISTS({query.ToSql("1").Replace(" \r\n", " \r\n ")})");
return this;
}
/// <summary>
/// WHERE exists(select ...)
/// </summary>
/// <param name="q"></param>
/// <returns></returns>
public SelectImpl WhereExists(Func<SubQuery, SelectImpl> q) => WhereExists(q, false);
public SelectImpl WhereExistsIf(bool condition, Func<SubQuery, SelectImpl> q) => condition ? WhereExists(q, false) : this;
public SelectImpl WhereNotExists(Func<SubQuery, SelectImpl> q) => WhereExists(q, true);
public SelectImpl WhereNotExistsIf(bool condition, Func<SubQuery, SelectImpl> q) => condition ? WhereExists(q, true) : this;
public SelectImpl GroupByRaw(string sql)
{
if (string.IsNullOrWhiteSpace(sql)) return this;
_useStates = false;
_groupBy = $"{_groupBy}, {sql}";
_useStates = false;
_select.GroupBy(_groupBy.Substring(2));
return this;
}
public SelectImpl GroupBy(string[] fields)
{
var count = 0;
for (var a = 0; a < fields.Length; a++)
{
if (string.IsNullOrWhiteSpace(fields[a])) continue;
var field1 = ParseField(null, null, fields[a]);
if (field1 == null) throw new Exception($"未匹配字段名 {fields[a]}");
_groupBy = $"{_groupBy}, {field1.Item1}";
count++;
}
if (count > 0)
{
_useStates = false;
_select.GroupBy(_groupBy.Substring(2));
}
return this;
}
public SelectImpl HavingRaw(string sql)
{
_select.Having(sql);
return this;
}
public SelectImpl OrderByRaw(string sql)
{
_select.OrderBy(sql);
return this;
}
SelectImpl OrderBy(bool isdesc, string[] fields)
{
for (var a = 0; a < fields.Length; a ++)
{
if (string.IsNullOrWhiteSpace(fields[a])) continue;
var field1 = ParseField(null, null, fields[a]);
if (field1 == null) throw new Exception($"未匹配字段名 {fields[a]}");
if (isdesc) _select.OrderBy($"{field1.Item1} DESC");
else _select.OrderBy(field1.Item1);
}
return this;
}
public SelectImpl OrderBy(params string[] fields) => OrderBy(false, fields);
public SelectImpl OrderByDescending(params string[] fields) => OrderBy(true, fields);
public SelectImpl Offset(int offset)
{
_select.Offset(offset);
return this;
}
public SelectImpl Limit(int limit)
{
_select.Limit(limit);
return this;
}
public SelectImpl Skip(int offset) => Offset(offset);
public SelectImpl Take(int limit) => Limit(limit);
public SelectImpl Page(int pageNumber, int pageSize)
{
_select.Page(pageNumber, pageSize);
return this;
}
TResult InternalQuerySingle<TResult>(string field) => _orm.Ado.CommandFluent(this.ToSql(field))
.WithConnection(_selectProvider._connection)
.WithTransaction(_selectProvider._transaction).QuerySingle<TResult>();
public decimal Sum(string field)
{
var field1 = ParseField(null, null, field);
if (field1 == null) throw new Exception($"未匹配字段名 {field}");
return InternalQuerySingle<decimal>($"sum({field1.Item1})");
}
public TMember Min<TMember>(string field)
{
var field1 = ParseField(null, null, field);
if (field1 == null) throw new Exception($"未匹配字段名 {field}");
return InternalQuerySingle<TMember>($"min({field1.Item1})");
}
public TMember Max<TMember>(string field)
{
var field1 = ParseField(null, null, field);
if (field1 == null) throw new Exception($"未匹配字段名 {field}");
return InternalQuerySingle<TMember>($"max({field1.Item1})");
}
public double Avg(string field)
{
var field1 = ParseField(null, null, field);
if (field1 == null) throw new Exception($"未匹配字段名 {field}");
return InternalQuerySingle<double>($"avg({field1.Item1})");
}
[Table(DisableSyncStructure = true)]
class TestDynamicFilterInfo
{
public Guid DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf00 { get; set; }
public bool DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf01 { get; set; }
public string DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf02 { get; set; }
public char DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf03 { get; set; }
public DateTime DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf04 { get; set; }
public DateTimeOffset DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf05 { get; set; }
public TimeSpan DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf06 { get; set; }
public int DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf07 { get; set; }
public long DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf08 { get; set; }
public short DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf09 { get; set; }
public sbyte DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf10 { get; set; }
public uint DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf11 { get; set; }
public ulong DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf12 { get; set; }
public ushort DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf13 { get; set; }
public byte DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf14 { get; set; }
public byte[] DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf15 { get; set; }
public double DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf16 { get; set; }
public float DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf17 { get; set; }
public decimal DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf18 { get; set; }
internal static Dictionary<Type, string> _dictTypeToPropertyname = new Dictionary<Type, string>
{
[typeof(Guid)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf00),
[typeof(bool)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf01),
[typeof(string)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf02),
[typeof(char)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf03),
[typeof(DateTime)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf04),
[typeof(DateTimeOffset)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf05),
[typeof(TimeSpan)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf06),
[typeof(int)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf07),
[typeof(long)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf08),
[typeof(short)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf09),
[typeof(sbyte)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf10),
[typeof(uint)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf11),
[typeof(ulong)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf12),
[typeof(ushort)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf13),
[typeof(byte)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf14),
[typeof(byte[])] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf15),
[typeof(double)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf16),
[typeof(float)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf17),
[typeof(decimal)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf18),
};
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,97 @@
using FreeSql.DataAnnotations;
using FreeSql.Internal.Model;
using System;
using System.Collections.Generic;
namespace FreeSql.Extensions.ZeroEntity
{
public class TableDescriptor
{
public string Name { get; set; }
public string DbName { get; set; }
public string AsTable { get; set; }
public bool DisableSyncStructure { get; set; }
public string Comment { get; set; }
public List<ColumnDescriptor> Columns { get; } = new List<ColumnDescriptor>();
public List<NavigateDescriptor> Navigates { get; } = new List<NavigateDescriptor>();
public List<IndexDescriptor> Indexes { get; } = new List<IndexDescriptor>();
public class ColumnDescriptor
{
public string Name { get; set; }
public string DbType { get; set; }
bool? _IsPrimary, _IsIdentity, _IsNullable, _IsVersion;
public bool IsPrimary { get => _IsPrimary ?? false; set => _IsPrimary = value; }
public bool IsIdentity { get => _IsIdentity ?? false; set => _IsIdentity = value; }
public bool IsNullable { get => _IsNullable ?? false; set => _IsNullable = value; }
public bool IsVersion { get => _IsVersion ?? false; set => _IsVersion = value; }
public Type MapType { get; set; }
public DateTimeKind ServerTime { get; set; }
public string InsertValueSql { get; set; }
int? _StringLength;
public int StringLength { get => _StringLength ?? 0; set => _StringLength = value; }
int? _Precision;
public int Precision { get => _Precision ?? 0; set => _Precision = value; }
int? _Scale;
public int Scale { get => _Scale ?? 0; set => _Scale = value; }
public string Comment { get; set; }
public ColumnAttribute ToAttribute()
{
var attr = new ColumnAttribute
{
Name = Name,
DbType = DbType,
MapType = MapType,
ServerTime = ServerTime,
InsertValueSql = InsertValueSql,
};
if (_IsPrimary != null) attr.IsPrimary = IsPrimary;
if (_IsIdentity != null) attr.IsIdentity = IsIdentity;
if (_IsNullable != null) attr.IsNullable = IsNullable;
if (_IsVersion != null) attr.IsVersion = IsVersion;
if (_StringLength != null) attr.StringLength = StringLength;
if (_Precision != null) attr.Precision = Precision;
if (_Scale != null) attr.Scale = Scale;
return attr;
}
}
public class IndexDescriptor
{
public string Name { get; set; }
public string Fields { get; set; }
public bool IsUnique { get; set; }
public IndexMethod IndexMethod { get; set; }
}
public class NavigateDescriptor
{
public string Name { get; set; }
public NavigateType Type { get; set; }
public string RelTable { get; set; }
public string Bind { get; set; }
public string ManyToMany { get; set; }
}
public enum NavigateType
{
OneToOne, ManyToOne, OneToMany, ManyToMany
}
}
class ZeroTableRef
{
internal string NavigateKey { get; set; }
public TableRefType RefType { get; set; }
internal ZeroTableInfo Table { get; set; }
internal ZeroTableInfo RefTable { get; set; }
internal ZeroTableInfo RefMiddleTable { get; set; }
public List<string> Columns { get; set; } = new List<string>();
public List<string> MiddleColumns { get; set; } = new List<string>();
public List<string> RefColumns { get; set; } = new List<string>();
}
class ZeroTableInfo : TableInfo
{
public Dictionary<string, ZeroTableRef> Navigates { get; set; } = new Dictionary<string, ZeroTableRef>(StringComparer.OrdinalIgnoreCase);
}
}

Binary file not shown.

View File

@@ -22,6 +22,7 @@ namespace FreeSql.Generator
string ArgsConnectionString { get; }
string ArgsFilter { get; }
string ArgsMatch { get; }
string ArgsJson { get; }
string ArgsFileName { get; }
bool ArgsReadKey { get; }
internal string ArgsOutput { get; private set; }
@@ -29,16 +30,16 @@ namespace FreeSql.Generator
public ConsoleApp(string[] args, ManualResetEvent wait)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var gb2312 = Encoding.GetEncoding("GB2312");
if (gb2312 != null)
{
try
{
Console.OutputEncoding = gb2312;
Console.InputEncoding = gb2312;
}
catch { }
}
//var gb2312 = Encoding.GetEncoding("GB2312");
//if (gb2312 != null)
//{
// try
// {
// Console.OutputEncoding = gb2312;
// Console.InputEncoding = gb2312;
// }
// catch { }
//}
//var ntjson = Assembly.LoadFile(@"C:\Users\28810\Desktop\testfreesql\bin\Debug\netcoreapp2.2\publish\testfreesql.dll");
@@ -62,6 +63,7 @@ new Colorful.Formatter("v" + string.Join(".", typeof(ConsoleApp).Assembly.GetNam
ArgsNameSpace = "MyProject";
ArgsFilter = "";
ArgsMatch = "";
ArgsJson = "Newtonsoft.Json";
ArgsFileName = "{name}.cs";
ArgsReadKey = true;
Action<string> setArgsOutput = value =>
@@ -90,11 +92,11 @@ new Colorful.Formatter("v" + string.Join(".", typeof(ConsoleApp).Assembly.GetNam
-Razor 1 * 选择模板:实体类+特性
-Razor 2 * 选择模板:实体类+特性+导航属性
-Razor ""d:\diy.cshtml"" * 自定义模板文件
-Razor ""d:\diy.cshtml"" * 自定义模板文件如乱码请修改为UTF8(不带BOM)编码格式
-NameOptions * 4个布尔值对应
首字母大写
首字母大写,其他小写
首字母大写其他小写
全部小写
下划线转驼峰
@@ -102,13 +104,13 @@ new Colorful.Formatter("v" + string.Join(".", typeof(ConsoleApp).Assembly.GetNam
-DB ""{6},data source=127.0.0.1;port=3306;user id=root;password=root;initial catalog=数据库;charset=utf8;sslmode=none;max pool size=2""
-DB ""{7},data source=.;integrated security=True;initial catalog=数据库;pooling=true;max pool size=2""
-DB ""{8},host=192.168.164.10;port=5432;username=postgres;password=123456;database=数据库;pooling=true;maximum pool size=2""
-DB ""{8},host=127.0.0.1;port=5432;username=postgres;password=123456;database=数据库;pooling=true;maximum pool size=2""
-DB ""{9},user id=user1;password=123456;data source=//127.0.0.1:1521/XE;pooling=true;max pool size=2""
-DB ""{10},data source=document.db""
-DB ""{14},database=localhost:D:\fbdata\EXAMPLES.fdb;user=sysdba;password=123456;max pool size=2""
-DB ""{11},server=127.0.0.1;port=5236;user id=2user;password=123456789;database=2user;poolsize=2""
-DB ""{12},server=127.0.0.1;port=54321;uid=USER2;pwd=123456789;database=数据库""
-DB ""{13},host=192.168.164.10;port=2003;database=数据库;username=SYSDBA;password=szoscar55;maxpoolsize=2""
-DB ""{13},host=127.0.0.1;port=2003;database=数据库;username=SYSDBA;password=szoscar55;maxpoolsize=2""
* {11}(达梦数据库)、{12}(人大金仓数据库)、{13}(神舟通用数据库)
-Filter Table+View+StoreProcedure
@@ -116,6 +118,8 @@ new Colorful.Formatter("v" + string.Join(".", typeof(ConsoleApp).Assembly.GetNam
如果不想生成视图和存储过程 -Filter View+StoreProcedure
-Match 表名或正则表达式只生成匹配的表dbo\.TB_.+
-Json NTJ、STJ、NONE
Newtonsoft.Json、System.Text.Json、不生成
-FileName 文件名,默认:{name}.cs
-Output 保存路径,默认为当前 shell 所在目录
@@ -152,14 +156,14 @@ new Colorful.Formatter("推荐在实体类目录创建 gen.bat双击它重新
{
case "1": ArgsRazor = RazorContentManager._特性_cshtml; break;
case "2": ArgsRazor = RazorContentManager._特性_导航属性_cshtml; break;
default: ArgsRazor = File.ReadAllText(args[a + 1]); break;
default: ArgsRazor = File.ReadAllText(args[a + 1], Encoding.UTF8); break;
}
a++;
break;
case "-nameoptions":
ArgsNameOptions = args[a + 1].Split(',').Select(opt => opt == "1").ToArray();
if (ArgsNameOptions.Length != 4) throw new ArgumentException("-NameOptions 参数错误格式为0,0,0,0");
if (ArgsNameOptions.Length != 4) throw new ArgumentException(CoreErrorStrings.S_NameOptions_Incorrect);
a++;
break;
case "-namespace":
@@ -168,7 +172,8 @@ new Colorful.Formatter("推荐在实体类目录创建 gen.bat双击它重新
break;
case "-db":
var dbargs = args[a + 1].Split(',', 2);
if (dbargs.Length != 2) throw new ArgumentException("-DB 参数错误格式为MySql,ConnectionString");
if (dbargs.Length != 2) throw new ArgumentException(CoreErrorStrings.S_DB_ParameterError);
switch (dbargs[0].Trim().ToLower())
{
case "mysql": ArgsDbType = DataType.MySql; break;
@@ -180,7 +185,8 @@ new Colorful.Formatter("推荐在实体类目录创建 gen.bat双击它重新
case "dameng": ArgsDbType = DataType.Dameng; break;
case "kingbasees": ArgsDbType = DataType.KingbaseES; break;
case "shentong": ArgsDbType = DataType.ShenTong; break;
default: throw new ArgumentException($"-DB 参数错误,不支持的类型:\"{dbargs[0]}\"");
case "clickhouse": ArgsDbType = DataType.ClickHouse; break;
default: throw new ArgumentException(CoreErrorStrings.S_DB_ParameterError_UnsupportedType(dbargs[0]));
}
ArgsConnectionString = dbargs[1].Trim();
a++;
@@ -194,6 +200,18 @@ new Colorful.Formatter("推荐在实体类目录创建 gen.bat双击它重新
if (Regex.IsMatch("", ArgsMatch)) { } //throw
a++;
break;
case "-json":
switch(args[a + 1].Trim().ToLower())
{
case "none":
ArgsJson = "";
break;
case "stj":
ArgsJson = "System.Text.Json";
break;
}
a++;
break;
case "-filename":
ArgsFileName = args[a + 1];
a++;
@@ -207,11 +225,11 @@ new Colorful.Formatter("推荐在实体类目录创建 gen.bat双击它重新
a++;
break;
default:
throw new ArgumentException($"错误的参数设置:{args[a]}");
throw new ArgumentException(CoreErrorStrings.S_WrongParameter(args[a]));
}
}
if (string.IsNullOrEmpty(ArgsConnectionString)) throw new ArgumentException($"-DB 参数错误,未提供 ConnectionString");
if (string.IsNullOrEmpty(ArgsConnectionString)) throw new ArgumentException(CoreErrorStrings.S_DB_Parameter_Error_NoConnectionString);
RazorEngine.Engine.Razor = RazorEngineService.Create(new RazorEngine.Configuration.TemplateServiceConfiguration
{
@@ -290,7 +308,7 @@ new Colorful.Formatter("推荐在实体类目录创建 gen.bat双击它重新
plus.AppendLine();
var outputFile = $"{ArgsOutput}{ArgsFileName.Replace("{name}", model.GetCsName(table.Name))}";
File.WriteAllText(outputFile, plus.ToString());
File.WriteAllText(outputFile, plus.ToString(), Encoding.UTF8);
switch (table.Type)
{
case DatabaseModel.DbTableType.TABLE:
@@ -313,14 +331,14 @@ new Colorful.Formatter("推荐在实体类目录创建 gen.bat双击它重新
var razorCshtml = ArgsOutput + "__razor.cshtml.txt";
if (File.Exists(razorCshtml) == false)
{
File.WriteAllText(razorCshtml, ArgsRazor);
File.WriteAllText(razorCshtml, ArgsRazor, Encoding.UTF8);
Console.WriteFormatted(" OUT -> " + razorCshtml + " (以后) 编辑它自定义模板生成\r\n", Color.Magenta);
++outputCounter;
}
File.WriteAllText(rebuildBat, $@"
FreeSql.Generator -Razor ""__razor.cshtml.txt"" -NameOptions {string.Join(",", ArgsNameOptions.Select(a => a ? 1 : 0))} -NameSpace {ArgsNameSpace} -DB ""{ArgsDbType},{ArgsConnectionString}""{(string.IsNullOrEmpty(ArgsFilter) ? "" : $" -Filter \"{ArgsFilter}\"")}{(string.IsNullOrEmpty(ArgsMatch) ? "" : $" -Match \"{ArgsMatch}\"")} -FileName ""{ArgsFileName}""
");
", Encoding.UTF8);
Console.WriteFormatted(" OUT -> " + rebuildBat + " (以后) 双击它重新生成实体\r\n", Color.Magenta);
++outputCounter;
}

View File

@@ -1,48 +1,60 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp3.1;net50</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IsPackable>true</IsPackable>
<PackAsTool>true</PackAsTool>
<Authors>FreeSql;ncc;YeXiangQin</Authors>
<Company>2881099</Company>
<Product>FreeSql</Product>
<Description>使用 FreeSql 快速生成数据库的实体类安装dotnet tool install -g FreeSql.Generator</Description>
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
<Version>2.5.100</Version>
<PackageTags>FreeSql DbFirst 实体生成器</PackageTags>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IsPackable>true</IsPackable>
<PackAsTool>true</PackAsTool>
<Authors>FreeSql;ncc;YeXiangQin</Authors>
<Company>2881099</Company>
<Product>FreeSql</Product>
<Description>使用 FreeSql 快速生成数据库的实体类安装dotnet tool install -g FreeSql.Generator</Description>
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
<PackageTags>FreeSql DbFirst 实体生成器</PackageTags>
<Version>3.5.210-preview20250626</Version>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Colorful.Console" Version="1.2.9" />
<PackageReference Include="RazorEngine.NetCore" Version="2.2.6" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<None Include="../../readme.md" Pack="true" PackagePath="\"/>
<None Include="../../logo.png" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<Reference Include="DmProvider">
<HintPath>..\..\Providers\FreeSql.Provider.Dameng\lib\DmProvider\netstandard2.0\DmProvider.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Reference Include="System.Data.OscarClient">
<HintPath>..\..\Providers\FreeSql.Provider.ShenTong\lib\System.Data.OscarClient.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Colorful.Console" Version="1.2.9" />
<PackageReference Include="RazorEngine.NetCore" Version="2.2.6" />
<PackageReference Include="System.CodeDom" Version="6.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Dameng\FreeSql.Provider.Dameng.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Firebird\FreeSql.Provider.Firebird.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.KingbaseES\FreeSql.Provider.KingbaseES.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.MySqlConnector\FreeSql.Provider.MySqlConnector.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Oracle\FreeSql.Provider.Oracle.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.PostgreSQL\FreeSql.Provider.PostgreSQL.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.ShenTong\FreeSql.Provider.ShenTong.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Sqlite\FreeSql.Provider.Sqlite.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.SqlServer\FreeSql.Provider.SqlServer.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="DmProvider">
<HintPath>..\..\Providers\FreeSql.Provider.Dameng\lib\DmProvider\netstandard2.0\DmProvider.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Reference Include="System.Data.OscarClient">
<HintPath>..\..\Providers\FreeSql.Provider.ShenTong\lib\System.Data.OscarClient.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Reference Include="Kdbndp">
<HintPath>..\..\Providers\FreeSql.Provider.KingbaseES\lib\Kdbndp.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Dameng\FreeSql.Provider.Dameng.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Firebird\FreeSql.Provider.Firebird.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.KingbaseES\FreeSql.Provider.KingbaseES.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.MySqlConnector\FreeSql.Provider.MySqlConnector.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Oracle\FreeSql.Provider.Oracle.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.PostgreSQL\FreeSql.Provider.PostgreSQL.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.ShenTong\FreeSql.Provider.ShenTong.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Sqlite\FreeSql.Provider.Sqlite.csproj" />
<ProjectReference Include="..\..\Providers\FreeSql.Provider.SqlServer\FreeSql.Provider.SqlServer.csproj" />
</ItemGroup>
</Project>

Some files were not shown because too many files have changed in this diff Show More