├── .gitattributes ├── .gitignore ├── Examples ├── dbcontext_01 │ ├── Controllers │ │ └── ValuesController.cs │ ├── DbContexts │ │ └── SongContext.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── dbcontext_01.csproj ├── domain_01 │ ├── Domains │ │ └── MusicDomain.cs │ ├── Entitys │ │ ├── Album.cs │ │ ├── AlbumSong.cs │ │ ├── Singer.cs │ │ └── Song.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── domain_01.csproj │ └── readme.md ├── orm_vs │ ├── Program.cs │ └── orm_vs.csproj └── repository_01 │ ├── Controllers │ └── SongController.cs │ ├── Entitys │ └── Song.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── repository_01.csproj ├── FreeSql.DbContext.Tests ├── FreeSql.DbContext.Tests.csproj ├── RepositoryTests.cs ├── UnitTest1.cs └── g.cs ├── FreeSql.DbContext.sln ├── FreeSql.DbContext ├── DbContext │ ├── DbContext.cs │ ├── DbContextAsync.cs │ ├── DbContextOptions.cs │ ├── DbContextOptionsBuilder.cs │ ├── DbContextSync.cs │ └── FreeContext.cs ├── DbSet │ ├── DbSet.cs │ ├── DbSetAsync.cs │ └── DbSetSync.cs ├── Extenssions │ ├── DependencyInjection.cs │ └── FreeSqlDbContextExtenssions.cs ├── FreeSql.DbContext.csproj ├── FreeSql.DbContext.xml ├── Repository │ ├── ContextSet │ │ ├── RepositoryDbContext.cs │ │ ├── RepositoryDbSet.cs │ │ └── RepositoryUnitOfWork.cs │ ├── DataFilter │ │ ├── DataFilter.cs │ │ └── DataFilterUtil.cs │ ├── Extenssions │ │ ├── DependencyInjection.cs │ │ └── FreeSqlRepositoryExtenssions.cs │ └── Repository │ │ ├── BaseRepository.cs │ │ ├── DefaultRepository.cs │ │ ├── GuidRepository.cs │ │ ├── IBaseRepository.cs │ │ ├── IBasicRepository.cs │ │ └── IReadOnlyRepository.cs ├── TempExtensions.cs └── UnitOfWork │ ├── IUnitOfWork.cs │ └── UnitOfWork.cs ├── FreeSql.Repository ├── FreeSql.Repository.csproj └── readme.md ├── LICENSE └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | package-lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Microsoft Azure ApplicationInsights config file 170 | ApplicationInsights.config 171 | 172 | # Windows Store app package directory 173 | AppPackages/ 174 | BundleArtifacts/ 175 | 176 | # Visual Studio cache files 177 | # files ending in .cache can be ignored 178 | *.[Cc]ache 179 | # but keep track of directories ending in .cache 180 | !*.[Cc]ache/ 181 | 182 | # Others 183 | ClientBin/ 184 | [Ss]tyle[Cc]op.* 185 | ~$* 186 | *~ 187 | *.dbmdl 188 | *.dbproj.schemaview 189 | *.pfx 190 | *.publishsettings 191 | node_modules/ 192 | orleans.codegen.cs 193 | 194 | # RIA/Silverlight projects 195 | Generated_Code/ 196 | 197 | # Backup & report files from converting an old project file 198 | # to a newer Visual Studio version. Backup files are not needed, 199 | # because we have git ;-) 200 | _UpgradeReport_Files/ 201 | Backup*/ 202 | UpgradeLog*.XML 203 | UpgradeLog*.htm 204 | 205 | # SQL Server files 206 | *.mdf 207 | *.ldf 208 | 209 | # Business Intelligence projects 210 | *.rdl.data 211 | *.bim.layout 212 | *.bim_*.settings 213 | 214 | # Microsoft Fakes 215 | FakesAssemblies/ 216 | 217 | # GhostDoc plugin setting file 218 | *.GhostDoc.xml 219 | 220 | # Node.js Tools for Visual Studio 221 | .ntvs_analysis.dat 222 | 223 | # Visual Studio 6 build log 224 | *.plg 225 | 226 | # Visual Studio 6 workspace options file 227 | *.opt 228 | 229 | # Visual Studio LightSwitch build output 230 | **/*.HTMLClient/GeneratedArtifacts 231 | **/*.DesktopClient/GeneratedArtifacts 232 | **/*.DesktopClient/ModelManifest.xml 233 | **/*.Server/GeneratedArtifacts 234 | **/*.Server/ModelManifest.xml 235 | _Pvt_Extensions 236 | 237 | # LightSwitch generated files 238 | GeneratedArtifacts/ 239 | ModelManifest.xml 240 | 241 | # Paket dependency manager 242 | .paket/paket.exe 243 | 244 | # FAKE - F# Make 245 | .fake/ -------------------------------------------------------------------------------- /Examples/dbcontext_01/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FreeSql; 6 | using Microsoft.AspNetCore.Mvc; 7 | 8 | namespace dbcontext_01.Controllers { 9 | [Route("api/[controller]")] 10 | [ApiController] 11 | public class ValuesController : ControllerBase { 12 | 13 | IFreeSql _orm; 14 | SongContext _songContext; 15 | public ValuesController(SongContext songContext, 16 | IFreeSql orm1, IFreeSql orm2, 17 | IFreeSql orm3 18 | ) { 19 | _songContext = songContext; 20 | _orm = orm1; 21 | 22 | } 23 | 24 | // GET api/values 25 | [HttpGet] 26 | async public Task Get() { 27 | 28 | long id = 0; 29 | 30 | try { 31 | 32 | var repos2Song = _orm.GetRepository(); 33 | repos2Song.Where(a => a.Id > 10).ToList(); 34 | //查询结果,进入 states 35 | 36 | var song = new Song { }; 37 | repos2Song.Insert(song); 38 | id = song.Id; 39 | 40 | var adds = Enumerable.Range(0, 100) 41 | .Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" }) 42 | .ToList(); 43 | //创建一堆无主键值 44 | 45 | repos2Song.Insert(adds); 46 | 47 | for (var a = 0; a < 10; a++) 48 | adds[a].Title = "dkdkdkdk" + a; 49 | 50 | repos2Song.Update(adds); 51 | //批量修改 52 | 53 | repos2Song.Delete(adds.Skip(10).Take(20).ToList()); 54 | //批量删除,10-20 元素的主键值会被清除 55 | 56 | adds.Last().Url = "skldfjlksdjglkjjcccc"; 57 | repos2Song.Update(adds.Last()); 58 | 59 | adds.First().Url = "skldfjlksdjglkjjcccc"; 60 | repos2Song.Update(adds.First()); 61 | 62 | 63 | var ctx = _songContext; 64 | var tag = new Tag { 65 | Name = "testaddsublist", 66 | Tags = new[] { 67 | new Tag { Name = "sub1" }, 68 | new Tag { Name = "sub2" }, 69 | new Tag { 70 | Name = "sub3", 71 | Tags = new[] { 72 | new Tag { Name = "sub3_01" } 73 | } 74 | } 75 | } 76 | }; 77 | ctx.Tags.Add(tag); 78 | 79 | 80 | ctx.UnitOfWork.GetOrBeginTransaction(); 81 | 82 | var tagAsync = new Tag { 83 | Name = "testaddsublist", 84 | Tags = new[] { 85 | new Tag { Name = "sub1" }, 86 | new Tag { Name = "sub2" }, 87 | new Tag { 88 | Name = "sub3", 89 | Tags = new[] { 90 | new Tag { Name = "sub3_01" } 91 | } 92 | } 93 | } 94 | }; 95 | await ctx.Tags.AddAsync(tagAsync); 96 | 97 | 98 | ctx.Songs.Select.Where(a => a.Id > 10).ToList(); 99 | //查询结果,进入 states 100 | 101 | song = new Song { }; 102 | //可插入的 song 103 | 104 | ctx.Songs.Add(song); 105 | id = song.Id; 106 | //因有自增类型,立即开启事务执行SQL,返回自增值 107 | 108 | adds = Enumerable.Range(0, 100) 109 | .Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" }) 110 | .ToList(); 111 | //创建一堆无主键值 112 | 113 | ctx.Songs.AddRange(adds); 114 | //立即执行,将自增值赋给 adds 所有元素,因为有自增类型,如果其他类型,指定传入主键值,不会立即执行 115 | 116 | for (var a = 0; a < 10; a++) 117 | adds[a].Title = "dkdkdkdk" + a; 118 | 119 | ctx.Songs.UpdateRange(adds); 120 | //批量修改,进入队列 121 | 122 | ctx.Songs.RemoveRange(adds.Skip(10).Take(20).ToList()); 123 | //批量删除,进入队列,完成时 10-20 元素的主键值会被清除 124 | 125 | //ctx.Songs.Update(adds.First()); 126 | 127 | adds.Last().Url = "skldfjlksdjglkjjcccc"; 128 | ctx.Songs.Update(adds.Last()); 129 | 130 | adds.First().Url = "skldfjlksdjglkjjcccc"; 131 | ctx.Songs.Update(adds.First()); 132 | 133 | //单条修改 urls 的值,进入队列 134 | 135 | //throw new Exception("回滚"); 136 | 137 | //ctx.Songs.Select.First(); 138 | //这里做一个查询,会立即打包【执行队列】,避免没有提交的数据,影响查询结果 139 | 140 | ctx.SaveChanges(); 141 | //打包【执行队列】,提交事务 142 | 143 | 144 | using (var uow = _orm.CreateUnitOfWork()) { 145 | 146 | var reposSong = uow.GetRepository(); 147 | reposSong.Where(a => a.Id > 10).ToList(); 148 | //查询结果,进入 states 149 | 150 | song = new Song { }; 151 | reposSong.Insert(song); 152 | id = song.Id; 153 | 154 | adds = Enumerable.Range(0, 100) 155 | .Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" }) 156 | .ToList(); 157 | //创建一堆无主键值 158 | 159 | reposSong.Insert(adds); 160 | 161 | for (var a = 0; a < 10; a++) 162 | adds[a].Title = "dkdkdkdk" + a; 163 | 164 | reposSong.Update(adds); 165 | //批量修改 166 | 167 | reposSong.Delete(adds.Skip(10).Take(20).ToList()); 168 | //批量删除,10-20 元素的主键值会被清除 169 | 170 | adds.Last().Url = "skldfjlksdjglkjjcccc"; 171 | reposSong.Update(adds.Last()); 172 | 173 | adds.First().Url = "skldfjlksdjglkjjcccc"; 174 | reposSong.Update(adds.First()); 175 | 176 | uow.Commit(); 177 | } 178 | 179 | 180 | 181 | //using (var ctx = new SongContext()) { 182 | 183 | // var song = new Song { }; 184 | // await ctx.Songs.AddAsync(song); 185 | // id = song.Id; 186 | 187 | // var adds = Enumerable.Range(0, 100) 188 | // .Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" }) 189 | // .ToList(); 190 | // await ctx.Songs.AddRangeAsync(adds); 191 | 192 | // for (var a = 0; a < adds.Count; a++) 193 | // adds[a].Title = "dkdkdkdk" + a; 194 | 195 | // ctx.Songs.UpdateRange(adds); 196 | 197 | // ctx.Songs.RemoveRange(adds.Skip(10).Take(20).ToList()); 198 | 199 | // //ctx.Songs.Update(adds.First()); 200 | 201 | // adds.Last().Url = "skldfjlksdjglkjjcccc"; 202 | // ctx.Songs.Update(adds.Last()); 203 | 204 | // //throw new Exception("回滚"); 205 | 206 | // await ctx.SaveChangesAsync(); 207 | //} 208 | } catch { 209 | var item = await _orm.Select().Where(a => a.Id == id).FirstAsync(); 210 | 211 | throw; 212 | } 213 | 214 | var item22 = await _orm.Select().Where(a => a.Id == id).FirstAsync(); 215 | var item33 = await _orm.Select().Where(a => a.Id > id).ToListAsync(); 216 | 217 | return item22.Id.ToString(); 218 | } 219 | 220 | // GET api/values/5 221 | [HttpGet("{id}")] 222 | public ActionResult Get(int id) { 223 | return "value"; 224 | } 225 | 226 | // POST api/values 227 | [HttpPost] 228 | public void Post([FromBody] string value) { 229 | } 230 | 231 | // PUT api/values/5 232 | [HttpPut("{id}")] 233 | public void Put(int id, [FromBody] string value) { 234 | } 235 | 236 | // DELETE api/values/5 237 | [HttpDelete("{id}")] 238 | public void Delete(int id) { 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /Examples/dbcontext_01/DbContexts/SongContext.cs: -------------------------------------------------------------------------------- 1 | using FreeSql; 2 | using FreeSql.DataAnnotations; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace dbcontext_01 { 7 | 8 | public class SongContext : DbContext { 9 | 10 | public DbSet Songs { get; set; } 11 | public DbSet Tags { get; set; } 12 | 13 | //protected override void OnConfiguring(DbContextOptionsBuilder builder) { 14 | // builder.UseFreeSql(dbcontext_01.Startup.Fsql); 15 | //} 16 | } 17 | 18 | 19 | public class Song { 20 | [Column(IsIdentity = true)] 21 | public int Id { get; set; } 22 | public DateTime? Create_time { get; set; } 23 | public bool? Is_deleted { get; set; } 24 | public string Title { get; set; } 25 | public string Url { get; set; } 26 | 27 | public virtual ICollection Tags { get; set; } 28 | 29 | [Column(IsVersion = true)] 30 | public long versionRow { get; set; } 31 | } 32 | public class Song_tag { 33 | public int Song_id { get; set; } 34 | public virtual Song Song { get; set; } 35 | 36 | public int Tag_id { get; set; } 37 | public virtual Tag Tag { get; set; } 38 | } 39 | 40 | public class Tag { 41 | [Column(IsIdentity = true)] 42 | public int Id { get; set; } 43 | public int? Parent_id { get; set; } 44 | public virtual Tag Parent { get; set; } 45 | 46 | public decimal? Ddd { get; set; } 47 | public string Name { get; set; } 48 | 49 | public virtual ICollection Songs { get; set; } 50 | public virtual ICollection Tags { get; set; } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Examples/dbcontext_01/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Numerics; 7 | using System.Threading.Tasks; 8 | using FreeSql; 9 | using FreeSql.DataAnnotations; 10 | using Microsoft.AspNetCore; 11 | using Microsoft.AspNetCore.Hosting; 12 | using Microsoft.Extensions.Configuration; 13 | using Microsoft.Extensions.Logging; 14 | 15 | namespace dbcontext_01 16 | { 17 | public class Program 18 | { 19 | 20 | public class Song { 21 | [Column(IsIdentity = true)] 22 | public int Id { get; set; } 23 | public string BigNumber { get; set; } 24 | 25 | [Column(IsVersion = true)]//使用简单 26 | public long versionRow { get; set; } 27 | } 28 | 29 | public class SongContext : DbContext { 30 | 31 | public DbSet Songs { get; set; } 32 | 33 | protected override void OnConfiguring(DbContextOptionsBuilder builder) { 34 | builder.UseFreeSql(fsql); 35 | } 36 | } 37 | static IFreeSql fsql; 38 | public static void Main(string[] args) { 39 | fsql = new FreeSql.FreeSqlBuilder() 40 | .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\dd2.db;Pooling=true;Max Pool Size=10") 41 | .UseAutoSyncStructure(true) 42 | .UseLazyLoading(true) 43 | .UseNoneCommandParameter(true) 44 | 45 | .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) 46 | .Build(); 47 | 48 | 49 | using (var ctx = new SongContext()) { 50 | var song = new Song { BigNumber = "1000000000000000000" }; 51 | ctx.Songs.Add(song); 52 | 53 | ctx.Songs.Update(song); 54 | 55 | song.BigNumber = (BigInteger.Parse(song.BigNumber) + 1).ToString(); 56 | ctx.Songs.Update(song); 57 | 58 | ctx.SaveChanges(); 59 | 60 | var sql = fsql.Update().SetSource(song).ToSql(); 61 | } 62 | 63 | CreateWebHostBuilder(args).Build().Run(); 64 | } 65 | 66 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 67 | WebHost.CreateDefaultBuilder(args) 68 | .UseStartup(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Examples/dbcontext_01/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:53030/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "dbcontext_01": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:53031/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Examples/dbcontext_01/Startup.cs: -------------------------------------------------------------------------------- 1 | using FreeSql; 2 | using FreeSql.DataAnnotations; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | using Swashbuckle.AspNetCore.Swagger; 9 | using System; 10 | using System.Diagnostics; 11 | using System.Linq; 12 | using System.Text; 13 | 14 | namespace dbcontext_01 15 | { 16 | public class Startup 17 | { 18 | public Startup(IConfiguration configuration, ILoggerFactory loggerFactory) 19 | { 20 | Configuration = configuration; 21 | 22 | Fsql = new FreeSql.FreeSqlBuilder() 23 | .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\document2.db;Pooling=true;Max Pool Size=10") 24 | //.UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=10") 25 | 26 | //.UseConnectionString(FreeSql.DataType.Oracle, "user id=user1;password=123456;data source=//127.0.0.1:1521/XE;Pooling=true;Max Pool Size=10") 27 | //.UseSyncStructureToUpper(true) 28 | 29 | .UseAutoSyncStructure(true) 30 | .UseLazyLoading(true) 31 | .UseNoneCommandParameter(true) 32 | 33 | .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText), 34 | (cmd, log) => Trace.WriteLine(log) 35 | ) 36 | .Build(); 37 | Fsql.Aop.SyncStructureBefore = (s, e) => { 38 | Console.WriteLine(e.Identifier + ": " + string.Join(", ", e.EntityTypes.Select(a => a.FullName))); 39 | }; 40 | Fsql.Aop.SyncStructureAfter = (s, e) => { 41 | Console.WriteLine(e.Identifier + ": " + string.Join(", ", e.EntityTypes.Select(a => a.FullName)) + " " + e.ElapsedMilliseconds + "ms\r\n" + e.Exception?.Message + e.Sql); 42 | }; 43 | 44 | Fsql.Aop.CurdBefore = (s, e) => { 45 | Console.WriteLine(e.Identifier + ": " + e.EntityType.FullName + ", " + e.Sql); 46 | }; 47 | Fsql.Aop.CurdAfter = (s, e) => { 48 | Console.WriteLine(e.Identifier + ": " + e.EntityType.FullName + " " + e.ElapsedMilliseconds + "ms, " + e.Sql); 49 | }; 50 | 51 | Fsql2 = new FreeSql.FreeSqlBuilder() 52 | .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\document222.db;Pooling=true;Max Pool Size=10") 53 | //.UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=10") 54 | .UseAutoSyncStructure(true) 55 | .UseLazyLoading(true) 56 | 57 | .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText), 58 | (cmd, log) => Trace.WriteLine(log) 59 | ) 60 | .Build(); 61 | } 62 | 63 | enum MySql { } 64 | enum PgSql { } 65 | 66 | public IConfiguration Configuration { get; } 67 | public static IFreeSql Fsql { get; private set; } 68 | public static IFreeSql Fsql2 { get; private set; } 69 | 70 | public void ConfigureServices(IServiceCollection services) 71 | { 72 | services.AddMvc(); 73 | services.AddSwaggerGen(options => { 74 | options.SwaggerDoc("v1", new Info { 75 | Version = "v1", 76 | Title = "FreeSql.DbContext API" 77 | }); 78 | //options.IncludeXmlComments(xmlPath); 79 | }); 80 | 81 | 82 | 83 | services.AddSingleton(Fsql); 84 | services.AddSingleton>(Fsql2); 85 | services.AddFreeDbContext(options => options.UseFreeSql(Fsql)); 86 | 87 | 88 | var sql1 = Fsql.Update(1).Set(a => a.Id + 10).ToSql(); 89 | var sql2 = Fsql.Update(1).Set(a => a.Title + 10).ToSql(); 90 | var sql3 = Fsql.Update(1).Set(a => a.Create_time.Value.AddHours(1)).ToSql(); 91 | } 92 | 93 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { 94 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 95 | Console.OutputEncoding = Encoding.GetEncoding("GB2312"); 96 | Console.InputEncoding = Encoding.GetEncoding("GB2312"); 97 | 98 | loggerFactory.AddConsole(Configuration.GetSection("Logging")); 99 | loggerFactory.AddDebug(); 100 | 101 | app.UseHttpMethodOverride(new HttpMethodOverrideOptions { FormFieldName = "X-Http-Method-Override" }); 102 | app.UseDeveloperExceptionPage(); 103 | app.UseMvc(); 104 | 105 | app.UseSwagger(); 106 | app.UseSwaggerUI(c => { 107 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "FreeSql.RESTful API V1"); 108 | }); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Examples/dbcontext_01/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Examples/dbcontext_01/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /Examples/dbcontext_01/dbcontext_01.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Examples/domain_01/Domains/MusicDomain.cs: -------------------------------------------------------------------------------- 1 | using domain_01.Entitys; 2 | using FreeSql; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace domain_01.Domains 9 | { 10 | public class MusicDomain 11 | { 12 | GuidRepository _singerRepostiry => g.orm.GetGuidRepository(); 13 | GuidRepository _albumRepostiry => g.orm.GetGuidRepository(); 14 | GuidRepository _songRepostiry => g.orm.GetGuidRepository(); 15 | GuidRepository _albumSongRepostiry => g.orm.GetGuidRepository(); 16 | 17 | public void SaveSong() { 18 | 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/domain_01/Entitys/Album.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace domain_01.Entitys 7 | { 8 | /// 9 | /// 专辑 10 | /// 11 | public class Album 12 | { 13 | public Guid Id { get; set; } 14 | 15 | public string Name { get; set; } 16 | 17 | public virtual ICollection Songs { get; set; } 18 | 19 | public DateTime PublishTime { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/domain_01/Entitys/AlbumSong.cs: -------------------------------------------------------------------------------- 1 | using FreeSql.DataAnnotations; 2 | using System; 3 | 4 | namespace domain_01.Entitys { 5 | public class AlbumSong { 6 | 7 | public Guid AlbumId { get; set; } 8 | 9 | public Guid SongId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/domain_01/Entitys/Singer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace domain_01.Entitys 7 | { 8 | /// 9 | /// 歌手 10 | /// 11 | public class Singer 12 | { 13 | public Guid Id { get; set; } 14 | 15 | public string Nickname { get; set; } 16 | 17 | public DateTime RegTime { get; set; } = DateTime.Now; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/domain_01/Entitys/Song.cs: -------------------------------------------------------------------------------- 1 | using FreeSql.DataAnnotations; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace domain_01.Entitys { 6 | 7 | /// 8 | /// 歌曲 9 | /// 10 | public class Song { 11 | 12 | public Guid Id { get; set; } 13 | 14 | public Guid SingerId { get; set; } 15 | public virtual Guid Singer { get; set; } 16 | 17 | public string Name { get; set; } 18 | 19 | public string Url { get; set; } 20 | 21 | public virtual ICollection Albums { get; set; } 22 | 23 | public DateTime RegTime { get; set; } = DateTime.Now; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Examples/domain_01/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace domain_01 { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateWebHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 20 | WebHost.CreateDefaultBuilder(args) 21 | .UseStartup(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Examples/domain_01/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:54379/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "repository_01": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:54383/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Examples/domain_01/Startup.cs: -------------------------------------------------------------------------------- 1 | using FreeSql; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Logging; 7 | using Swashbuckle.AspNetCore.Swagger; 8 | using System; 9 | using System.Text; 10 | 11 | namespace domain_01 { 12 | public class Startup { 13 | public Startup(IConfiguration configuration, ILoggerFactory loggerFactory) { 14 | Configuration = configuration; 15 | 16 | g.orm = new FreeSql.FreeSqlBuilder() 17 | .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\document.db;Pooling=true;Max Pool Size=10") 18 | .UseLogger(loggerFactory.CreateLogger()) 19 | .UseAutoSyncStructure(true) 20 | .Build(); 21 | } 22 | 23 | public IConfiguration Configuration { get; } 24 | 25 | public void ConfigureServices(IServiceCollection services) { 26 | services.AddSingleton(g.orm); 27 | 28 | services.AddMvc(); 29 | services.AddSwaggerGen(options => { 30 | options.SwaggerDoc("v1", new Info { 31 | Version = "v1", 32 | Title = "FreeSql.domain_01 API" 33 | }); 34 | //options.IncludeXmlComments(xmlPath); 35 | }); 36 | } 37 | 38 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { 39 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 40 | Console.OutputEncoding = Encoding.GetEncoding("GB2312"); 41 | Console.InputEncoding = Encoding.GetEncoding("GB2312"); 42 | 43 | loggerFactory.AddConsole(Configuration.GetSection("Logging")); 44 | loggerFactory.AddDebug(); 45 | 46 | app.UseHttpMethodOverride(new HttpMethodOverrideOptions { FormFieldName = "X-Http-Method-Override" }); 47 | app.UseDeveloperExceptionPage(); 48 | app.UseMvc(); 49 | 50 | app.UseSwagger(); 51 | app.UseSwaggerUI(c => { 52 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "FreeSql.domain_01 API V1"); 53 | }); 54 | } 55 | } 56 | } 57 | 58 | public static class g { 59 | public static IFreeSql orm { get; set; } 60 | } -------------------------------------------------------------------------------- /Examples/domain_01/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Examples/domain_01/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /Examples/domain_01/domain_01.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Code 22 | 23 | 24 | Code 25 | 26 | 27 | Code 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Examples/domain_01/readme.md: -------------------------------------------------------------------------------- 1 | ## 示例项目 domain_01 2 | 3 | 实体:Entitys 4 | 5 | 领域:Domains -------------------------------------------------------------------------------- /Examples/orm_vs/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using SqlSugar; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel.DataAnnotations; 6 | using System.ComponentModel.DataAnnotations.Schema; 7 | using System.Diagnostics; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | namespace orm_vs 14 | { 15 | class Program 16 | { 17 | static IFreeSql fsql = new FreeSql.FreeSqlBuilder() 18 | //.UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=20") 19 | .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") 20 | .UseAutoSyncStructure(false) 21 | .UseNoneCommandParameter(true) 22 | //.UseConfigEntityFromDbFirst(true) 23 | .Build(); 24 | 25 | static SqlSugarClient sugar { 26 | get => new SqlSugarClient(new ConnectionConfig() { 27 | //不欺负,让连接池100个最小 28 | //ConnectionString = "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Min Pool Size=20;Max Pool Size=20", 29 | //DbType = DbType.SqlServer, 30 | 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", 31 | DbType = DbType.MySql, 32 | IsAutoCloseConnection = true, 33 | InitKeyType = InitKeyType.Attribute 34 | }); 35 | } 36 | 37 | class SongContext : DbContext { 38 | public DbSet Songs { get; set; } 39 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { 40 | //optionsBuilder.UseSqlServer(@"Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Min Pool Size=21;Max Pool Size=21"); 41 | 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"); 42 | } 43 | } 44 | 45 | class FreeSongContext : FreeSql.DbContext { 46 | public FreeSql.DbSet Songs { get; set; } 47 | 48 | protected override void OnConfiguring(FreeSql.DbContextOptionsBuilder builder) { 49 | builder.UseFreeSql(fsql); 50 | } 51 | } 52 | 53 | static void Main(string[] args) { 54 | 55 | fsql.CodeFirst.SyncStructure(typeof(Song), typeof(Song_tag), typeof(Tag)); 56 | //sugar.CodeFirst.InitTables(typeof(Song), typeof(Song_tag), typeof(Tag)); 57 | //sugar创建表失败:SqlSugar.SqlSugarException: Sequence contains no elements 58 | 59 | //测试前清空数据 60 | fsql.Delete().Where(a => a.Id > 0).ExecuteAffrows(); 61 | sugar.Deleteable().Where(a => a.Id > 0).ExecuteCommand(); 62 | fsql.Ado.ExecuteNonQuery("delete from efcore_song"); 63 | 64 | 65 | var sql = fsql.Select().Where(a => a.Id == int.Parse("55")).ToSql(); 66 | 67 | var sb = new StringBuilder(); 68 | Console.WriteLine("插入性能:"); 69 | //Insert(sb, 1000, 1); 70 | //Console.Write(sb.ToString()); 71 | //sb.Clear(); 72 | //Insert(sb, 1000, 10); 73 | //Console.Write(sb.ToString()); 74 | //sb.Clear(); 75 | 76 | Insert(sb, 1, 1000); 77 | Console.Write(sb.ToString()); 78 | sb.Clear(); 79 | Insert(sb, 1, 10000); 80 | Console.Write(sb.ToString()); 81 | sb.Clear(); 82 | Insert(sb, 1, 50000); 83 | Console.Write(sb.ToString()); 84 | sb.Clear(); 85 | Insert(sb, 1, 100000); 86 | Console.Write(sb.ToString()); 87 | sb.Clear(); 88 | 89 | Console.WriteLine("查询性能:"); 90 | Select(sb, 1000, 1); 91 | Console.Write(sb.ToString()); 92 | sb.Clear(); 93 | Select(sb, 1000, 10); 94 | Console.Write(sb.ToString()); 95 | sb.Clear(); 96 | 97 | Select(sb, 1, 1000); 98 | Console.Write(sb.ToString()); 99 | sb.Clear(); 100 | Select(sb, 1, 10000); 101 | Console.Write(sb.ToString()); 102 | sb.Clear(); 103 | Select(sb, 1, 50000); 104 | Console.Write(sb.ToString()); 105 | sb.Clear(); 106 | Select(sb, 1, 100000); 107 | Console.Write(sb.ToString()); 108 | sb.Clear(); 109 | 110 | Console.WriteLine("更新:"); 111 | Update(sb, 1000, 1); 112 | Console.Write(sb.ToString()); 113 | sb.Clear(); 114 | Update(sb, 1000, 10); 115 | Console.Write(sb.ToString()); 116 | sb.Clear(); 117 | 118 | Update(sb, 1, 1000); 119 | Console.Write(sb.ToString()); 120 | sb.Clear(); 121 | Update(sb, 1, 10000); 122 | Console.Write(sb.ToString()); 123 | sb.Clear(); 124 | Update(sb, 1, 50000); 125 | Console.Write(sb.ToString()); 126 | sb.Clear(); 127 | Update(sb, 1, 100000); 128 | Console.Write(sb.ToString()); 129 | sb.Clear(); 130 | 131 | Console.WriteLine("测试结束,按任意键退出..."); 132 | Console.ReadKey(); 133 | } 134 | 135 | static void Select(StringBuilder sb, int forTime, int size) { 136 | Stopwatch sw = new Stopwatch(); 137 | sw.Restart(); 138 | for (var a = 0; a < forTime; a++) 139 | fsql.Select().Limit(size).ToList(); 140 | sw.Stop(); 141 | sb.AppendLine($"FreeSql Select {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms"); 142 | 143 | sw.Restart(); 144 | for (var a = 0; a < forTime; a++) 145 | sugar.Queryable().Take(size).ToList(); 146 | sw.Stop(); 147 | sb.AppendLine($"SqlSugar Select {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms"); 148 | 149 | sw.Restart(); 150 | for (var a = 0; a < forTime; a++) { 151 | using (var db = new SongContext()) { 152 | db.Songs.Take(size).AsNoTracking().ToList(); 153 | } 154 | } 155 | sw.Stop(); 156 | sb.AppendLine($"EFCore Select {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms\r\n"); 157 | } 158 | 159 | static void Insert(StringBuilder sb, int forTime, int size) { 160 | var songs = Enumerable.Range(0, size).Select(a => new Song { 161 | Create_time = DateTime.Now, 162 | Is_deleted = false, 163 | Title = $"Insert_{a}", 164 | Url = $"Url_{a}" 165 | }); 166 | 167 | //预热 168 | fsql.Insert(songs.First()).ExecuteAffrows(); 169 | sugar.Insertable(songs.First()).ExecuteCommand(); 170 | using (var db = new SongContext()) { 171 | //db.Configuration.AutoDetectChangesEnabled = false; 172 | db.Songs.AddRange(songs.First()); 173 | db.SaveChanges(); 174 | } 175 | Stopwatch sw = new Stopwatch(); 176 | 177 | sw.Restart(); 178 | for (var a = 0; a < forTime; a++) { 179 | fsql.Insert(songs).ExecuteAffrows(); 180 | } 181 | sw.Stop(); 182 | sb.AppendLine($"FreeSql Insert {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms"); 183 | 184 | sw.Restart(); 185 | for (var a = 0; a < forTime; a++) { 186 | using (var db = new FreeSongContext()) { 187 | db.Songs.AddRange(songs.ToArray()); 188 | db.SaveChanges(); 189 | } 190 | } 191 | sw.Stop(); 192 | sb.AppendLine($"FreeSql.DbContext Insert {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms"); 193 | 194 | sw.Restart(); 195 | Exception sugarEx = null; 196 | try { 197 | for (var a = 0; a < forTime; a++) 198 | sugar.Insertable(songs.ToArray()).ExecuteCommand(); 199 | } catch (Exception ex) { 200 | sugarEx = ex; 201 | } 202 | sw.Stop(); 203 | sb.AppendLine($"SqlSugar Insert {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms" + (sugarEx != null ? $"成绩无效,错误:{sugarEx.Message}" : "")); 204 | 205 | sw.Restart(); 206 | for (var a = 0; a < forTime; a++) { 207 | 208 | using (var db = new SongContext()) { 209 | //db.Configuration.AutoDetectChangesEnabled = false; 210 | db.Songs.AddRange(songs.ToArray()); 211 | db.SaveChanges(); 212 | } 213 | } 214 | sw.Stop(); 215 | sb.AppendLine($"EFCore Insert {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms\r\n"); 216 | } 217 | 218 | static void Update(StringBuilder sb, int forTime, int size) { 219 | Stopwatch sw = new Stopwatch(); 220 | 221 | var songs = fsql.Select().Limit(size).ToList(); 222 | sw.Restart(); 223 | for (var a = 0; a < forTime; a++) { 224 | fsql.Update().SetSource(songs).ExecuteAffrows(); 225 | } 226 | sw.Stop(); 227 | sb.AppendLine($"FreeSql Update {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms"); 228 | 229 | songs = sugar.Queryable().Take(size).ToList(); 230 | sw.Restart(); 231 | Exception sugarEx = null; 232 | try { 233 | for (var a = 0; a < forTime; a++) 234 | sugar.Updateable(songs).ExecuteCommand(); 235 | } catch (Exception ex) { 236 | sugarEx = ex; 237 | } 238 | sw.Stop(); 239 | sb.AppendLine($"SqlSugar Update {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms" + (sugarEx != null ? $"成绩无效,错误:{sugarEx.Message}" : "")); 240 | 241 | using (var db = new SongContext()) { 242 | songs = db.Songs.Take(size).AsNoTracking().ToList(); 243 | } 244 | sw.Restart(); 245 | for (var a = 0; a < forTime; a++) { 246 | 247 | using (var db = new SongContext()) { 248 | //db.Configuration.AutoDetectChangesEnabled = false; 249 | db.Songs.UpdateRange(songs.ToArray()); 250 | db.SaveChanges(); 251 | } 252 | } 253 | sw.Stop(); 254 | sb.AppendLine($"EFCore Update {size}条数据,循环{forTime}次,耗时{sw.ElapsedMilliseconds}ms\r\n"); 255 | } 256 | } 257 | 258 | [FreeSql.DataAnnotations.Table(Name = "freesql_song")] 259 | [SugarTable("sugar_song")] 260 | [Table("efcore_song")] 261 | public class Song { 262 | [FreeSql.DataAnnotations.Column(IsIdentity = true)] 263 | [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] 264 | [Key] 265 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 266 | public int Id { get; set; } 267 | public DateTime? Create_time { get; set; } 268 | public bool? Is_deleted { get; set; } 269 | public string Title { get; set; } 270 | public string Url { get; set; } 271 | 272 | [SugarColumn(IsIgnore = true)] 273 | [NotMapped] 274 | public virtual ICollection Tags { get; set; } 275 | } 276 | [FreeSql.DataAnnotations.Table(Name = "freesql_song_tag")] 277 | [SugarTable("sugar_song_tag")] 278 | [Table("efcore_song_tag")] 279 | public class Song_tag { 280 | public int Song_id { get; set; } 281 | [SugarColumn(IsIgnore = true)] 282 | [NotMapped] 283 | public virtual Song Song { get; set; } 284 | 285 | public int Tag_id { get; set; } 286 | [SugarColumn(IsIgnore = true)] 287 | [NotMapped] 288 | public virtual Tag Tag { get; set; } 289 | } 290 | [FreeSql.DataAnnotations.Table(Name = "freesql_tag")] 291 | [SugarTable("sugar_tag")] 292 | [Table("efcore_tag")] 293 | public class Tag { 294 | [FreeSql.DataAnnotations.Column(IsIdentity = true)] 295 | [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] 296 | [Key] 297 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 298 | public int Id { get; set; } 299 | public int? Parent_id { get; set; } 300 | [SugarColumn(IsIgnore = true)] 301 | [NotMapped] 302 | public virtual Tag Parent { get; set; } 303 | 304 | public decimal? Ddd { get; set; } 305 | public string Name { get; set; } 306 | 307 | [SugarColumn(IsIgnore = true)] 308 | [NotMapped] 309 | public virtual ICollection Songs { get; set; } 310 | [SugarColumn(IsIgnore = true)] 311 | [NotMapped] 312 | public virtual ICollection Tags { get; set; } 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /Examples/orm_vs/orm_vs.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ..\..\..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.entityframeworkcore\2.2.0\lib\netstandard2.0\Microsoft.EntityFrameworkCore.dll 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/repository_01/Controllers/SongController.cs: -------------------------------------------------------------------------------- 1 | using FreeSql; 2 | using Microsoft.AspNetCore.Mvc; 3 | using restful.Entitys; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace restful.Controllers { 10 | 11 | public class SongRepository : GuidRepository { 12 | public SongRepository(IFreeSql fsql) : base(fsql) { 13 | } 14 | } 15 | 16 | [Route("restapi/[controller]")] 17 | public class SongsController : Controller { 18 | 19 | BaseRepository _songRepository; 20 | 21 | public class xxxx { 22 | public int Id { get; set; } 23 | 24 | public bool IsDeleted { get; set; } 25 | } 26 | 27 | 28 | 29 | public SongsController(IFreeSql fsql, 30 | GuidRepository repos1, 31 | GuidRepository repos2, 32 | 33 | DefaultRepository repos11, 34 | DefaultRepository repos21, 35 | 36 | BaseRepository repos3, BaseRepository repos4, 37 | IBasicRepository repos31, IBasicRepository repos41, 38 | IReadOnlyRepository repos311, IReadOnlyRepository repos411, 39 | 40 | SongRepository reposSong 41 | ) { 42 | _songRepository = repos4; 43 | 44 | //test code 45 | var curd1 = fsql.GetRepository(); 46 | var curd2 = fsql.GetRepository(); 47 | var curd3 = fsql.GetRepository(); 48 | var curd4 = fsql.GetGuidRepository(); 49 | 50 | Console.WriteLine(repos1.Select.ToSql()); 51 | Console.WriteLine(reposSong.Select.ToSql()); 52 | 53 | Console.WriteLine(repos2.Select.ToSql()); 54 | Console.WriteLine(repos21.Select.ToSql()); 55 | 56 | using (reposSong.DataFilter.DisableAll()) { 57 | Console.WriteLine(reposSong.Select.ToSql()); 58 | } 59 | } 60 | 61 | [HttpGet] 62 | public Task> GetItems([FromQuery] string key, [FromQuery] int page = 1, [FromQuery] int limit = 20) { 63 | return _songRepository.Select.WhereIf(!string.IsNullOrEmpty(key), a => a.Title.Contains(key)).Page(page, limit).ToListAsync(); 64 | } 65 | 66 | [HttpGet("{id}")] 67 | public Task GetItem([FromRoute] int id) { 68 | return _songRepository.FindAsync(id); 69 | } 70 | 71 | public class ModelSong { 72 | public string title { get; set; } 73 | } 74 | 75 | [HttpPost, ProducesResponseType(201)] 76 | public Task Create([FromBody] ModelSong model) { 77 | return _songRepository.InsertAsync(new Song { Title = model.title }); 78 | } 79 | 80 | [HttpPut("{id}")] 81 | public Task Update([FromRoute] int id, [FromBody] ModelSong model) { 82 | return _songRepository.UpdateAsync(new Song { Id = id, Title = model.title }); 83 | } 84 | 85 | [HttpPatch("{id}")] 86 | async public Task UpdateDiy([FromRoute] int id, [FromForm] string title) { 87 | var up = _songRepository.UpdateDiy.Where(a => a.Id == id); 88 | if (!string.IsNullOrEmpty(title)) up.Set(a => a.Title, title); 89 | var ret = await up.ExecuteUpdatedAsync(); 90 | return ret.FirstOrDefault(); 91 | } 92 | 93 | [HttpDelete("{id}"), ProducesResponseType(204)] 94 | public Task Delete([FromRoute] int id) { 95 | return _songRepository.DeleteAsync(a => a.Id == id); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/repository_01/Entitys/Song.cs: -------------------------------------------------------------------------------- 1 | using FreeSql.DataAnnotations; 2 | using repository_01; 3 | 4 | namespace restful.Entitys { 5 | public class Song { 6 | 7 | [Column(IsIdentity = true)] 8 | public int Id { get; set; } 9 | public string Title { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/repository_01/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace repository_01 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Examples/repository_01/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:52751/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "repository_01": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:52752/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Examples/repository_01/Startup.cs: -------------------------------------------------------------------------------- 1 | using FreeSql; 2 | using FreeSql.DataAnnotations; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Logging; 9 | using restful.Entitys; 10 | using Swashbuckle.AspNetCore.Swagger; 11 | using System; 12 | using System.Diagnostics; 13 | using System.Linq; 14 | using System.Text; 15 | 16 | namespace repository_01 { 17 | 18 | /// 19 | /// 用户密码信息 20 | /// 21 | public class Sys1UserLogOn { 22 | [Column(IsPrimary = true, Name = "Id")] 23 | public Guid UserLogOnId { get; set; } 24 | public virtual Sys1User User { get; set; } 25 | } 26 | public class Sys1User { 27 | [Column(IsPrimary = true, Name = "Id")] 28 | public Guid UserId { get; set; } 29 | public virtual Sys1UserLogOn UserLogOn { get; set; } 30 | } 31 | 32 | public class Startup { 33 | public Startup(IConfiguration configuration, ILoggerFactory loggerFactory) { 34 | Configuration = configuration; 35 | 36 | Fsql = new FreeSql.FreeSqlBuilder() 37 | .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\document.db;Pooling=true;Max Pool Size=10") 38 | .UseAutoSyncStructure(true) 39 | .UseLazyLoading(true) 40 | 41 | .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) 42 | .Build(); 43 | 44 | var sysu = new Sys1User { }; 45 | Fsql.Insert().AppendData(sysu).ExecuteAffrows(); 46 | Fsql.Insert().AppendData(new Sys1UserLogOn { UserLogOnId = sysu.UserId }).ExecuteAffrows(); 47 | var a = Fsql.Select().ToList(); 48 | var b = Fsql.Select().Any(); 49 | } 50 | 51 | public IConfiguration Configuration { get; } 52 | public static IFreeSql Fsql { get; private set; } 53 | 54 | public void ConfigureServices(IServiceCollection services) { 55 | 56 | //services.AddTransient(s => s.) 57 | 58 | services.AddMvc(); 59 | services.AddSwaggerGen(options => { 60 | options.SwaggerDoc("v1", new Info { 61 | Version = "v1", 62 | Title = "FreeSql.RESTful API" 63 | }); 64 | //options.IncludeXmlComments(xmlPath); 65 | }); 66 | 67 | services.AddSingleton(Fsql); 68 | 69 | services.AddFreeRepository(filter => { 70 | filter 71 | //.Apply("test", a => a.Title == DateTime.Now.ToString() + System.Threading.Thread.CurrentThread.ManagedThreadId) 72 | .Apply("softdelete", a => a.IsDeleted == false); 73 | }, this.GetType().Assembly); 74 | } 75 | 76 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { 77 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 78 | Console.OutputEncoding = Encoding.GetEncoding("GB2312"); 79 | Console.InputEncoding = Encoding.GetEncoding("GB2312"); 80 | 81 | loggerFactory.AddConsole(Configuration.GetSection("Logging")); 82 | loggerFactory.AddDebug(); 83 | 84 | app.UseHttpMethodOverride(new HttpMethodOverrideOptions { FormFieldName = "X-Http-Method-Override" }); 85 | app.UseDeveloperExceptionPage(); 86 | app.UseMvc(); 87 | 88 | app.UseSwagger(); 89 | app.UseSwaggerUI(c => { 90 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "FreeSql.RESTful API V1"); 91 | }); 92 | } 93 | } 94 | 95 | public interface ISoftDelete { 96 | bool IsDeleted { get; set; } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/repository_01/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Warning", 6 | "Microsoft": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Examples/repository_01/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /Examples/repository_01/repository_01.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /FreeSql.DbContext.Tests/FreeSql.DbContext.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /FreeSql.DbContext.Tests/RepositoryTests.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2881099/FreeSql.DbContext/26786e5c9caa0194eaf7296bb09f4d8618bddd04/FreeSql.DbContext.Tests/RepositoryTests.cs -------------------------------------------------------------------------------- /FreeSql.DbContext.Tests/UnitTest1.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2881099/FreeSql.DbContext/26786e5c9caa0194eaf7296bb09f4d8618bddd04/FreeSql.DbContext.Tests/UnitTest1.cs -------------------------------------------------------------------------------- /FreeSql.DbContext.Tests/g.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Text; 5 | 6 | 7 | public class g { 8 | 9 | static Lazy sqlserverLazy = new Lazy(() => new FreeSql.FreeSqlBuilder() 10 | .UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=10") 11 | .UseAutoSyncStructure(true) 12 | .UseMonitorCommand( 13 | cmd => { 14 | Trace.WriteLine(cmd.CommandText); 15 | }, //监听SQL命令对象,在执行前 16 | (cmd, traceLog) => { 17 | Console.WriteLine(traceLog); 18 | }) //监听SQL命令对象,在执行后 19 | .UseLazyLoading(true) 20 | .UseNoneCommandParameter(true) 21 | .Build()); 22 | public static IFreeSql sqlserver => sqlserverLazy.Value; 23 | 24 | static Lazy mysqlLazy = new Lazy(() => new FreeSql.FreeSqlBuilder() 25 | .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=10") 26 | .UseAutoSyncStructure(true) 27 | .UseMonitorCommand( 28 | cmd => { 29 | Trace.WriteLine(cmd.CommandText); 30 | }, //监听SQL命令对象,在执行前 31 | (cmd, traceLog) => { 32 | Console.WriteLine(traceLog); 33 | }) //监听SQL命令对象,在执行后 34 | .UseLazyLoading(true) 35 | .UseNoneCommandParameter(true) 36 | .Build()); 37 | public static IFreeSql mysql => mysqlLazy.Value; 38 | 39 | static Lazy pgsqlLazy = new Lazy(() => new FreeSql.FreeSqlBuilder() 40 | .UseConnectionString(FreeSql.DataType.PostgreSQL, "Host=192.168.164.10;Port=5432;Username=postgres;Password=123456;Database=tedb;Pooling=true;Maximum Pool Size=10") 41 | .UseAutoSyncStructure(true) 42 | .UseSyncStructureToLower(true) 43 | .UseLazyLoading(true) 44 | .UseMonitorCommand( 45 | cmd => { 46 | Trace.WriteLine(cmd.CommandText); 47 | }, //监听SQL命令对象,在执行前 48 | (cmd, traceLog) => { 49 | Console.WriteLine(traceLog); 50 | }) //监听SQL命令对象,在执行后 51 | .UseNoneCommandParameter(true) 52 | .Build()); 53 | public static IFreeSql pgsql => pgsqlLazy.Value; 54 | 55 | static Lazy oracleLazy = new Lazy(() => new FreeSql.FreeSqlBuilder() 56 | .UseConnectionString(FreeSql.DataType.Oracle, "user id=user1;password=123456;data source=//127.0.0.1:1521/XE;Pooling=true;Max Pool Size=10") 57 | .UseAutoSyncStructure(true) 58 | .UseLazyLoading(true) 59 | .UseSyncStructureToUpper(true) 60 | .UseNoneCommandParameter(true) 61 | 62 | .UseMonitorCommand( 63 | cmd => { 64 | Trace.WriteLine(cmd.CommandText); 65 | }, //监听SQL命令对象,在执行前 66 | (cmd, traceLog) => { 67 | Console.WriteLine(traceLog); 68 | }) //监听SQL命令对象,在执行后 69 | .Build()); 70 | public static IFreeSql oracle => oracleLazy.Value; 71 | 72 | static Lazy sqliteLazy = new Lazy(() => new FreeSql.FreeSqlBuilder() 73 | .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|/document22.db;Attachs=xxxtb.db;Pooling=true;Max Pool Size=10") 74 | .UseAutoSyncStructure(true) 75 | .UseLazyLoading(true) 76 | .UseMonitorCommand( 77 | cmd => { 78 | Trace.WriteLine(cmd.CommandText); 79 | }, //监听SQL命令对象,在执行前 80 | (cmd, traceLog) => { 81 | Console.WriteLine(traceLog); 82 | }) //监听SQL命令对象,在执行后 83 | .UseNoneCommandParameter(true) 84 | .Build()); 85 | public static IFreeSql sqlite => sqliteLazy.Value; 86 | } 87 | -------------------------------------------------------------------------------- /FreeSql.DbContext.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.136 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.DbContext", "FreeSql.DbContext\FreeSql.DbContext.csproj", "{4BFA2127-4971-4E92-97EE-821F4CFA629B}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B395C044-C9D4-4F52-9F5D-5A6E6123AE0C}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dbcontext_01", "Examples\dbcontext_01\dbcontext_01.csproj", "{889DF104-629C-41DF-ACF7-461A2A2AB730}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "repository_01", "Examples\repository_01\repository_01.csproj", "{88DE7C66-8B93-45E3-ABF4-2EF1CDFAC479}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{C11D358B-990B-4EC6-B54C-867C7F1A894F}" 15 | ProjectSection(SolutionItems) = preProject 16 | readme.md = readme.md 17 | EndProjectSection 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "orm_vs", "Examples\orm_vs\orm_vs.csproj", "{0E11197B-63F7-4C16-AA36-7214B177172B}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.DbContext.Tests", "FreeSql.DbContext.Tests\FreeSql.DbContext.Tests.csproj", "{A2FE3944-61D3-4516-AC0E-DA9181D87ED2}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeSql.Repository", "FreeSql.Repository\FreeSql.Repository.csproj", "{9762D0E2-AF2F-431B-B37B-FC00E24DF2AA}" 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Release|Any CPU = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {4BFA2127-4971-4E92-97EE-821F4CFA629B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {4BFA2127-4971-4E92-97EE-821F4CFA629B}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {4BFA2127-4971-4E92-97EE-821F4CFA629B}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {4BFA2127-4971-4E92-97EE-821F4CFA629B}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {889DF104-629C-41DF-ACF7-461A2A2AB730}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {889DF104-629C-41DF-ACF7-461A2A2AB730}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {889DF104-629C-41DF-ACF7-461A2A2AB730}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {889DF104-629C-41DF-ACF7-461A2A2AB730}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {88DE7C66-8B93-45E3-ABF4-2EF1CDFAC479}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {88DE7C66-8B93-45E3-ABF4-2EF1CDFAC479}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {88DE7C66-8B93-45E3-ABF4-2EF1CDFAC479}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {88DE7C66-8B93-45E3-ABF4-2EF1CDFAC479}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {0E11197B-63F7-4C16-AA36-7214B177172B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {0E11197B-63F7-4C16-AA36-7214B177172B}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {0E11197B-63F7-4C16-AA36-7214B177172B}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {0E11197B-63F7-4C16-AA36-7214B177172B}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {A2FE3944-61D3-4516-AC0E-DA9181D87ED2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {A2FE3944-61D3-4516-AC0E-DA9181D87ED2}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {A2FE3944-61D3-4516-AC0E-DA9181D87ED2}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {A2FE3944-61D3-4516-AC0E-DA9181D87ED2}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {9762D0E2-AF2F-431B-B37B-FC00E24DF2AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {9762D0E2-AF2F-431B-B37B-FC00E24DF2AA}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {9762D0E2-AF2F-431B-B37B-FC00E24DF2AA}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {9762D0E2-AF2F-431B-B37B-FC00E24DF2AA}.Release|Any CPU.Build.0 = Release|Any CPU 55 | EndGlobalSection 56 | GlobalSection(SolutionProperties) = preSolution 57 | HideSolutionNode = FALSE 58 | EndGlobalSection 59 | GlobalSection(NestedProjects) = preSolution 60 | {889DF104-629C-41DF-ACF7-461A2A2AB730} = {B395C044-C9D4-4F52-9F5D-5A6E6123AE0C} 61 | {88DE7C66-8B93-45E3-ABF4-2EF1CDFAC479} = {B395C044-C9D4-4F52-9F5D-5A6E6123AE0C} 62 | {0E11197B-63F7-4C16-AA36-7214B177172B} = {B395C044-C9D4-4F52-9F5D-5A6E6123AE0C} 63 | EndGlobalSection 64 | GlobalSection(ExtensibilityGlobals) = postSolution 65 | SolutionGuid = {3F03089A-7411-482B-8F25-5AA7CB5AB2E5} 66 | EndGlobalSection 67 | EndGlobal 68 | -------------------------------------------------------------------------------- /FreeSql.DbContext/DbContext/DbContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Concurrent; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Threading.Tasks; 7 | 8 | namespace FreeSql { 9 | public abstract partial class DbContext : IDisposable { 10 | 11 | internal IFreeSql _orm; 12 | internal IFreeSql _fsql => _orm ?? throw new ArgumentNullException("请在 OnConfiguring 或 AddFreeDbContext 中配置 UseFreeSql"); 13 | 14 | public IFreeSql Orm => _fsql; 15 | 16 | protected IUnitOfWork _uowPriv; 17 | internal IUnitOfWork _uow => _isUseUnitOfWork ? (_uowPriv ?? (_uowPriv = new UnitOfWork(_fsql))) : null; 18 | internal bool _isUseUnitOfWork = true; //不使用工作单元事务 19 | 20 | public IUnitOfWork UnitOfWork => _uow; 21 | 22 | DbContextOptions _options; 23 | internal DbContextOptions Options { 24 | get { 25 | if (_options != null) return _options; 26 | if (FreeSqlDbContextExtenssions._dicSetDbContextOptions.TryGetValue(Orm, out _options)) return _options; 27 | _options = new DbContextOptions(); 28 | return _options; 29 | } 30 | } 31 | 32 | static ConcurrentDictionary _dicGetDbSetProps = new ConcurrentDictionary(); 33 | protected DbContext() { 34 | 35 | var builder = new DbContextOptionsBuilder(); 36 | OnConfiguring(builder); 37 | _orm = builder._fsql; 38 | 39 | if (_orm != null) InitPropSets(); 40 | } 41 | 42 | internal void InitPropSets() { 43 | var props = _dicGetDbSetProps.GetOrAdd(this.GetType(), tp => 44 | tp.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) 45 | .Where(a => a.PropertyType.IsGenericType && 46 | a.PropertyType == typeof(DbSet<>).MakeGenericType(a.PropertyType.GenericTypeArguments[0])).ToArray()); 47 | 48 | foreach (var prop in props) { 49 | var set = this.Set(prop.PropertyType.GenericTypeArguments[0]); 50 | 51 | prop.SetValue(this, set); 52 | AllSets.Add(prop.Name, set); 53 | } 54 | } 55 | 56 | protected virtual void OnConfiguring(DbContextOptionsBuilder builder) { 57 | 58 | } 59 | 60 | protected Dictionary _dicSet = new Dictionary(); 61 | public DbSet Set() where TEntity : class => this.Set(typeof(TEntity)) as DbSet; 62 | public virtual IDbSet Set(Type entityType) { 63 | if (_dicSet.ContainsKey(entityType)) return _dicSet[entityType]; 64 | var sd = Activator.CreateInstance(typeof(DbContextDbSet<>).MakeGenericType(entityType), this) as IDbSet; 65 | if (entityType != typeof(object)) _dicSet.Add(entityType, sd); 66 | return sd; 67 | } 68 | protected Dictionary AllSets { get; } = new Dictionary(); 69 | 70 | #region DbSet 快速代理 71 | /// 72 | /// 添加 73 | /// 74 | /// 75 | /// 76 | public void Add(TEntity data) where TEntity : class => this.Set().Add(data); 77 | public void AddRange(IEnumerable data) where TEntity : class => this.Set().AddRange(data); 78 | public Task AddAsync(TEntity data) where TEntity : class => this.Set().AddAsync(data); 79 | public Task AddRangeAsync(IEnumerable data) where TEntity : class => this.Set().AddRangeAsync(data); 80 | 81 | /// 82 | /// 更新 83 | /// 84 | /// 85 | /// 86 | public void Update(TEntity data) where TEntity : class => this.Set().Update(data); 87 | public void UpdateRange(IEnumerable data) where TEntity : class => this.Set().UpdateRange(data); 88 | public Task UpdateAsync(TEntity data) where TEntity : class => this.Set().UpdateAsync(data); 89 | public Task UpdateRangeAsync(IEnumerable data) where TEntity : class => this.Set().UpdateRangeAsync(data); 90 | 91 | /// 92 | /// 删除 93 | /// 94 | /// 95 | /// 96 | public void Remove(TEntity data) where TEntity : class => this.Set().Remove(data); 97 | public void RemoveRange(IEnumerable data) where TEntity : class => this.Set().RemoveRange(data); 98 | 99 | /// 100 | /// 添加或更新 101 | /// 102 | /// 103 | /// 104 | public void AddOrUpdate(TEntity data) where TEntity : class => this.Set().AddOrUpdate(data); 105 | public Task AddOrUpdateAsync(TEntity data) where TEntity : class => this.Set().AddOrUpdateAsync(data); 106 | 107 | /// 108 | /// 附加实体,可用于不查询就更新或删除 109 | /// 110 | /// 111 | /// 112 | public void Attach(TEntity data) where TEntity : class => this.Set().Attach(data); 113 | public void AttachRange(IEnumerable data) where TEntity : class => this.Set().AttachRange(data); 114 | #endregion 115 | 116 | internal class ExecCommandInfo { 117 | public ExecCommandInfoType actionType { get; set; } 118 | public IDbSet dbSet { get; set; } 119 | public Type stateType { get; set; } 120 | public object state { get; set; } 121 | } 122 | internal enum ExecCommandInfoType { Insert, Update, Delete } 123 | Queue _actions = new Queue(); 124 | internal int _affrows = 0; 125 | 126 | internal void EnqueueAction(ExecCommandInfoType actionType, IDbSet dbSet, Type stateType, object state) { 127 | _actions.Enqueue(new ExecCommandInfo { actionType = actionType, dbSet = dbSet, stateType = stateType, state = state }); 128 | } 129 | 130 | ~DbContext() { 131 | this.Dispose(); 132 | } 133 | bool _isdisposed = false; 134 | public void Dispose() { 135 | if (_isdisposed) return; 136 | try { 137 | _actions.Clear(); 138 | 139 | foreach (var set in _dicSet) 140 | try { 141 | set.Value.Dispose(); 142 | } catch { } 143 | 144 | _dicSet.Clear(); 145 | AllSets.Clear(); 146 | 147 | _uow?.Rollback(); 148 | } finally { 149 | _isdisposed = true; 150 | GC.SuppressFinalize(this); 151 | } 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /FreeSql.DbContext/DbContext/DbContextAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Linq.Expressions; 6 | using System.Threading.Tasks; 7 | 8 | namespace FreeSql { 9 | partial class DbContext { 10 | 11 | async public virtual Task SaveChangesAsync() { 12 | await ExecCommandAsync(); 13 | _uow?.Commit(); 14 | var ret = _affrows; 15 | _affrows = 0; 16 | return ret; 17 | } 18 | 19 | static Dictionary>>> _dicExecCommandDbContextBetchAsync = new Dictionary>>>(); 20 | async internal Task ExecCommandAsync() { 21 | if (isExecCommanding) return; 22 | if (_actions.Any() == false) return; 23 | isExecCommanding = true; 24 | 25 | ExecCommandInfo oldinfo = null; 26 | var states = new List(); 27 | 28 | Func> dbContextBetch = methodName => { 29 | if (_dicExecCommandDbContextBetchAsync.TryGetValue(oldinfo.stateType, out var trydic) == false) 30 | trydic = new Dictionary>>(); 31 | if (trydic.TryGetValue(methodName, out var tryfunc) == false) { 32 | var arrType = oldinfo.stateType.MakeArrayType(); 33 | var dbsetType = oldinfo.dbSet.GetType().BaseType; 34 | var dbsetTypeMethod = dbsetType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { arrType }, null); 35 | 36 | var returnTarget = Expression.Label(typeof(Task)); 37 | var parm1DbSet = Expression.Parameter(typeof(object)); 38 | var parm2Vals = Expression.Parameter(typeof(object[])); 39 | var var1Vals = Expression.Variable(arrType); 40 | tryfunc = Expression.Lambda>>(Expression.Block( 41 | new[] { var1Vals }, 42 | Expression.Assign(var1Vals, Expression.Convert(global::FreeSql.Internal.Utils.GetDataReaderValueBlockExpression(arrType, parm2Vals), arrType)), 43 | Expression.Return(returnTarget, Expression.Call(Expression.Convert(parm1DbSet, dbsetType), dbsetTypeMethod, var1Vals)), 44 | Expression.Label(returnTarget, Expression.Default(typeof(Task))) 45 | ), new[] { parm1DbSet, parm2Vals }).Compile(); 46 | trydic.Add(methodName, tryfunc); 47 | } 48 | return tryfunc(oldinfo.dbSet, states.ToArray()); 49 | }; 50 | Func funcDelete = async () => { 51 | _affrows += await dbContextBetch("DbContextBetchRemoveAsync"); 52 | states.Clear(); 53 | }; 54 | Func funcInsert = async () => { 55 | _affrows += await dbContextBetch("DbContextBetchAddAsync"); 56 | states.Clear(); 57 | }; 58 | Func funcUpdate = async (isLiveUpdate) => { 59 | var affrows = 0; 60 | if (isLiveUpdate) affrows = await dbContextBetch("DbContextBetchUpdateNowAsync"); 61 | else affrows = await dbContextBetch("DbContextBetchUpdateAsync"); 62 | if (affrows == -999) { //最后一个元素已被删除 63 | states.RemoveAt(states.Count - 1); 64 | return; 65 | } 66 | if (affrows == -998 || affrows == -997) { //没有执行更新 67 | var laststate = states[states.Count - 1]; 68 | states.Clear(); 69 | if (affrows == -997) states.Add(laststate); //保留最后一个 70 | } 71 | if (affrows > 0) { 72 | _affrows += affrows; 73 | var islastNotUpdated = states.Count != affrows; 74 | var laststate = states[states.Count - 1]; 75 | states.Clear(); 76 | if (islastNotUpdated) states.Add(laststate); //保留最后一个 77 | } 78 | }; 79 | 80 | while (_actions.Any() || states.Any()) { 81 | var info = _actions.Any() ? _actions.Dequeue() : null; 82 | if (oldinfo == null) oldinfo = info; 83 | var isLiveUpdate = false; 84 | 85 | if (_actions.Any() == false && states.Any() || 86 | info != null && oldinfo.actionType != info.actionType || 87 | info != null && oldinfo.stateType != info.stateType) { 88 | 89 | if (info != null && oldinfo.actionType == info.actionType && oldinfo.stateType == info.stateType) { 90 | //最后一个,合起来发送 91 | states.Add(info.state); 92 | info = null; 93 | } 94 | 95 | switch (oldinfo.actionType) { 96 | case ExecCommandInfoType.Insert: 97 | await funcInsert(); 98 | break; 99 | case ExecCommandInfoType.Delete: 100 | await funcDelete(); 101 | break; 102 | } 103 | isLiveUpdate = true; 104 | } 105 | 106 | if (isLiveUpdate || oldinfo.actionType == ExecCommandInfoType.Update) { 107 | if (states.Any()) 108 | await funcUpdate(isLiveUpdate); 109 | } 110 | 111 | if (info != null) { 112 | states.Add(info.state); 113 | oldinfo = info; 114 | } 115 | } 116 | isExecCommanding = false; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /FreeSql.DbContext/DbContext/DbContextOptions.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace FreeSql { 3 | public class DbContextOptions { 4 | 5 | /// 6 | /// 是否开启一对多,联级保存功能 7 | /// 8 | public bool EnableAddOrUpdateNavigateList { get; set; } = true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /FreeSql.DbContext/DbContext/DbContextOptionsBuilder.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace FreeSql { 4 | public class DbContextOptionsBuilder { 5 | 6 | internal IFreeSql _fsql; 7 | 8 | public DbContextOptionsBuilder UseFreeSql(IFreeSql orm) { 9 | _fsql = orm; 10 | return this; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /FreeSql.DbContext/DbContext/DbContextSync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Linq.Expressions; 6 | 7 | namespace FreeSql { 8 | partial class DbContext { 9 | 10 | public virtual int SaveChanges() { 11 | ExecCommand(); 12 | _uow?.Commit(); 13 | var ret = _affrows; 14 | _affrows = 0; 15 | return ret; 16 | } 17 | 18 | static Dictionary>> _dicExecCommandDbContextBetch = new Dictionary>>(); 19 | bool isExecCommanding = false; 20 | internal void ExecCommand() { 21 | if (isExecCommanding) return; 22 | if (_actions.Any() == false) return; 23 | isExecCommanding = true; 24 | 25 | ExecCommandInfo oldinfo = null; 26 | var states = new List(); 27 | 28 | Func dbContextBetch = methodName => { 29 | if (_dicExecCommandDbContextBetch.TryGetValue(oldinfo.stateType, out var trydic) == false) 30 | trydic = new Dictionary>(); 31 | if (trydic.TryGetValue(methodName, out var tryfunc) == false) { 32 | var arrType = oldinfo.stateType.MakeArrayType(); 33 | var dbsetType = oldinfo.dbSet.GetType().BaseType; 34 | var dbsetTypeMethod = dbsetType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { arrType }, null); 35 | 36 | var returnTarget = Expression.Label(typeof(int)); 37 | var parm1DbSet = Expression.Parameter(typeof(object)); 38 | var parm2Vals = Expression.Parameter(typeof(object[])); 39 | var var1Vals = Expression.Variable(arrType); 40 | tryfunc = Expression.Lambda>(Expression.Block( 41 | new[] { var1Vals }, 42 | Expression.Assign(var1Vals, Expression.Convert(global::FreeSql.Internal.Utils.GetDataReaderValueBlockExpression(arrType, parm2Vals), arrType)), 43 | Expression.Return(returnTarget, Expression.Call(Expression.Convert(parm1DbSet, dbsetType), dbsetTypeMethod, var1Vals)), 44 | Expression.Label(returnTarget, Expression.Default(typeof(int))) 45 | ), new[] { parm1DbSet, parm2Vals }).Compile(); 46 | trydic.Add(methodName, tryfunc); 47 | } 48 | return tryfunc(oldinfo.dbSet, states.ToArray()); 49 | }; 50 | Action funcDelete = () => { 51 | _affrows += dbContextBetch("DbContextBetchRemove"); 52 | states.Clear(); 53 | }; 54 | Action funcInsert = () => { 55 | _affrows += dbContextBetch("DbContextBetchAdd"); 56 | states.Clear(); 57 | }; 58 | Action funcUpdate = isLiveUpdate => { 59 | var affrows = 0; 60 | if (isLiveUpdate) affrows = dbContextBetch("DbContextBetchUpdateNow"); 61 | else affrows = dbContextBetch("DbContextBetchUpdate"); 62 | if (affrows == -999) { //最后一个元素已被删除 63 | states.RemoveAt(states.Count - 1); 64 | return; 65 | } 66 | if (affrows == -998 || affrows == -997) { //没有执行更新 67 | var laststate = states[states.Count - 1]; 68 | states.Clear(); 69 | if (affrows == -997) states.Add(laststate); //保留最后一个 70 | } 71 | if (affrows > 0) { 72 | _affrows += affrows; 73 | var islastNotUpdated = states.Count != affrows; 74 | var laststate = states[states.Count - 1]; 75 | states.Clear(); 76 | if (islastNotUpdated) states.Add(laststate); //保留最后一个 77 | } 78 | }; 79 | 80 | while (_actions.Any() || states.Any()) { 81 | var info = _actions.Any() ? _actions.Dequeue() : null; 82 | if (oldinfo == null) oldinfo = info; 83 | var isLiveUpdate = false; 84 | 85 | if (_actions.Any() == false && states.Any() || 86 | info != null && oldinfo.actionType != info.actionType || 87 | info != null && oldinfo.stateType != info.stateType) { 88 | 89 | if (info != null && oldinfo.actionType == info.actionType && oldinfo.stateType == info.stateType) { 90 | //最后一个,合起来发送 91 | states.Add(info.state); 92 | info = null; 93 | } 94 | 95 | switch (oldinfo.actionType) { 96 | case ExecCommandInfoType.Insert: 97 | funcInsert(); 98 | break; 99 | case ExecCommandInfoType.Delete: 100 | funcDelete(); 101 | break; 102 | } 103 | isLiveUpdate = true; 104 | } 105 | 106 | if (isLiveUpdate || oldinfo.actionType == ExecCommandInfoType.Update) { 107 | if (states.Any()) 108 | funcUpdate(isLiveUpdate); 109 | } 110 | 111 | if (info != null) { 112 | states.Add(info.state); 113 | oldinfo = info; 114 | } 115 | } 116 | isExecCommanding = false; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /FreeSql.DbContext/DbContext/FreeContext.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace FreeSql { 4 | public class FreeContext : DbContext { 5 | 6 | public FreeContext(IFreeSql orm) { 7 | _orm = orm; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /FreeSql.DbContext/DbSet/DbSet.cs: -------------------------------------------------------------------------------- 1 | using FreeSql.Extensions.EntityUtil; 2 | using FreeSql.Internal.Model; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Collections.Concurrent; 7 | using System.Linq; 8 | using System.Linq.Expressions; 9 | using System.Reflection; 10 | 11 | namespace FreeSql { 12 | 13 | internal class DbContextDbSet : DbSet where TEntity : class { 14 | 15 | public DbContextDbSet(DbContext ctx) { 16 | _ctx = ctx; 17 | _uow = ctx._uow; 18 | _fsql = ctx._fsql; 19 | } 20 | } 21 | 22 | public interface IDbSet : IDisposable { 23 | Type EntityType { get; } 24 | } 25 | public abstract partial class DbSet : IDbSet where TEntity : class { 26 | 27 | internal DbContext _ctx; 28 | internal IUnitOfWork _uow; 29 | internal IFreeSql _fsql; 30 | 31 | protected virtual ISelect OrmSelect(object dywhere) { 32 | DbContextExecCommand(); //查询前先提交,否则会出脏读 33 | return _fsql.Select().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction(false)).TrackToList(TrackToList).WhereDynamic(dywhere); 34 | } 35 | 36 | ~DbSet() { 37 | this.Dispose(); 38 | } 39 | bool _isdisposed = false; 40 | public void Dispose() { 41 | if (_isdisposed) return; 42 | try { 43 | this._dicUpdateTimes.Clear(); 44 | this._states.Clear(); 45 | } finally { 46 | _isdisposed = true; 47 | GC.SuppressFinalize(this); 48 | } 49 | } 50 | 51 | protected virtual IInsert OrmInsert() => _fsql.Insert().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction()); 52 | protected virtual IInsert OrmInsert(TEntity data) => _fsql.Insert().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction()).AppendData(data); 53 | protected virtual IInsert OrmInsert(IEnumerable data) => _fsql.Insert().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction()).AppendData(data); 54 | 55 | protected virtual IUpdate OrmUpdate(IEnumerable entitys) => _fsql.Update().AsType(_entityType).SetSource(entitys).WithTransaction(_uow?.GetOrBeginTransaction()); 56 | protected virtual IDelete OrmDelete(object dywhere) => _fsql.Delete().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction()).WhereDynamic(dywhere); 57 | 58 | internal void EnqueueToDbContext(DbContext.ExecCommandInfoType actionType, EntityState state) { 59 | _ctx.EnqueueAction(actionType, this, typeof(EntityState), state); 60 | } 61 | internal void IncrAffrows(int affrows) { 62 | _ctx._affrows += affrows; 63 | } 64 | 65 | internal void TrackToList(object list) { 66 | if (list == null) return; 67 | var ls = list as IList; 68 | if (ls == null) { 69 | var ie = list as IEnumerable; 70 | if (ie == null) return; 71 | foreach (var item in ie) { 72 | if (item == null) return; 73 | var itemType = item.GetType(); 74 | if (itemType == typeof(object)) return; 75 | if (itemType.FullName.StartsWith("Submission#")) itemType = itemType.BaseType; 76 | var dbset = _ctx.Set(itemType); 77 | dbset?.GetType().GetMethod("TrackToList", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(dbset, new object[] { list }); 78 | return; 79 | } 80 | return; 81 | } 82 | 83 | foreach (var item in ls) { 84 | var key = _fsql.GetEntityKeyString(_entityType, item, false); 85 | if (key == null) continue; 86 | _states.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) => { 87 | _fsql.MapEntityValue(_entityType, item, ov.Value); 88 | ov.Time = DateTime.Now; 89 | return ov; 90 | }); 91 | } 92 | } 93 | 94 | public ISelect Select => this.OrmSelect(null); 95 | public ISelect Where(Expression> exp) => this.OrmSelect(null).Where(exp); 96 | public ISelect WhereIf(bool condition, Expression> exp) => this.OrmSelect(null).WhereIf(condition, exp); 97 | 98 | protected ConcurrentDictionary _states = new ConcurrentDictionary(); 99 | internal ConcurrentDictionary _statesInternal => _states; 100 | TableInfo _tablePriv; 101 | protected TableInfo _table => _tablePriv ?? (_tablePriv = _fsql.CodeFirst.GetTableByEntity(_entityType)); 102 | ColumnInfo[] _tableIdentitysPriv; 103 | protected ColumnInfo[] _tableIdentitys => _tableIdentitysPriv ?? (_tableIdentitysPriv = _table.Primarys.Where(a => a.Attribute.IsIdentity).ToArray()); 104 | protected Type _entityType = typeof(TEntity); 105 | public Type EntityType => _entityType; 106 | 107 | /// 108 | /// 动态Type,在使用 DbSet<object> 后使用本方法,指定实体类型 109 | /// 110 | /// 111 | /// 112 | public void AsType(Type entityType) { 113 | if (entityType == typeof(object)) throw new Exception("ISelect.AsType 参数不支持指定为 object"); 114 | if (entityType == _entityType) return; 115 | var newtb = _fsql.CodeFirst.GetTableByEntity(entityType); 116 | _entityType = entityType; 117 | _tablePriv = newtb ?? throw new Exception("DbSet.AsType 参数错误,请传入正确的实体类型"); 118 | _tableIdentitysPriv = null; 119 | } 120 | 121 | public class EntityState { 122 | public EntityState(TEntity value, string key) { 123 | this.Value = value; 124 | this.Key = key; 125 | this.Time = DateTime.Now; 126 | } 127 | public TEntity OldValue { get; set; } 128 | public TEntity Value { get; set; } 129 | public string Key { get; set; } 130 | public DateTime Time { get; set; } 131 | } 132 | /// 133 | /// 附加实体,可用于不查询就更新或删除 134 | /// 135 | /// 136 | public void Attach(TEntity data) => AttachRange(new[] { data }); 137 | public void AttachRange(IEnumerable data) { 138 | if (data == null || data.Any() == false) return; 139 | if (_table.Primarys.Any() == false) throw new Exception($"不可附加,实体没有主键:{_fsql.GetEntityString(_entityType, data.First())}"); 140 | foreach (var item in data) { 141 | var key = _fsql.GetEntityKeyString(_entityType, item, false); 142 | if (string.IsNullOrEmpty(key)) throw new Exception($"不可附加,未设置主键的值:{_fsql.GetEntityString(_entityType, item)}"); 143 | 144 | _states.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) => { 145 | _fsql.MapEntityValue(_entityType, item, ov.Value); 146 | ov.Time = DateTime.Now; 147 | return ov; 148 | }); 149 | } 150 | } 151 | /// 152 | /// 清空状态数据 153 | /// 154 | public void FlushState() { 155 | _states.Clear(); 156 | } 157 | 158 | #region Utils 159 | EntityState CreateEntityState(TEntity data) { 160 | if (data == null) throw new ArgumentNullException(nameof(data)); 161 | var key = _fsql.GetEntityKeyString(_entityType, data, false); 162 | var state = new EntityState((TEntity)Activator.CreateInstance(_entityType), key); 163 | _fsql.MapEntityValue(_entityType, data, state.Value); 164 | return state; 165 | } 166 | bool? ExistsInStates(TEntity data) { 167 | if (data == null) throw new ArgumentNullException(nameof(data)); 168 | var key = _fsql.GetEntityKeyString(_entityType, data, false); 169 | if (string.IsNullOrEmpty(key)) return null; 170 | return _states.ContainsKey(key); 171 | } 172 | 173 | bool CanAdd(IEnumerable data, bool isThrow) { 174 | if (data == null) { 175 | if (isThrow) throw new ArgumentNullException(nameof(data)); 176 | return false; 177 | } 178 | if (data.Any() == false) return false; 179 | foreach (var s in data) if (CanAdd(s, isThrow) == false) return false; 180 | return true; 181 | } 182 | bool CanAdd(TEntity data, bool isThrow) { 183 | if (data == null) { 184 | if (isThrow) throw new ArgumentNullException(nameof(data)); 185 | return false; 186 | } 187 | if (_table.Primarys.Any() == false) { 188 | if (isThrow) throw new Exception($"不可添加,实体没有主键:{_fsql.GetEntityString(_entityType, data)}"); 189 | return false; 190 | } 191 | var key = _fsql.GetEntityKeyString(_entityType, data, true); 192 | if (string.IsNullOrEmpty(key)) { 193 | switch (_fsql.Ado.DataType) { 194 | case DataType.SqlServer: 195 | case DataType.PostgreSQL: 196 | return true; 197 | case DataType.MySql: 198 | case DataType.Oracle: 199 | case DataType.Sqlite: 200 | if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) { 201 | return true; 202 | } 203 | if (isThrow) throw new Exception($"不可添加,未设置主键的值:{_fsql.GetEntityString(_entityType, data)}"); 204 | return false; 205 | } 206 | } else { 207 | if (_states.ContainsKey(key)) { 208 | if (isThrow) throw new Exception($"不可添加,已存在于状态管理:{_fsql.GetEntityString(_entityType, data)}"); 209 | return false; 210 | } 211 | var idval = _fsql.GetEntityIdentityValueWithPrimary(_entityType, data); 212 | if (idval > 0) { 213 | if (isThrow) throw new Exception($"不可添加,自增属性有值:{_fsql.GetEntityString(_entityType, data)}"); 214 | return false; 215 | } 216 | } 217 | return true; 218 | } 219 | 220 | bool CanUpdate(IEnumerable data, bool isThrow) { 221 | if (data == null) { 222 | if (isThrow) throw new ArgumentNullException(nameof(data)); 223 | return false; 224 | } 225 | if (data.Any() == false) return false; 226 | foreach (var s in data) if (CanUpdate(s, isThrow) == false) return false; 227 | return true; 228 | } 229 | bool CanUpdate(TEntity data, bool isThrow) { 230 | if (data == null) { 231 | if (isThrow) throw new ArgumentNullException(nameof(data)); 232 | return false; 233 | } 234 | if (_table.Primarys.Any() == false) { 235 | if (isThrow) throw new Exception($"不可更新,实体没有主键:{_fsql.GetEntityString(_entityType, data)}"); 236 | return false; 237 | } 238 | var key = _fsql.GetEntityKeyString(_entityType, data, false); 239 | if (string.IsNullOrEmpty(key)) { 240 | if (isThrow) throw new Exception($"不可更新,未设置主键的值:{_fsql.GetEntityString(_entityType, data)}"); 241 | return false; 242 | } 243 | if (_states.TryGetValue(key, out var tryval) == false) { 244 | if (isThrow) throw new Exception($"不可更新,数据未被跟踪,应该先查询 或者 Attach:{_fsql.GetEntityString(_entityType, data)}"); 245 | return false; 246 | } 247 | return true; 248 | } 249 | 250 | bool CanRemove(IEnumerable data, bool isThrow) { 251 | if (data == null) { 252 | if (isThrow) throw new ArgumentNullException(nameof(data)); 253 | return false; 254 | } 255 | if (data.Any() == false) return false; 256 | foreach (var s in data) if (CanRemove(s, isThrow) == false) return false; 257 | return true; 258 | } 259 | bool CanRemove(TEntity data, bool isThrow) { 260 | if (data == null) { 261 | if (isThrow) throw new ArgumentNullException(nameof(data)); 262 | return false; 263 | } 264 | if (_table.Primarys.Any() == false) { 265 | if (isThrow) throw new Exception($"不可删除,实体没有主键:{_fsql.GetEntityString(_entityType, data)}"); 266 | return false; 267 | } 268 | var key = _fsql.GetEntityKeyString(_entityType, data, false); 269 | if (string.IsNullOrEmpty(key)) { 270 | if (isThrow) throw new Exception($"不可删除,未设置主键的值:{_fsql.GetEntityString(_entityType, data)}"); 271 | return false; 272 | } 273 | //if (_states.TryGetValue(key, out var tryval) == false) { 274 | // if (isThrow) throw new Exception($"不可删除,数据未被跟踪,应该先查询:{_fsql.GetEntityString(_entityType, data)}"); 275 | // return false; 276 | //} 277 | return true; 278 | } 279 | #endregion 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /FreeSql.DbContext/DbSet/DbSetAsync.cs: -------------------------------------------------------------------------------- 1 | using FreeSql.Extensions.EntityUtil; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace FreeSql { 10 | partial class DbSet { 11 | 12 | Task DbContextExecCommandAsync() { 13 | _dicUpdateTimes.Clear(); 14 | return _ctx.ExecCommandAsync(); 15 | } 16 | 17 | async Task DbContextBetchAddAsync(EntityState[] adds) { 18 | if (adds.Any() == false) return 0; 19 | var affrows = await this.OrmInsert(adds.Select(a => a.Value)).ExecuteAffrowsAsync(); 20 | return affrows; 21 | } 22 | 23 | #region Add 24 | async Task AddPrivAsync(TEntity data, bool isCheck) { 25 | if (isCheck && CanAdd(data, true) == false) return; 26 | if (_tableIdentitys.Length > 0) { 27 | //有自增,马上执行 28 | switch (_fsql.Ado.DataType) { 29 | case DataType.SqlServer: 30 | case DataType.PostgreSQL: 31 | if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) { 32 | await DbContextExecCommandAsync(); 33 | var idtval = await this.OrmInsert(data).ExecuteIdentityAsync(); 34 | IncrAffrows(1); 35 | _fsql.SetEntityIdentityValueWithPrimary(_entityType, data, idtval); 36 | Attach(data); 37 | if (_ctx.Options.EnableAddOrUpdateNavigateList) 38 | await AddOrUpdateNavigateListAsync(data); 39 | } else { 40 | await DbContextExecCommandAsync(); 41 | var newval = (await this.OrmInsert(data).ExecuteInsertedAsync()).First(); 42 | IncrAffrows(1); 43 | _fsql.MapEntityValue(_entityType, newval, data); 44 | Attach(newval); 45 | if (_ctx.Options.EnableAddOrUpdateNavigateList) 46 | await AddOrUpdateNavigateListAsync(data); 47 | } 48 | return; 49 | case DataType.MySql: 50 | case DataType.Oracle: 51 | case DataType.Sqlite: 52 | if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) { 53 | await DbContextExecCommandAsync(); 54 | var idtval = await this.OrmInsert(data).ExecuteIdentityAsync(); 55 | IncrAffrows(1); 56 | _fsql.SetEntityIdentityValueWithPrimary(_entityType, data, idtval); 57 | Attach(data); 58 | if (_ctx.Options.EnableAddOrUpdateNavigateList) 59 | await AddOrUpdateNavigateListAsync(data); 60 | } 61 | return; 62 | } 63 | } 64 | EnqueueToDbContext(DbContext.ExecCommandInfoType.Insert, CreateEntityState(data)); 65 | Attach(data); 66 | if (_ctx.Options.EnableAddOrUpdateNavigateList) 67 | await AddOrUpdateNavigateListAsync(data); 68 | } 69 | public Task AddAsync(TEntity data) => AddPrivAsync(data, true); 70 | async public Task AddRangeAsync(IEnumerable data) { 71 | if (CanAdd(data, true) == false) return; 72 | if (data.ElementAtOrDefault(1) == default(TEntity)) { 73 | await AddAsync(data.First()); 74 | return; 75 | } 76 | if (_tableIdentitys.Length > 0) { 77 | //有自增,马上执行 78 | switch (_fsql.Ado.DataType) { 79 | case DataType.SqlServer: 80 | case DataType.PostgreSQL: 81 | await DbContextExecCommandAsync(); 82 | var rets = await this.OrmInsert(data).ExecuteInsertedAsync(); 83 | if (rets.Count != data.Count()) throw new Exception($"特别错误:批量添加失败,{_fsql.Ado.DataType} 的返回数据,与添加的数目不匹配"); 84 | var idx = 0; 85 | foreach (var s in data) 86 | _fsql.MapEntityValue(_entityType, rets[idx++], s); 87 | IncrAffrows(rets.Count); 88 | AttachRange(rets); 89 | if (_ctx.Options.EnableAddOrUpdateNavigateList) 90 | foreach (var item in data) 91 | await AddOrUpdateNavigateListAsync(item); 92 | return; 93 | case DataType.MySql: 94 | case DataType.Oracle: 95 | case DataType.Sqlite: 96 | foreach (var s in data) 97 | await AddPrivAsync(s, false); 98 | return; 99 | } 100 | } else { 101 | //进入队列,等待 SaveChanges 时执行 102 | foreach (var item in data) 103 | EnqueueToDbContext(DbContext.ExecCommandInfoType.Insert, CreateEntityState(item)); 104 | AttachRange(data); 105 | if (_ctx.Options.EnableAddOrUpdateNavigateList) 106 | foreach (var item in data) 107 | await AddOrUpdateNavigateListAsync(item); 108 | } 109 | } 110 | async Task AddOrUpdateNavigateListAsync(TEntity item) { 111 | Type itemType = null; 112 | foreach (var prop in _table.Properties) { 113 | if (_table.ColumnsByCsIgnore.ContainsKey(prop.Key)) continue; 114 | if (_table.ColumnsByCs.ContainsKey(prop.Key)) continue; 115 | var tref = _table.GetTableRef(prop.Key, true); 116 | if (tref == null) continue; 117 | 118 | switch (tref.RefType) { 119 | case Internal.Model.TableRefType.OneToOne: 120 | case Internal.Model.TableRefType.ManyToOne: 121 | case Internal.Model.TableRefType.ManyToMany: 122 | continue; 123 | case Internal.Model.TableRefType.OneToMany: 124 | if (itemType == null) itemType = item.GetType(); 125 | if (_table.TypeLazy != null && itemType == _table.TypeLazy) { 126 | var lazyField = _dicLazyIsSetField.GetOrAdd(_table.TypeLazy, tl => new ConcurrentDictionary()).GetOrAdd(prop.Key, propName => 127 | _table.TypeLazy.GetField($"__lazy__{propName}", System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)); 128 | if (lazyField != null) { 129 | var lazyFieldValue = (bool)lazyField.GetValue(item); 130 | if (lazyFieldValue == false) continue; 131 | } 132 | } 133 | var propVal = prop.Value.GetValue(item); 134 | var propValEach = propVal as IEnumerable; 135 | if (propValEach == null) continue; 136 | object dbset = null; 137 | System.Reflection.MethodInfo dbsetAddOrUpdate = null; 138 | foreach (var propValItem in propValEach) { 139 | if (dbset == null) { 140 | dbset = _ctx.Set(tref.RefEntityType); 141 | dbsetAddOrUpdate = dbset.GetType().GetMethod("AddOrUpdateAsync", new Type[] { tref.RefEntityType }); 142 | } 143 | for (var colidx = 0; colidx < tref.Columns.Count; colidx++) { 144 | tref.RefColumns[colidx].Table.Properties[tref.RefColumns[colidx].CsName] 145 | .SetValue(propValItem, tref.Columns[colidx].Table.Properties[tref.Columns[colidx].CsName].GetValue(item)); 146 | } 147 | Task task = dbsetAddOrUpdate.Invoke(dbset, new object[] { propValItem }) as Task; 148 | await task; 149 | } 150 | break; 151 | } 152 | } 153 | } 154 | #endregion 155 | 156 | #region UpdateAsync 157 | Task DbContextBetchUpdateAsync(EntityState[] ups) => DbContextBetchUpdatePrivAsync(ups, false); 158 | Task DbContextBetchUpdateNowAsync(EntityState[] ups) => DbContextBetchUpdatePrivAsync(ups, true); 159 | async Task DbContextBetchUpdatePrivAsync(EntityState[] ups, bool isLiveUpdate) { 160 | if (ups.Any() == false) return 0; 161 | var uplst1 = ups[ups.Length - 1]; 162 | var uplst2 = ups.Length > 1 ? ups[ups.Length - 2] : null; 163 | 164 | if (_states.TryGetValue(uplst1.Key, out var lstval1) == false) return -999; 165 | var lstval2 = default(EntityState); 166 | if (uplst2 != null && _states.TryGetValue(uplst2.Key, out lstval2) == false) throw new Exception($"特别错误:更新失败,数据未被跟踪:{_fsql.GetEntityString(_entityType, uplst2.Value)}"); 167 | 168 | var cuig1 = _fsql.CompareEntityValueReturnColumns(_entityType, uplst1.Value, lstval1.Value, true); 169 | var cuig2 = uplst2 != null ? _fsql.CompareEntityValueReturnColumns(_entityType, uplst2.Value, lstval2.Value, true) : null; 170 | 171 | List data = null; 172 | string[] cuig = null; 173 | if (uplst2 != null && string.Compare(string.Join(",", cuig1), string.Join(",", cuig2)) != 0) { 174 | //最后一个不保存 175 | data = ups.ToList(); 176 | data.RemoveAt(ups.Length - 1); 177 | cuig = cuig2; 178 | } else if (isLiveUpdate) { 179 | //立即保存 180 | data = ups.ToList(); 181 | cuig = cuig1; 182 | } 183 | 184 | if (data?.Count > 0) { 185 | 186 | if (cuig.Length == _table.Columns.Count) 187 | return ups.Length == data.Count ? -998 : -997; 188 | 189 | var updateSource = data.Select(a => a.Value).ToArray(); 190 | var update = this.OrmUpdate(null).SetSource(updateSource).IgnoreColumns(cuig); 191 | 192 | var affrows = await update.ExecuteAffrowsAsync(); 193 | 194 | foreach (var newval in data) { 195 | if (_states.TryGetValue(newval.Key, out var tryold)) 196 | _fsql.MapEntityValue(_entityType, newval.Value, tryold.Value); 197 | if (newval.OldValue != null) 198 | _fsql.MapEntityValue(_entityType, newval.Value, newval.OldValue); 199 | } 200 | return affrows; 201 | } 202 | 203 | //等待下次对比再保存 204 | return 0; 205 | } 206 | async public Task UpdateAsync(TEntity data) { 207 | var exists = ExistsInStates(data); 208 | if (exists == null) throw new Exception($"不可更新,未设置主键的值:{_fsql.GetEntityString(_entityType, data)}"); 209 | if (exists == false) { 210 | var olddata = await OrmSelect(data).FirstAsync(); 211 | if (olddata == null) throw new Exception($"不可更新,数据库不存在该记录:{_fsql.GetEntityString(_entityType, data)}"); 212 | } 213 | 214 | await UpdateRangePrivAsync(new[] { data }, true); 215 | } 216 | public Task UpdateRangeAsync(IEnumerable data) => UpdateRangePrivAsync(data, true); 217 | async Task UpdateRangePrivAsync(IEnumerable data, bool isCheck) { 218 | if (CanUpdate(data, true) == false) return; 219 | foreach (var item in data) { 220 | if (_dicUpdateTimes.ContainsKey(item)) 221 | await DbContextExecCommandAsync(); 222 | _dicUpdateTimes.Add(item, 1); 223 | 224 | var state = CreateEntityState(item); 225 | state.OldValue = item; 226 | EnqueueToDbContext(DbContext.ExecCommandInfoType.Update, state); 227 | } 228 | if (_ctx.Options.EnableAddOrUpdateNavigateList) 229 | foreach (var item in data) 230 | await AddOrUpdateNavigateListAsync(item); 231 | } 232 | #endregion 233 | 234 | #region RemoveAsync 235 | async Task DbContextBetchRemoveAsync(EntityState[] dels) { 236 | if (dels.Any() == false) return 0; 237 | var affrows = await this.OrmDelete(dels.Select(a => a.Value)).ExecuteAffrowsAsync(); 238 | return Math.Max(dels.Length, affrows); 239 | } 240 | #endregion 241 | 242 | #region AddOrUpdateAsync 243 | async public Task AddOrUpdateAsync(TEntity data) { 244 | if (data == null) throw new ArgumentNullException(nameof(data)); 245 | if (_table.Primarys.Any() == false) throw new Exception($"不可添加,实体没有主键:{_fsql.GetEntityString(_entityType, data)}"); 246 | 247 | var flagExists = ExistsInStates(data); 248 | if (flagExists == false) { 249 | var olddata = await OrmSelect(data).FirstAsync(); 250 | if (olddata == null) flagExists = false; 251 | } 252 | 253 | if (flagExists == true && CanUpdate(data, false)) { 254 | await DbContextExecCommandAsync(); 255 | var affrows = _ctx._affrows; 256 | await UpdateRangePrivAsync(new[] { data }, false); 257 | await DbContextExecCommandAsync(); 258 | affrows = _ctx._affrows - affrows; 259 | if (affrows > 0) return; 260 | } 261 | if (CanAdd(data, false)) { 262 | _fsql.ClearEntityPrimaryValueWithIdentity(_entityType, data); 263 | await AddPrivAsync(data, false); 264 | } 265 | } 266 | #endregion 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /FreeSql.DbContext/DbSet/DbSetSync.cs: -------------------------------------------------------------------------------- 1 | using FreeSql.Extensions.EntityUtil; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Collections.Concurrent; 6 | using System.Linq; 7 | using System.Reflection; 8 | 9 | namespace FreeSql { 10 | partial class DbSet { 11 | 12 | void DbContextExecCommand() { 13 | _dicUpdateTimes.Clear(); 14 | _ctx.ExecCommand(); 15 | } 16 | 17 | int DbContextBetchAdd(EntityState[] adds) { 18 | if (adds.Any() == false) return 0; 19 | var affrows = this.OrmInsert(adds.Select(a => a.Value)).ExecuteAffrows(); 20 | return affrows; 21 | } 22 | 23 | #region Add 24 | void AddPriv(TEntity data, bool isCheck) { 25 | if (isCheck && CanAdd(data, true) == false) return; 26 | if (_tableIdentitys.Length > 0) { 27 | //有自增,马上执行 28 | switch (_fsql.Ado.DataType) { 29 | case DataType.SqlServer: 30 | case DataType.PostgreSQL: 31 | if (_tableIdentitys.Length == 1) { 32 | DbContextExecCommand(); 33 | var idtval = this.OrmInsert(data).ExecuteIdentity(); 34 | IncrAffrows(1); 35 | _fsql.SetEntityIdentityValueWithPrimary(_entityType, data, idtval); 36 | Attach(data); 37 | if (_ctx.Options.EnableAddOrUpdateNavigateList) 38 | AddOrUpdateNavigateList(data); 39 | } else { 40 | DbContextExecCommand(); 41 | var newval = this.OrmInsert(data).ExecuteInserted().First(); 42 | IncrAffrows(1); 43 | _fsql.MapEntityValue(_entityType, newval, data); 44 | Attach(newval); 45 | if (_ctx.Options.EnableAddOrUpdateNavigateList) 46 | AddOrUpdateNavigateList(data); 47 | } 48 | return; 49 | case DataType.MySql: 50 | case DataType.Oracle: 51 | case DataType.Sqlite: 52 | if (_tableIdentitys.Length == 1) { 53 | DbContextExecCommand(); 54 | var idtval = this.OrmInsert(data).ExecuteIdentity(); 55 | IncrAffrows(1); 56 | _fsql.SetEntityIdentityValueWithPrimary(_entityType, data, idtval); 57 | Attach(data); 58 | if (_ctx.Options.EnableAddOrUpdateNavigateList) 59 | AddOrUpdateNavigateList(data); 60 | } 61 | return; 62 | } 63 | } 64 | EnqueueToDbContext(DbContext.ExecCommandInfoType.Insert, CreateEntityState(data)); 65 | Attach(data); 66 | if (_ctx.Options.EnableAddOrUpdateNavigateList) 67 | AddOrUpdateNavigateList(data); 68 | } 69 | /// 70 | /// 添加 71 | /// 72 | /// 73 | public void Add(TEntity data) => AddPriv(data, true); 74 | public void AddRange(IEnumerable data) { 75 | if (CanAdd(data, true) == false) return; 76 | if (data.ElementAtOrDefault(1) == default(TEntity)) { 77 | Add(data.First()); 78 | return; 79 | } 80 | if (_tableIdentitys.Length > 0) { 81 | //有自增,马上执行 82 | switch (_fsql.Ado.DataType) { 83 | case DataType.SqlServer: 84 | case DataType.PostgreSQL: 85 | DbContextExecCommand(); 86 | var rets = this.OrmInsert(data).ExecuteInserted(); 87 | if (rets.Count != data.Count()) throw new Exception($"特别错误:批量添加失败,{_fsql.Ado.DataType} 的返回数据,与添加的数目不匹配"); 88 | var idx = 0; 89 | foreach (var s in data) 90 | _fsql.MapEntityValue(_entityType, rets[idx++], s); 91 | IncrAffrows(rets.Count); 92 | AttachRange(rets); 93 | if (_ctx.Options.EnableAddOrUpdateNavigateList) 94 | foreach (var item in data) 95 | AddOrUpdateNavigateList(item); 96 | return; 97 | case DataType.MySql: 98 | case DataType.Oracle: 99 | case DataType.Sqlite: 100 | foreach (var s in data) 101 | AddPriv(s, false); 102 | return; 103 | } 104 | } else { 105 | //进入队列,等待 SaveChanges 时执行 106 | foreach (var item in data) 107 | EnqueueToDbContext(DbContext.ExecCommandInfoType.Insert, CreateEntityState(item)); 108 | AttachRange(data); 109 | if (_ctx.Options.EnableAddOrUpdateNavigateList) 110 | foreach (var item in data) 111 | AddOrUpdateNavigateList(item); 112 | } 113 | } 114 | static ConcurrentDictionary> _dicLazyIsSetField = new ConcurrentDictionary>(); 115 | void AddOrUpdateNavigateList(TEntity item) { 116 | Type itemType = null; 117 | foreach (var prop in _table.Properties) { 118 | if (_table.ColumnsByCsIgnore.ContainsKey(prop.Key)) continue; 119 | if (_table.ColumnsByCs.ContainsKey(prop.Key)) continue; 120 | 121 | object propVal = null; 122 | 123 | if (itemType == null) itemType = item.GetType(); 124 | if (_table.TypeLazy != null && itemType == _table.TypeLazy) { 125 | var lazyField = _dicLazyIsSetField.GetOrAdd(_table.TypeLazy, tl => new ConcurrentDictionary()).GetOrAdd(prop.Key, propName => 126 | _table.TypeLazy.GetField($"__lazy__{propName}", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance)); 127 | if (lazyField != null) { 128 | var lazyFieldValue = (bool)lazyField.GetValue(item); 129 | if (lazyFieldValue == false) continue; 130 | } 131 | propVal = prop.Value.GetValue(item); 132 | } else { 133 | propVal = prop.Value.GetValue(item); 134 | if (propVal == null) continue; 135 | } 136 | 137 | var tref = _table.GetTableRef(prop.Key, true); 138 | if (tref == null) continue; 139 | 140 | switch(tref.RefType) { 141 | case Internal.Model.TableRefType.OneToOne: 142 | case Internal.Model.TableRefType.ManyToOne: 143 | case Internal.Model.TableRefType.ManyToMany: 144 | continue; 145 | case Internal.Model.TableRefType.OneToMany: 146 | var propValEach = propVal as IEnumerable; 147 | if (propValEach == null) continue; 148 | object dbset = null; 149 | MethodInfo dbsetAddOrUpdate = null; 150 | foreach (var propValItem in propValEach) { 151 | if (dbset == null) { 152 | dbset = _ctx.Set(tref.RefEntityType); 153 | dbsetAddOrUpdate = dbset.GetType().GetMethod("AddOrUpdate", new Type[] { tref.RefEntityType }); 154 | } 155 | for (var colidx = 0; colidx < tref.Columns.Count; colidx++) { 156 | tref.RefColumns[colidx].Table.Properties[tref.RefColumns[colidx].CsName] 157 | .SetValue(propValItem, tref.Columns[colidx].Table.Properties[tref.Columns[colidx].CsName].GetValue(item)); 158 | } 159 | dbsetAddOrUpdate.Invoke(dbset, new object[] { propValItem }); 160 | } 161 | break; 162 | } 163 | } 164 | } 165 | #endregion 166 | 167 | #region Update 168 | int DbContextBetchUpdate(EntityState[] ups) => DbContextBetchUpdatePriv(ups, false); 169 | int DbContextBetchUpdateNow(EntityState[] ups) => DbContextBetchUpdatePriv(ups, true); 170 | int DbContextBetchUpdatePriv(EntityState[] ups, bool isLiveUpdate) { 171 | if (ups.Any() == false) return 0; 172 | var uplst1 = ups[ups.Length - 1]; 173 | var uplst2 = ups.Length > 1 ? ups[ups.Length - 2] : null; 174 | 175 | if (_states.TryGetValue(uplst1.Key, out var lstval1) == false) return -999; 176 | var lstval2 = default(EntityState); 177 | if (uplst2 != null && _states.TryGetValue(uplst2.Key, out lstval2) == false) throw new Exception($"特别错误:更新失败,数据未被跟踪:{_fsql.GetEntityString(_entityType, uplst2.Value)}"); 178 | 179 | var cuig1 = _fsql.CompareEntityValueReturnColumns(_entityType, uplst1.Value, lstval1.Value, true); 180 | var cuig2 = uplst2 != null ? _fsql.CompareEntityValueReturnColumns(_entityType, uplst2.Value, lstval2.Value, true) : null; 181 | 182 | List data = null; 183 | string[] cuig = null; 184 | if (uplst2 != null && string.Compare(string.Join(",", cuig1), string.Join(",", cuig2)) != 0) { 185 | //最后一个不保存 186 | data = ups.ToList(); 187 | data.RemoveAt(ups.Length - 1); 188 | cuig = cuig2; 189 | } else if (isLiveUpdate) { 190 | //立即保存 191 | data = ups.ToList(); 192 | cuig = cuig1; 193 | } 194 | 195 | if (data?.Count > 0) { 196 | 197 | if (cuig.Length == _table.Columns.Count) 198 | return ups.Length == data.Count ? -998 : -997; 199 | 200 | var updateSource = data.Select(a => a.Value).ToArray(); 201 | var update = this.OrmUpdate(null).SetSource(updateSource).IgnoreColumns(cuig); 202 | 203 | var affrows = update.ExecuteAffrows(); 204 | 205 | foreach (var newval in data) { 206 | if (_states.TryGetValue(newval.Key, out var tryold)) 207 | _fsql.MapEntityValue(_entityType, newval.Value, tryold.Value); 208 | if (newval.OldValue != null) 209 | _fsql.MapEntityValue(_entityType, newval.Value, newval.OldValue); 210 | } 211 | return affrows; 212 | } 213 | 214 | //等待下次对比再保存 215 | return 0; 216 | } 217 | 218 | Dictionary _dicUpdateTimes = new Dictionary(); 219 | /// 220 | /// 更新 221 | /// 222 | /// 223 | public void Update(TEntity data) { 224 | var exists = ExistsInStates(data); 225 | if (exists == null) throw new Exception($"不可更新,未设置主键的值:{_fsql.GetEntityString(_entityType, data)}"); 226 | if (exists == false) { 227 | var olddata = OrmSelect(data).First(); 228 | if (olddata == null) throw new Exception($"不可更新,数据库不存在该记录:{_fsql.GetEntityString(_entityType, data)}"); 229 | } 230 | 231 | UpdateRangePriv(new[] { data }, true); 232 | } 233 | public void UpdateRange(IEnumerable data) => UpdateRangePriv(data, true); 234 | void UpdateRangePriv(IEnumerable data, bool isCheck) { 235 | if (CanUpdate(data, true) == false) return; 236 | foreach (var item in data) { 237 | if (_dicUpdateTimes.ContainsKey(item)) 238 | DbContextExecCommand(); 239 | _dicUpdateTimes.Add(item, 1); 240 | 241 | var state = CreateEntityState(item); 242 | state.OldValue = item; 243 | EnqueueToDbContext(DbContext.ExecCommandInfoType.Update, state); 244 | } 245 | if (_ctx.Options.EnableAddOrUpdateNavigateList) 246 | foreach (var item in data) 247 | AddOrUpdateNavigateList(item); 248 | } 249 | #endregion 250 | 251 | #region Remove 252 | int DbContextBetchRemove(EntityState[] dels) { 253 | if (dels.Any() == false) return 0; 254 | var affrows = this.OrmDelete(dels.Select(a => a.Value)).ExecuteAffrows(); 255 | return Math.Max(dels.Length, affrows); 256 | } 257 | 258 | /// 259 | /// 删除 260 | /// 261 | /// 262 | public void Remove(TEntity data) => RemoveRange(new[] { data }); 263 | public void RemoveRange(IEnumerable data) { 264 | if (CanRemove(data, true) == false) return; 265 | foreach (var item in data) { 266 | var state = CreateEntityState(item); 267 | _states.TryRemove(state.Key, out var trystate); 268 | _fsql.ClearEntityPrimaryValueWithIdentityAndGuid(_entityType, item); 269 | 270 | EnqueueToDbContext(DbContext.ExecCommandInfoType.Delete, state); 271 | } 272 | } 273 | #endregion 274 | 275 | #region AddOrUpdate 276 | /// 277 | /// 添加或更新 278 | /// 279 | /// 280 | public void AddOrUpdate(TEntity data) { 281 | if (data == null) throw new ArgumentNullException(nameof(data)); 282 | if (_table.Primarys.Any() == false) throw new Exception($"不可添加,实体没有主键:{_fsql.GetEntityString(_entityType, data)}"); 283 | 284 | var flagExists = ExistsInStates(data); 285 | if (flagExists == false) { 286 | var olddata = OrmSelect(data).First(); 287 | if (olddata == null) flagExists = false; 288 | } 289 | 290 | if (flagExists == true && CanUpdate(data, false)) { 291 | DbContextExecCommand(); 292 | var affrows = _ctx._affrows; 293 | UpdateRangePriv(new[] { data }, false); 294 | DbContextExecCommand(); 295 | affrows = _ctx._affrows - affrows; 296 | if (affrows > 0) return; 297 | } 298 | if (CanAdd(data, false)) { 299 | _fsql.ClearEntityPrimaryValueWithIdentity(_entityType, data); 300 | AddPriv(data, false); 301 | } 302 | } 303 | #endregion 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /FreeSql.DbContext/Extenssions/DependencyInjection.cs: -------------------------------------------------------------------------------- 1 | #if ns20 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | 6 | namespace FreeSql { 7 | public static class DbContextDependencyInjection { 8 | 9 | public static IServiceCollection AddFreeDbContext(this IServiceCollection services, Action options) where TDbContext : DbContext { 10 | 11 | services.AddScoped(sp => { 12 | var ctx = Activator.CreateInstance(); 13 | 14 | if (ctx._orm == null) { 15 | var builder = new DbContextOptionsBuilder(); 16 | options(builder); 17 | ctx._orm = builder._fsql; 18 | 19 | if (ctx._orm == null) 20 | throw new Exception("请在 OnConfiguring 或 AddFreeDbContext 中配置 UseFreeSql"); 21 | 22 | ctx.InitPropSets(); 23 | } 24 | 25 | return ctx; 26 | }); 27 | 28 | return services; 29 | } 30 | } 31 | } 32 | #endif -------------------------------------------------------------------------------- /FreeSql.DbContext/Extenssions/FreeSqlDbContextExtenssions.cs: -------------------------------------------------------------------------------- 1 | using FreeSql; 2 | using System; 3 | using System.Collections.Concurrent; 4 | 5 | public static class FreeSqlDbContextExtenssions { 6 | 7 | /// 8 | /// 创建普通数据上下文档对象 9 | /// 10 | /// 11 | /// 12 | public static DbContext CreateDbContext(this IFreeSql that) { 13 | return new FreeContext(that); 14 | } 15 | 16 | /// 17 | /// 不跟踪查询的实体数据(在不需要更新其数据时使用),可提长查询性能 18 | /// 19 | /// 20 | /// 21 | /// 22 | public static ISelect NoTracking(this ISelect select) where T : class { 23 | return select.TrackToList(null); 24 | } 25 | 26 | /// 27 | /// 设置 DbContext 选项设置 28 | /// 29 | /// 30 | /// 31 | public static void SetDbContextOptions(this IFreeSql that, Action options) { 32 | if (options == null) return; 33 | var cfg = _dicSetDbContextOptions.GetOrAdd(that, t => new DbContextOptions()); 34 | options(cfg); 35 | _dicSetDbContextOptions.AddOrUpdate(that, cfg, (t, o) => cfg); 36 | } 37 | internal static ConcurrentDictionary _dicSetDbContextOptions = new ConcurrentDictionary(); 38 | } -------------------------------------------------------------------------------- /FreeSql.DbContext/FreeSql.DbContext.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net45 5 | 0.6.13 6 | true 7 | YeXiangQin 8 | FreeSql is the most convenient ORM in dotnet. It supports Mysql, Postgresql, SqlServer, Oracle and Sqlite. 9 | https://github.com/2881099/FreeSql.DbContext 10 | FreeSql ORM DbContext 11 | git 12 | MIT 13 | $(AssemblyName) 14 | $(AssemblyName) 15 | true 16 | true 17 | 18 | 19 | 20 | FreeSql.DbContext.xml 21 | 3 22 | 23 | 24 | 25 | ns20;netstandard20 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /FreeSql.DbContext/FreeSql.DbContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FreeSql.DbContext 5 | 6 | 7 | 8 | 9 | 添加 10 | 11 | 12 | 13 | 14 | 15 | 16 | 更新 17 | 18 | 19 | 20 | 21 | 22 | 23 | 删除 24 | 25 | 26 | 27 | 28 | 29 | 30 | 添加或更新 31 | 32 | 33 | 34 | 35 | 36 | 37 | 附加实体,可用于不查询就更新或删除 38 | 39 | 40 | 41 | 42 | 43 | 44 | 是否开启一对多,联级保存功能 45 | 46 | 47 | 48 | 49 | 动态Type,在使用 DbSet<object> 后使用本方法,指定实体类型 50 | 51 | 52 | 53 | 54 | 55 | 56 | 附加实体,可用于不查询就更新或删除 57 | 58 | 59 | 60 | 61 | 62 | 清空状态数据 63 | 64 | 65 | 66 | 67 | 添加 68 | 69 | 70 | 71 | 72 | 73 | 更新 74 | 75 | 76 | 77 | 78 | 79 | 删除 80 | 81 | 82 | 83 | 84 | 85 | 添加或更新 86 | 87 | 88 | 89 | 90 | 91 | 在工作单元内创建默认仓库类,工作单元下的仓储操作具有事务特点 92 | 93 | 94 | 95 | 数据过滤 + 验证 96 | 97 | 98 | 99 | 100 | 在工作单元内创建联合主键的仓储类,工作单元下的仓储操作具有事务特点 101 | 102 | 103 | 数据过滤 + 验证 104 | 105 | 106 | 107 | 108 | 在工作单元内创建仓库类,工作单元下的仓储操作具有事务特点 109 | 110 | 111 | 数据过滤 + 验证 112 | 分表规则,参数:旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository 113 | 114 | 115 | 116 | 117 | 开启过滤器,若使用 using 则使用完后,恢复为原有状态 118 | 119 | 过滤器名称 120 | 121 | 122 | 123 | 124 | 开启所有过滤器,若使用 using 则使用完后,恢复为原有状态 125 | 126 | 127 | 128 | 129 | 130 | 禁用过滤器,若使用 using 则使用完后,恢复为原有状态 131 | 132 | 133 | 134 | 135 | 136 | 137 | 禁用所有过滤器,若使用 using 则使用完后,恢复为原有状态 138 | 139 | 140 | 141 | 142 | 143 | 动态Type,在使用 Repository<object> 后使用本方法,指定实体类型 144 | 145 | 146 | 147 | 148 | 149 | 150 | 清空状态数据 151 | 152 | 153 | 154 | 155 | 附加实体,可用于不查询就更新或删除 156 | 157 | 158 | 159 | 160 | 161 | 是否启用工作单元 162 | 163 | 164 | 165 | 166 | 禁用工作单元 167 | 168 | 169 | 若已开启事务(已有Insert/Update/Delete操作),调用此方法将发生异常,建议在执行逻辑前调用 170 | 171 | 172 | 173 | 174 | 开启工作单元 175 | 176 | 177 | 178 | 179 | 是否启用工作单元 180 | 181 | 182 | 183 | 184 | 禁用工作单元 185 | 186 | 187 | 若已开启事务(已有Insert/Update/Delete操作),调用此方法将发生异常,建议在执行逻辑前调用 188 | 189 | 190 | 191 | 192 | 创建普通数据上下文档对象 193 | 194 | 195 | 196 | 197 | 198 | 199 | 不跟踪查询的实体数据(在不需要更新其数据时使用),可提长查询性能 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 设置 DbContext 选项设置 208 | 209 | 210 | 211 | 212 | 213 | 214 | 返回默认仓库类 215 | 216 | 217 | 218 | 219 | 数据过滤 + 验证 220 | 221 | 222 | 223 | 224 | 返回默认仓库类,适用联合主键的仓储类 225 | 226 | 227 | 228 | 数据过滤 + 验证 229 | 230 | 231 | 232 | 233 | 返回仓库类 234 | 235 | 236 | 237 | 数据过滤 + 验证 238 | 分表规则,参数:旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository 239 | 240 | 241 | 242 | 243 | 合并两个仓储的设置(过滤+分表),以便查询 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 创建基于仓储功能的工作单元,务必使用 using 包含使用 254 | 255 | 256 | 257 | 258 | 259 | 260 | -------------------------------------------------------------------------------- /FreeSql.DbContext/Repository/ContextSet/RepositoryDbContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Reflection; 4 | using System.Threading.Tasks; 5 | 6 | namespace FreeSql { 7 | internal class RepositoryDbContext : DbContext { 8 | 9 | protected IBaseRepository _repos; 10 | public RepositoryDbContext(IFreeSql orm, IBaseRepository repos) : base() { 11 | _orm = orm; 12 | _repos = repos; 13 | _isUseUnitOfWork = false; 14 | _uowPriv = _repos.UnitOfWork; 15 | } 16 | 17 | 18 | static ConcurrentDictionary _dicGetRepositoryDbField = new ConcurrentDictionary(); 19 | static FieldInfo GetRepositoryDbField(Type type) => _dicGetRepositoryDbField.GetOrAdd(type, tp => typeof(BaseRepository<,>).MakeGenericType(tp, typeof(int)).GetField("_dbPriv", BindingFlags.Instance | BindingFlags.NonPublic)); 20 | public override IDbSet Set(Type entityType) { 21 | if (_dicSet.ContainsKey(entityType)) return _dicSet[entityType]; 22 | 23 | var tb = _orm.CodeFirst.GetTableByEntity(entityType); 24 | if (tb == null) return null; 25 | 26 | object repos = _repos; 27 | if (entityType != _repos.EntityType) { 28 | repos = Activator.CreateInstance(typeof(DefaultRepository<,>).MakeGenericType(entityType, typeof(int)), _repos.Orm); 29 | (repos as IBaseRepository).UnitOfWork = _repos.UnitOfWork; 30 | GetRepositoryDbField(entityType).SetValue(repos, this); 31 | 32 | typeof(RepositoryDbContext).GetMethod("SetRepositoryDataFilter").MakeGenericMethod(_repos.EntityType) 33 | .Invoke(null, new object[] { repos, _repos }); 34 | } 35 | 36 | var sd = Activator.CreateInstance(typeof(RepositoryDbSet<>).MakeGenericType(entityType), repos) as IDbSet; 37 | if (entityType != typeof(object)) _dicSet.Add(entityType, sd); 38 | return sd; 39 | } 40 | 41 | public static void SetRepositoryDataFilter(object repos, BaseRepository baseRepo) where TEntity : class { 42 | var filter = baseRepo.DataFilter as DataFilter; 43 | DataFilterUtil.SetRepositoryDataFilter(repos, fl => { 44 | foreach (var f in filter._filters) 45 | fl.Apply(f.Key, f.Value.Expression); 46 | }); 47 | } 48 | 49 | public override int SaveChanges() { 50 | ExecCommand(); 51 | var ret = _affrows; 52 | _affrows = 0; 53 | return ret; 54 | } 55 | async public override Task SaveChangesAsync() { 56 | await ExecCommandAsync(); 57 | var ret = _affrows; 58 | _affrows = 0; 59 | return ret; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /FreeSql.DbContext/Repository/ContextSet/RepositoryDbSet.cs: -------------------------------------------------------------------------------- 1 | using FreeSql.Extensions.EntityUtil; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace FreeSql { 7 | internal class RepositoryDbSet : DbSet where TEntity : class { 8 | 9 | protected BaseRepository _repos; 10 | public RepositoryDbSet(BaseRepository repos) { 11 | _ctx = repos._db; 12 | _fsql = repos.Orm; 13 | _uow = repos.UnitOfWork; 14 | _repos = repos; 15 | } 16 | 17 | protected override ISelect OrmSelect(object dywhere) { 18 | var select = base.OrmSelect(dywhere); 19 | 20 | var filters = (_repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); 21 | foreach (var filter in filters) select.Where(filter.Value.Expression); 22 | return select.AsTable(_repos.AsTableSelectInternal); 23 | } 24 | internal ISelect OrmSelectInternal(object dywhere) => OrmSelect(dywhere); 25 | protected override IUpdate OrmUpdate(IEnumerable entitys) { 26 | var update = base.OrmUpdate(entitys); 27 | var filters = (_repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); 28 | foreach (var filter in filters) { 29 | if (entitys != null) 30 | foreach (var entity in entitys) 31 | if (filter.Value.ExpressionDelegate?.Invoke(entity) == false) 32 | throw new Exception($"FreeSql.Repository Update 失败,因为设置了过滤器 {filter.Key}: {filter.Value.Expression},更新的数据不符合 {_fsql.GetEntityString(_entityType, entity)}"); 33 | update.Where(filter.Value.Expression); 34 | } 35 | return update.AsTable(_repos.AsTableInternal); 36 | } 37 | internal IUpdate OrmUpdateInternal(IEnumerable entitys) => OrmUpdate(entitys); 38 | protected override IDelete OrmDelete(object dywhere) { 39 | var delete = base.OrmDelete(dywhere); 40 | var filters = (_repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); 41 | foreach (var filter in filters) delete.Where(filter.Value.Expression); 42 | return delete.AsTable(_repos.AsTableInternal); 43 | } 44 | internal IDelete OrmDeleteInternal(object dywhere) => OrmDelete(dywhere); 45 | protected override IInsert OrmInsert(TEntity entity) => OrmInsert(new[] { entity }); 46 | protected override IInsert OrmInsert(IEnumerable entitys) { 47 | var insert = base.OrmInsert(entitys); 48 | var filters = (_repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); 49 | foreach (var filter in filters) { 50 | if (entitys != null) 51 | foreach (var entity in entitys) 52 | if (filter.Value.ExpressionDelegate?.Invoke(entity) == false) 53 | throw new Exception($"FreeSql.Repository Insert 失败,因为设置了过滤器 {filter.Key}: {filter.Value.Expression},插入的数据不符合 {_fsql.GetEntityString(_entityType, entity)}"); 54 | } 55 | return insert.AsTable(_repos.AsTableInternal); 56 | } 57 | internal IInsert OrmInsertInternal(TEntity entity) => OrmInsert(entity); 58 | internal IInsert OrmInsertInternal(IEnumerable entitys) => OrmInsert(entitys); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /FreeSql.DbContext/Repository/ContextSet/RepositoryUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace FreeSql { 5 | 6 | public interface IRepositoryUnitOfWork : IUnitOfWork { 7 | /// 8 | /// 在工作单元内创建默认仓库类,工作单元下的仓储操作具有事务特点 9 | /// 10 | /// 11 | /// 12 | /// 数据过滤 + 验证 13 | /// 14 | DefaultRepository GetRepository(Expression> filter = null) where TEntity : class; 15 | 16 | /// 17 | /// 在工作单元内创建联合主键的仓储类,工作单元下的仓储操作具有事务特点 18 | /// 19 | /// 20 | /// 数据过滤 + 验证 21 | /// 22 | BaseRepository GetRepository(Expression> filter = null) where TEntity : class; 23 | 24 | /// 25 | /// 在工作单元内创建仓库类,工作单元下的仓储操作具有事务特点 26 | /// 27 | /// 28 | /// 数据过滤 + 验证 29 | /// 分表规则,参数:旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository 30 | /// 31 | GuidRepository GetGuidRepository(Expression> filter = null, Func asTable = null) where TEntity : class; 32 | } 33 | 34 | class RepositoryUnitOfWork : UnitOfWork, IRepositoryUnitOfWork { 35 | 36 | public RepositoryUnitOfWork(IFreeSql fsql) : base(fsql) { 37 | } 38 | 39 | public GuidRepository GetGuidRepository(Expression> filter = null, Func asTable = null) where TEntity : class { 40 | var repos = new GuidRepository(_fsql, filter, asTable); 41 | repos.UnitOfWork = this; 42 | return repos; 43 | } 44 | 45 | public DefaultRepository GetRepository(Expression> filter = null) where TEntity : class { 46 | var repos = new DefaultRepository(_fsql, filter); 47 | repos.UnitOfWork = this; 48 | return repos; 49 | } 50 | 51 | public BaseRepository GetRepository(Expression> filter = null) where TEntity : class { 52 | var repos = new DefaultRepository(_fsql, filter); 53 | repos.UnitOfWork = this; 54 | return repos; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /FreeSql.DbContext/Repository/DataFilter/DataFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Concurrent; 4 | using System.Linq.Expressions; 5 | using System.Linq; 6 | 7 | namespace FreeSql { 8 | public interface IDataFilter : IDisposable where TEntity : class { 9 | 10 | IDataFilter Apply(string filterName, Expression> filterAndValidateExp); 11 | 12 | /// 13 | /// 开启过滤器,若使用 using 则使用完后,恢复为原有状态 14 | /// 15 | /// 过滤器名称 16 | /// 17 | IDisposable Enable(params string[] filterName); 18 | /// 19 | /// 开启所有过滤器,若使用 using 则使用完后,恢复为原有状态 20 | /// 21 | /// 22 | IDisposable EnableAll(); 23 | 24 | /// 25 | /// 禁用过滤器,若使用 using 则使用完后,恢复为原有状态 26 | /// 27 | /// 28 | /// 29 | IDisposable Disable(params string[] filterName); 30 | /// 31 | /// 禁用所有过滤器,若使用 using 则使用完后,恢复为原有状态 32 | /// 33 | /// 34 | IDisposable DisableAll(); 35 | 36 | bool IsEnabled(string filterName); 37 | } 38 | 39 | internal class DataFilter : IDataFilter where TEntity : class { 40 | 41 | internal class FilterItem { 42 | public Expression> Expression { get; set; } 43 | Func _expressionDelegate; 44 | public Func ExpressionDelegate => _expressionDelegate ?? (_expressionDelegate = Expression?.Compile()); 45 | public bool IsEnabled { get; set; } 46 | } 47 | 48 | internal ConcurrentDictionary _filters = new ConcurrentDictionary(StringComparer.CurrentCultureIgnoreCase); 49 | public IDataFilter Apply(string filterName, Expression> filterAndValidateExp) { 50 | 51 | if (filterName == null) 52 | throw new ArgumentNullException(nameof(filterName)); 53 | if (filterAndValidateExp == null) return this; 54 | 55 | var filterItem = new FilterItem { Expression = filterAndValidateExp, IsEnabled = true }; 56 | _filters.AddOrUpdate(filterName, filterItem, (k, v) => filterItem); 57 | return this; 58 | } 59 | 60 | public IDisposable Disable(params string[] filterName) { 61 | if (filterName == null || filterName.Any() == false) return new UsingAny(() => { }); 62 | 63 | List restore = new List(); 64 | foreach (var name in filterName) { 65 | if (_filters.TryGetValue(name, out var tryfi)) { 66 | if (tryfi.IsEnabled) { 67 | restore.Add(name); 68 | tryfi.IsEnabled = false; 69 | } 70 | } 71 | } 72 | return new UsingAny(() => this.Enable(restore.ToArray())); 73 | } 74 | public IDisposable DisableAll() { 75 | List restore = new List(); 76 | foreach (var val in _filters) { 77 | if (val.Value.IsEnabled) { 78 | restore.Add(val.Key); 79 | val.Value.IsEnabled = false; 80 | } 81 | } 82 | return new UsingAny(() => this.Enable(restore.ToArray())); 83 | } 84 | class UsingAny : IDisposable { 85 | Action _ondis; 86 | public UsingAny(Action ondis) { 87 | _ondis = ondis; 88 | } 89 | public void Dispose() { 90 | _ondis?.Invoke(); 91 | } 92 | } 93 | 94 | public IDisposable Enable(params string[] filterName) { 95 | if (filterName == null || filterName.Any() == false) return new UsingAny(() => { }); 96 | 97 | List restore = new List(); 98 | foreach (var name in filterName) { 99 | if (_filters.TryGetValue(name, out var tryfi)) { 100 | if (tryfi.IsEnabled == false) { 101 | restore.Add(name); 102 | tryfi.IsEnabled = true; 103 | } 104 | } 105 | } 106 | return new UsingAny(() => this.Disable(restore.ToArray())); 107 | } 108 | public IDisposable EnableAll() { 109 | List restore = new List(); 110 | foreach (var val in _filters) { 111 | if (val.Value.IsEnabled == false) { 112 | restore.Add(val.Key); 113 | val.Value.IsEnabled = true; 114 | } 115 | } 116 | return new UsingAny(() => this.Disable(restore.ToArray())); 117 | } 118 | 119 | public bool IsEnabled(string filterName) { 120 | if (filterName == null) return false; 121 | return _filters.TryGetValue(filterName, out var tryfi) ? tryfi.IsEnabled : false; 122 | } 123 | 124 | ~DataFilter() { 125 | this.Dispose(); 126 | } 127 | public void Dispose() { 128 | _filters.Clear(); 129 | } 130 | } 131 | 132 | public class FluentDataFilter : IDisposable { 133 | 134 | internal List<(Type type, string name, LambdaExpression exp)> _filters = new List<(Type type, string name, LambdaExpression exp)>(); 135 | 136 | public FluentDataFilter Apply(string filterName, Expression> filterAndValidateExp) where TEntity : class { 137 | if (filterName == null) 138 | throw new ArgumentNullException(nameof(filterName)); 139 | if (filterAndValidateExp == null) return this; 140 | 141 | _filters.Add((typeof(TEntity), filterName, filterAndValidateExp)); 142 | return this; 143 | } 144 | 145 | ~FluentDataFilter() { 146 | this.Dispose(); 147 | } 148 | public void Dispose() { 149 | _filters.Clear(); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /FreeSql.DbContext/Repository/DataFilter/DataFilterUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | 8 | namespace FreeSql { 9 | 10 | internal class DataFilterUtil { 11 | 12 | internal static Action _globalDataFilter; 13 | 14 | static ConcurrentDictionary _dicSetRepositoryDataFilterApplyDataFilterFunc = new ConcurrentDictionary(); 15 | static ConcurrentDictionary> _dicSetRepositoryDataFilterConvertFilterNotExists = new ConcurrentDictionary>(); 16 | internal static void SetRepositoryDataFilter(object repos, Action scopedDataFilter) { 17 | if (scopedDataFilter != null) { 18 | SetRepositoryDataFilter(repos, null); 19 | } 20 | if (scopedDataFilter == null) { 21 | scopedDataFilter = _globalDataFilter; 22 | } 23 | if (scopedDataFilter == null) return; 24 | using (var globalFilter = new FluentDataFilter()) { 25 | scopedDataFilter(globalFilter); 26 | 27 | var type = repos.GetType(); 28 | Type entityType = (repos as IBaseRepository).EntityType; 29 | if (entityType == null) throw new Exception("FreeSql.Repository 设置过滤器失败,原因是对象不属于 IRepository"); 30 | 31 | var notExists = _dicSetRepositoryDataFilterConvertFilterNotExists.GetOrAdd(type, t => new ConcurrentDictionary()); 32 | var newFilter = new Dictionary(); 33 | foreach (var gf in globalFilter._filters) { 34 | if (notExists.ContainsKey(gf.name)) continue; 35 | 36 | LambdaExpression newExp = null; 37 | var filterParameter1 = Expression.Parameter(entityType, gf.exp.Parameters[0].Name); 38 | try { 39 | newExp = Expression.Lambda( 40 | typeof(Func<,>).MakeGenericType(entityType, typeof(bool)), 41 | new ReplaceVisitor().Modify(gf.exp.Body, filterParameter1), 42 | filterParameter1 43 | ); 44 | } catch { 45 | notExists.TryAdd(gf.name, true); //防止第二次错误 46 | continue; 47 | } 48 | newFilter.Add(gf.name, newExp); 49 | } 50 | if (newFilter.Any() == false) return; 51 | 52 | var del = _dicSetRepositoryDataFilterApplyDataFilterFunc.GetOrAdd(type, t => { 53 | var reposParameter = Expression.Parameter(type); 54 | var nameParameter = Expression.Parameter(typeof(string)); 55 | var expressionParameter = Expression.Parameter( 56 | typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(entityType, typeof(bool))) 57 | ); 58 | return Expression.Lambda( 59 | Expression.Block( 60 | Expression.Call(reposParameter, type.GetMethod("ApplyDataFilter", BindingFlags.Instance | BindingFlags.NonPublic), nameParameter, expressionParameter) 61 | ), 62 | new[] { 63 | reposParameter, nameParameter, expressionParameter 64 | } 65 | ).Compile(); 66 | }); 67 | foreach (var nf in newFilter) { 68 | del.DynamicInvoke(repos, nf.Key, nf.Value); 69 | } 70 | newFilter.Clear(); 71 | } 72 | } 73 | } 74 | 75 | class ReplaceVisitor : ExpressionVisitor { 76 | private ParameterExpression parameter; 77 | 78 | public Expression Modify(Expression expression, ParameterExpression parameter) { 79 | this.parameter = parameter; 80 | return Visit(expression); 81 | } 82 | 83 | protected override Expression VisitMember(MemberExpression node) { 84 | if (node.Expression?.NodeType == ExpressionType.Parameter) 85 | return Expression.Property(parameter, node.Member.Name); 86 | return base.VisitMember(node); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /FreeSql.DbContext/Repository/Extenssions/DependencyInjection.cs: -------------------------------------------------------------------------------- 1 | #if ns20 2 | 3 | using System; 4 | using System.Reflection; 5 | using System.Linq; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | namespace FreeSql { 9 | public static class FreeSqlRepositoryDependencyInjection { 10 | 11 | public static IServiceCollection AddFreeRepository(this IServiceCollection services, Action globalDataFilter = null, params Assembly[] assemblies) { 12 | 13 | DataFilterUtil._globalDataFilter = globalDataFilter; 14 | 15 | services.AddScoped(typeof(IReadOnlyRepository<>), typeof(GuidRepository<>)); 16 | services.AddScoped(typeof(IBasicRepository<>), typeof(GuidRepository<>)); 17 | services.AddScoped(typeof(BaseRepository<>), typeof(GuidRepository<>)); 18 | services.AddScoped(typeof(GuidRepository<>)); 19 | 20 | services.AddScoped(typeof(IReadOnlyRepository<,>), typeof(DefaultRepository<,>)); 21 | services.AddScoped(typeof(IBasicRepository<,>), typeof(DefaultRepository<,>)); 22 | services.AddScoped(typeof(BaseRepository<,>), typeof(DefaultRepository<,>)); 23 | services.AddScoped(typeof(DefaultRepository<,>)); 24 | 25 | if (assemblies?.Any() == true) { 26 | foreach(var asse in assemblies) { 27 | foreach (var repos in asse.GetTypes().Where(a => a.IsAbstract == false && typeof(IBaseRepository).IsAssignableFrom(a))) { 28 | 29 | services.AddScoped(repos); 30 | } 31 | } 32 | } 33 | 34 | return services; 35 | } 36 | } 37 | } 38 | 39 | #endif -------------------------------------------------------------------------------- /FreeSql.DbContext/Repository/Extenssions/FreeSqlRepositoryExtenssions.cs: -------------------------------------------------------------------------------- 1 | using FreeSql; 2 | using System; 3 | using System.Linq.Expressions; 4 | using System.Linq; 5 | 6 | public static class FreeSqlRepositoryExtenssions { 7 | 8 | /// 9 | /// 返回默认仓库类 10 | /// 11 | /// 12 | /// 13 | /// 14 | /// 数据过滤 + 验证 15 | /// 16 | public static DefaultRepository GetRepository(this IFreeSql that, Expression> filter = null) where TEntity : class { 17 | return new DefaultRepository(that, filter); 18 | } 19 | 20 | /// 21 | /// 返回默认仓库类,适用联合主键的仓储类 22 | /// 23 | /// 24 | /// 25 | /// 数据过滤 + 验证 26 | /// 27 | public static BaseRepository GetRepository(this IFreeSql that, Expression> filter = null) where TEntity : class { 28 | return new DefaultRepository(that, filter); 29 | } 30 | 31 | /// 32 | /// 返回仓库类 33 | /// 34 | /// 35 | /// 36 | /// 数据过滤 + 验证 37 | /// 分表规则,参数:旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository 38 | /// 39 | public static GuidRepository GetGuidRepository(this IFreeSql that, Expression> filter = null, Func asTable = null) where TEntity : class { 40 | return new GuidRepository(that, filter, asTable); 41 | } 42 | 43 | /// 44 | /// 合并两个仓储的设置(过滤+分表),以便查询 45 | /// 46 | /// 47 | /// 48 | /// 49 | /// 50 | /// 51 | public static ISelect FromRepository(this ISelect that, BaseRepository repos) where TEntity : class where T2 : class { 52 | var filters = (repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); 53 | foreach (var filter in filters) that.Where(filter.Value.Expression); 54 | return that.AsTable(repos.AsTableSelectInternal); 55 | } 56 | 57 | /// 58 | /// 创建基于仓储功能的工作单元,务必使用 using 包含使用 59 | /// 60 | /// 61 | /// 62 | public static IRepositoryUnitOfWork CreateUnitOfWork(this IFreeSql that) { 63 | return new RepositoryUnitOfWork(that); 64 | } 65 | } -------------------------------------------------------------------------------- /FreeSql.DbContext/Repository/Repository/BaseRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Threading.Tasks; 6 | 7 | namespace FreeSql { 8 | public abstract class BaseRepository : IBaseRepository 9 | where TEntity : class { 10 | 11 | internal RepositoryDbContext _dbPriv; 12 | internal RepositoryDbContext _db => _dbPriv ?? (_dbPriv = new RepositoryDbContext(Orm, this)); 13 | internal RepositoryDbSet _dbsetPriv; 14 | internal RepositoryDbSet _dbset => _dbsetPriv ?? (_dbsetPriv = _db.Set() as RepositoryDbSet); 15 | public IDataFilter DataFilter { get; } = new DataFilter(); 16 | Func _asTableVal; 17 | protected Func AsTable { 18 | get => _asTableVal; 19 | set { 20 | _asTableVal = value; 21 | AsTableSelect = value == null ? null : new Func((a, b) => a == EntityType ? value(b) : null); 22 | } 23 | } 24 | internal Func AsTableInternal => AsTable; 25 | protected Func AsTableSelect { get; private set; } 26 | internal Func AsTableSelectInternal => AsTableSelect; 27 | 28 | protected void ApplyDataFilter(string name, Expression> exp) => DataFilter.Apply(name, exp); 29 | 30 | protected BaseRepository(IFreeSql fsql, Expression> filter, Func asTable = null) { 31 | Orm = fsql; 32 | DataFilterUtil.SetRepositoryDataFilter(this, null); 33 | DataFilter.Apply("", filter); 34 | AsTable = asTable; 35 | } 36 | 37 | ~BaseRepository() { 38 | this.Dispose(); 39 | } 40 | bool _isdisposed = false; 41 | public void Dispose() { 42 | if (_isdisposed) return; 43 | try { 44 | _dbsetPriv?.Dispose(); 45 | _dbPriv?.Dispose(); 46 | this.DataFilter.Dispose(); 47 | } finally { 48 | _isdisposed = true; 49 | GC.SuppressFinalize(this); 50 | } 51 | } 52 | public Type EntityType => _dbsetPriv?.EntityType ?? typeof(TEntity); 53 | public void AsType(Type entityType) => _dbset.AsType(entityType); 54 | 55 | public IFreeSql Orm { get; private set; } 56 | public IUnitOfWork UnitOfWork { get; set; } 57 | public IUpdate UpdateDiy => _dbset.OrmUpdateInternal(null); 58 | 59 | public ISelect Select => _dbset.OrmSelectInternal(null); 60 | public ISelect Where(Expression> exp) => _dbset.OrmSelectInternal(null).Where(exp); 61 | public ISelect WhereIf(bool condition, Expression> exp) => _dbset.OrmSelectInternal(null).WhereIf(condition, exp); 62 | 63 | public int Delete(Expression> predicate) => _dbset.OrmDeleteInternal(null).Where(predicate).ExecuteAffrows(); 64 | public Task DeleteAsync(Expression> predicate) => _dbset.OrmDeleteInternal(null).Where(predicate).ExecuteAffrowsAsync(); 65 | 66 | public int Delete(TEntity entity) { 67 | _dbset.Remove(entity); 68 | return _db.SaveChanges(); 69 | } 70 | public Task DeleteAsync(TEntity entity) { 71 | _dbset.Remove(entity); 72 | return _db.SaveChangesAsync(); 73 | } 74 | public int Delete(IEnumerable entitys) { 75 | _dbset.RemoveRange(entitys); 76 | return _db.SaveChanges(); 77 | } 78 | public Task DeleteAsync(IEnumerable entitys) { 79 | _dbset.RemoveRange(entitys); 80 | return _db.SaveChangesAsync(); 81 | } 82 | 83 | public virtual TEntity Insert(TEntity entity) { 84 | _dbset.Add(entity); 85 | _db.SaveChanges(); 86 | return entity; 87 | } 88 | async public virtual Task InsertAsync(TEntity entity) { 89 | await _dbset.AddAsync(entity); 90 | _db.SaveChanges(); 91 | return entity; 92 | } 93 | public virtual List Insert(IEnumerable entitys) { 94 | _dbset.AddRange(entitys); 95 | _db.SaveChanges(); 96 | return entitys.ToList(); 97 | } 98 | async public virtual Task> InsertAsync(IEnumerable entitys) { 99 | await _dbset.AddRangeAsync(entitys); 100 | await _db.SaveChangesAsync(); 101 | return entitys.ToList(); 102 | } 103 | 104 | public int Update(TEntity entity) { 105 | _dbset.Update(entity); 106 | return _db.SaveChanges(); 107 | } 108 | public Task UpdateAsync(TEntity entity) { 109 | _dbset.Update(entity); 110 | return _db.SaveChangesAsync(); 111 | } 112 | public int Update(IEnumerable entitys) { 113 | _dbset.UpdateRange(entitys); 114 | return _db.SaveChanges(); 115 | } 116 | public Task UpdateAsync(IEnumerable entitys) { 117 | _dbset.UpdateRange(entitys); 118 | return _db.SaveChangesAsync(); 119 | } 120 | 121 | public void Attach(TEntity data) => _db.Attach(data); 122 | public void Attach(IEnumerable data) => _db.AttachRange(data); 123 | public void FlushState() => _dbset.FlushState(); 124 | 125 | public TEntity InsertOrUpdate(TEntity entity) { 126 | _dbset.AddOrUpdate(entity); 127 | _db.SaveChanges(); 128 | return entity; 129 | } 130 | async public Task InsertOrUpdateAsync(TEntity entity) { 131 | await _dbset.AddOrUpdateAsync(entity); 132 | _db.SaveChanges(); 133 | return entity; 134 | } 135 | } 136 | 137 | public abstract class BaseRepository : BaseRepository, IBaseRepository 138 | where TEntity : class { 139 | 140 | public BaseRepository(IFreeSql fsql, Expression> filter, Func asTable = null) : base(fsql, filter, asTable) { 141 | } 142 | 143 | public int Delete(TKey id) { 144 | var stateKey = string.Concat(id); 145 | _dbset._statesInternal.TryRemove(stateKey, out var trystate); 146 | return _dbset.OrmDeleteInternal(id).ExecuteAffrows(); 147 | } 148 | public Task DeleteAsync(TKey id) { 149 | var stateKey = string.Concat(id); 150 | _dbset._statesInternal.TryRemove(stateKey, out var trystate); 151 | return _dbset.OrmDeleteInternal(id).ExecuteAffrowsAsync(); 152 | } 153 | 154 | public TEntity Find(TKey id) => _dbset.OrmSelectInternal(id).ToOne(); 155 | public Task FindAsync(TKey id) => _dbset.OrmSelectInternal(id).ToOneAsync(); 156 | 157 | public TEntity Get(TKey id) => Find(id); 158 | public Task GetAsync(TKey id) => FindAsync(id); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /FreeSql.DbContext/Repository/Repository/DefaultRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace FreeSql { 5 | public class DefaultRepository : 6 | BaseRepository 7 | where TEntity : class { 8 | 9 | public DefaultRepository(IFreeSql fsql) : base(fsql, null, null) { 10 | 11 | } 12 | 13 | public DefaultRepository(IFreeSql fsql, Expression> filter) : base(fsql, filter, null) { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /FreeSql.DbContext/Repository/Repository/GuidRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace FreeSql { 5 | public class GuidRepository : 6 | BaseRepository 7 | where TEntity : class { 8 | 9 | public GuidRepository(IFreeSql fsql) : this(fsql, null, null) { 10 | 11 | } 12 | public GuidRepository(IFreeSql fsql, Expression> filter, Func asTable) : base(fsql, filter, asTable) { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /FreeSql.DbContext/Repository/Repository/IBaseRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Threading.Tasks; 4 | 5 | namespace FreeSql { 6 | 7 | public interface IBaseRepository : IDisposable { 8 | Type EntityType { get; } 9 | IUnitOfWork UnitOfWork { get; set; } 10 | IFreeSql Orm { get; } 11 | 12 | /// 13 | /// 动态Type,在使用 Repository<object> 后使用本方法,指定实体类型 14 | /// 15 | /// 16 | /// 17 | void AsType(Type entityType); 18 | } 19 | 20 | public interface IBaseRepository : IReadOnlyRepository, IBasicRepository 21 | where TEntity : class { 22 | int Delete(Expression> predicate); 23 | 24 | Task DeleteAsync(Expression> predicate); 25 | } 26 | 27 | public interface IBaseRepository : IBaseRepository, IReadOnlyRepository, IBasicRepository 28 | where TEntity : class { 29 | } 30 | } -------------------------------------------------------------------------------- /FreeSql.DbContext/Repository/Repository/IBasicRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace FreeSql { 5 | public interface IBasicRepository : IReadOnlyRepository 6 | where TEntity : class { 7 | TEntity Insert(TEntity entity); 8 | List Insert(IEnumerable entitys); 9 | Task InsertAsync(TEntity entity); 10 | Task> InsertAsync(IEnumerable entitys); 11 | 12 | /// 13 | /// 清空状态数据 14 | /// 15 | void FlushState(); 16 | /// 17 | /// 附加实体,可用于不查询就更新或删除 18 | /// 19 | /// 20 | void Attach(TEntity entity); 21 | void Attach(IEnumerable entity); 22 | int Update(TEntity entity); 23 | int Update(IEnumerable entitys); 24 | Task UpdateAsync(TEntity entity); 25 | Task UpdateAsync(IEnumerable entitys); 26 | 27 | TEntity InsertOrUpdate(TEntity entity); 28 | Task InsertOrUpdateAsync(TEntity entity); 29 | 30 | IUpdate UpdateDiy { get; } 31 | 32 | int Delete(TEntity entity); 33 | int Delete(IEnumerable entitys); 34 | Task DeleteAsync(TEntity entity); 35 | Task DeleteAsync(IEnumerable entitys); 36 | } 37 | 38 | public interface IBasicRepository : IBasicRepository, IReadOnlyRepository 39 | where TEntity : class { 40 | int Delete(TKey id); 41 | 42 | Task DeleteAsync(TKey id); 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /FreeSql.DbContext/Repository/Repository/IReadOnlyRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Threading.Tasks; 4 | 5 | namespace FreeSql { 6 | public interface IReadOnlyRepository : IBaseRepository 7 | where TEntity : class { 8 | 9 | IDataFilter DataFilter { get; } 10 | 11 | ISelect Select { get; } 12 | 13 | ISelect Where(Expression> exp); 14 | ISelect WhereIf(bool condition, Expression> exp); 15 | } 16 | 17 | public interface IReadOnlyRepository : IReadOnlyRepository 18 | where TEntity : class { 19 | TEntity Get(TKey id); 20 | 21 | Task GetAsync(TKey id); 22 | 23 | TEntity Find(TKey id); 24 | 25 | Task FindAsync(TKey id); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FreeSql.DbContext/TempExtensions.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace FreeSql.Extensions.EntityUtil { 3 | public static class TempExtensions { 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /FreeSql.DbContext/UnitOfWork/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | 5 | namespace FreeSql { 6 | public interface IUnitOfWork : IDisposable { 7 | 8 | DbTransaction GetOrBeginTransaction(bool isCreate = true); 9 | 10 | IsolationLevel? IsolationLevel { get; set; } 11 | 12 | /// 13 | /// 是否启用工作单元 14 | /// 15 | bool Enable { get; } 16 | 17 | void Commit(); 18 | 19 | void Rollback(); 20 | 21 | /// 22 | /// 禁用工作单元 23 | /// 24 | /// 25 | /// 若已开启事务(已有Insert/Update/Delete操作),调用此方法将发生异常,建议在执行逻辑前调用 26 | /// 27 | void Close(); 28 | 29 | /// 30 | /// 开启工作单元 31 | /// 32 | void Open(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /FreeSql.DbContext/UnitOfWork/UnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using SafeObjectPool; 2 | using System; 3 | using System.Data; 4 | using System.Data.Common; 5 | 6 | namespace FreeSql { 7 | class UnitOfWork : IUnitOfWork { 8 | 9 | protected IFreeSql _fsql; 10 | protected Object _conn; 11 | protected DbTransaction _tran; 12 | 13 | public UnitOfWork(IFreeSql fsql) { 14 | _fsql = fsql; 15 | } 16 | 17 | void ReturnObject() { 18 | _fsql.Ado.MasterPool.Return(_conn); 19 | _tran = null; 20 | _conn = null; 21 | } 22 | 23 | 24 | /// 25 | /// 是否启用工作单元 26 | /// 27 | public bool Enable { get; private set; } = true; 28 | 29 | /// 30 | /// 禁用工作单元 31 | /// 32 | /// 33 | /// 若已开启事务(已有Insert/Update/Delete操作),调用此方法将发生异常,建议在执行逻辑前调用 34 | /// 35 | public void Close() 36 | { 37 | if (_tran != null) 38 | { 39 | throw new Exception("已开启事务,不能禁用工作单元"); 40 | } 41 | 42 | Enable = false; 43 | } 44 | 45 | public void Open() 46 | { 47 | Enable = true; 48 | } 49 | 50 | public IsolationLevel? IsolationLevel { get; set; } 51 | 52 | public DbTransaction GetOrBeginTransaction(bool isCreate = true) { 53 | 54 | if (_tran != null) return _tran; 55 | if (isCreate == false) return null; 56 | if (!Enable) return null; 57 | if (_conn != null) _fsql.Ado.MasterPool.Return(_conn); 58 | 59 | _conn = _fsql.Ado.MasterPool.Get(); 60 | try { 61 | _tran = IsolationLevel == null ? 62 | _conn.Value.BeginTransaction() : 63 | _conn.Value.BeginTransaction(IsolationLevel.Value); 64 | } catch { 65 | ReturnObject(); 66 | throw; 67 | } 68 | return _tran; 69 | } 70 | 71 | public void Commit() { 72 | if (_tran != null) { 73 | try { 74 | _tran.Commit(); 75 | } finally { 76 | ReturnObject(); 77 | } 78 | } 79 | } 80 | public void Rollback() { 81 | if (_tran != null) { 82 | try { 83 | _tran.Rollback(); 84 | } finally { 85 | ReturnObject(); 86 | } 87 | } 88 | } 89 | ~UnitOfWork() { 90 | this.Dispose(); 91 | } 92 | bool _isdisposed = false; 93 | public void Dispose() { 94 | if (_isdisposed) return; 95 | try { 96 | this.Rollback(); 97 | } finally { 98 | _isdisposed = true; 99 | GC.SuppressFinalize(this); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /FreeSql.Repository/FreeSql.Repository.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net45 5 | 0.6.13 6 | YeXiangQin 7 | FreeSql Implementation of General Repository, Support MySql/SqlServer/PostgreSQL/Oracle/Sqlite, and read/write separation、and split table. 8 | https://github.com/2881099/FreeSql/wiki/Repository 9 | FreeSql ORM Repository 10 | true 11 | git 12 | MIT 13 | $(AssemblyName) 14 | $(AssemblyName) 15 | true 16 | true 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /FreeSql.Repository/readme.md: -------------------------------------------------------------------------------- 1 | 这是 [FreeSql](https://github.com/2881099/FreeSql) 衍生出来的扩展包,包含 Repository & UnitOfWork 实现面向对象的特性(QQ群:4336577)。 2 | 3 | > dotnet add package FreeSql.Repository 4 | 5 | ## Repository & UnitOfWork 6 | 7 | 仓储与工作单元一起使用,工作单元具有事务特点。 8 | 9 | ```csharp 10 | using (var unitOfWork = fsql.CreateUnitOfWork()) { 11 | var songRepository = unitOfWork.GetRepository(); 12 | var tagRepository = unitOfWork.GetRepository(); 13 | 14 | var song = new Song { BigNumber = "1000000000000000000" }; 15 | songRepository.Insert(song); 16 | 17 | songRepository.Update(song); 18 | 19 | song.BigNumber = (BigInteger.Parse(song.BigNumber) + 1).ToString(); 20 | songRepository.Update(song); 21 | 22 | var tag = new Tag { 23 | Name = "testaddsublist", 24 | Tags = new[] { 25 | new Tag { Name = "sub1" }, 26 | new Tag { Name = "sub2" }, 27 | new Tag { 28 | Name = "sub3", 29 | Tags = new[] { 30 | new Tag { Name = "sub3_01" } 31 | } 32 | } 33 | } 34 | }; 35 | tagRepository.Insert(tag); 36 | 37 | ctx.Commit(); 38 | } 39 | ``` 40 | 41 | ## Repository 42 | 43 | 简单使用仓储,有状态跟踪,它不包含事务的特点。 44 | 45 | ```csharp 46 | var songRepository = fsql.GetRepository(); 47 | var song = new Song { BigNumber = "1000000000000000000" }; 48 | songRepository.Insert(song); 49 | ``` 50 | 51 | ## IFreeSql 核心定义 52 | 53 | ```csharp 54 | var fsql = new FreeSql.FreeSqlBuilder() 55 | .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\dd2.db;Pooling=true;Max Pool Size=10") 56 | .UseAutoSyncStructure(true) 57 | .UseNoneCommandParameter(true) 58 | 59 | .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) 60 | .Build(); 61 | 62 | public class Song { 63 | [Column(IsIdentity = true)] 64 | public int Id { get; set; } 65 | public string BigNumber { get; set; } 66 | 67 | [Column(IsVersion = true)] //乐观锁 68 | public long versionRow { get; set; } 69 | } 70 | public class Tag { 71 | [Column(IsIdentity = true)] 72 | public int Id { get; set; } 73 | 74 | public int? Parent_id { get; set; } 75 | public virtual Tag Parent { get; set; } 76 | 77 | public string Name { get; set; } 78 | 79 | public virtual ICollection Tags { get; set; } 80 | } 81 | 82 | public class SongContext : DbContext { 83 | public DbSet Songs { get; set; } 84 | public DbSet Tags { get; set; } 85 | 86 | protected override void OnConfiguring(DbContextOptionsBuilder builder) { 87 | builder.UseFreeSql(fsql); 88 | } 89 | } 90 | ``` 91 | 92 | # 过滤器与验证 93 | 94 | 假设我们有User(用户)、Topic(主题)两个实体,在领域类中定义了两个仓储: 95 | 96 | ```csharp 97 | var userRepository = fsql.GetGuidRepository(); 98 | var topicRepository = fsql.GetGuidRepository(); 99 | ``` 100 | 101 | 在开发过程中,总是担心 topicRepository 的数据安全问题,即有可能查询或操作到其他用户的主题。因此我们在v0.0.7版本进行了改进,增加了 filter lambad 表达式参数。 102 | 103 | ```csharp 104 | var userRepository = fsql.GetGuidRepository(a => a.Id == 1); 105 | var topicRepository = fsql.GetGuidRepository(a => a.UserId == 1); 106 | ``` 107 | 108 | * 在查询/修改/删除时附加此条件,从而达到不会修改其他用户的数据; 109 | * 在添加时,使用表达式验证数据的合法性,若不合法则抛出异常; 110 | 111 | # 分表与分库 112 | 113 | FreeSql 提供 AsTable 分表的基础方法,GuidRepository 作为分存式仓储将实现了分表与分库(不支持跨服务器分库)的封装。 114 | 115 | ```csharp 116 | var logRepository = fsql.GetGuidRepository(null, oldname => $"{oldname}_{DateTime.Now.ToString("YYYYMM")}"); 117 | ``` 118 | 119 | 上面我们得到一个日志仓储按年月分表,使用它 CURD 最终会操作 Log_201903 表。 120 | 121 | 合并两个仓储,实现分表下的联表查询: 122 | 123 | ```csharp 124 | fsql.GetGuidRepository().Select.FromRepository(logRepository) 125 | .LeftJoin(b => b.UserId == a.Id) 126 | .ToList(); 127 | ``` 128 | 129 | 注意事项: 130 | 131 | * 不能使用 CodeFirst 迁移分表,开发环境时仍然可以迁移 Log 表; 132 | * 不可在分表分库的实体类型中使用《延时加载》; 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 YeXiangQin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # 源码于【2019年06月26日】移至 FreeSql 仓储维护。。。 3 | 4 | 这是 [FreeSql](https://github.com/2881099/FreeSql) 衍生出来的扩展包,包含 DbContext & DbSet、Repository & UnitOfWork 实现面向对象的特性(QQ群:4336577)。 5 | 6 | > dotnet add package FreeSql.DbContext 7 | 8 | ## 更新日志 9 | 10 | ### v0.6.5 11 | 12 | - 修复 Repository 联级保存的 bug; 13 | - 添加工作单元开启方法; 14 | - 适配 .net framework 4.5、netstandard 2.0; 15 | 16 | ### v0.6.1 17 | 18 | - 拆分 FreeSql 小包引用,各数据库单独包、延时加载包; 19 | - FreeSql.Extensions.LazyLoading 20 | - FreeSql.Provider.MySql 21 | - FreeSql.Provider.PostgreSQL 22 | - FreeSql.Provider.SqlServer 23 | - FreeSql.Provider.Sqlite 24 | - FreeSql.Provider.Oracle 25 | - 移除 IFreeSql.Cache,以及 ISelect.Caching 方法; 26 | - 移除 IFreeSql.Log,包括内部原有的日志输出,改为 Trace.WriteLine; 27 | - IAdo.Query\ 读取返回变为 List\\>; 28 | - 定义 IFreeSql 和以前一样,移除了 UseCache、UseLogger 方法; 29 | 30 | ## DbContext & DbSet 31 | 32 | ```csharp 33 | using (var ctx = new SongContext()) { 34 | var song = new Song { BigNumber = "1000000000000000000" }; 35 | ctx.Songs.Add(song); 36 | 37 | song.BigNumber = (BigInteger.Parse(song.BigNumber) + 1).ToString(); 38 | ctx.Songs.Update(song); 39 | 40 | var tag = new Tag { 41 | Name = "testaddsublist", 42 | Tags = new[] { 43 | new Tag { Name = "sub1" }, 44 | new Tag { Name = "sub2" }, 45 | new Tag { 46 | Name = "sub3", 47 | Tags = new[] { 48 | new Tag { Name = "sub3_01" } 49 | } 50 | } 51 | } 52 | }; 53 | ctx.Tags.Add(tag); 54 | 55 | ctx.SaveChanges(); 56 | } 57 | ``` 58 | 59 | ## Repository & UnitOfWork 60 | 61 | 仓储与工作单元一起使用,工作单元具有事务特点。 62 | 63 | ```csharp 64 | using (var unitOfWork = fsql.CreateUnitOfWork()) { 65 | var songRepository = unitOfWork.GetRepository(); 66 | var tagRepository = unitOfWork.GetRepository(); 67 | 68 | var song = new Song { BigNumber = "1000000000000000000" }; 69 | songRepository.Insert(song); 70 | 71 | songRepository.Update(song); 72 | 73 | song.BigNumber = (BigInteger.Parse(song.BigNumber) + 1).ToString(); 74 | songRepository.Update(song); 75 | 76 | var tag = new Tag { 77 | Name = "testaddsublist", 78 | Tags = new[] { 79 | new Tag { Name = "sub1" }, 80 | new Tag { Name = "sub2" }, 81 | new Tag { 82 | Name = "sub3", 83 | Tags = new[] { 84 | new Tag { Name = "sub3_01" } 85 | } 86 | } 87 | } 88 | }; 89 | tagRepository.Insert(tag); 90 | 91 | ctx.Commit(); 92 | } 93 | ``` 94 | 95 | ## Repository 96 | 97 | 简单使用仓储,有状态跟踪,它不包含事务的特点。 98 | 99 | ```csharp 100 | var songRepository = fsql.GetRepository(); 101 | var song = new Song { BigNumber = "1000000000000000000" }; 102 | songRepository.Insert(song); 103 | ``` 104 | 105 | ## IFreeSql 核心定义 106 | 107 | ```csharp 108 | var fsql = new FreeSql.FreeSqlBuilder() 109 | .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\dd2.db;Pooling=true;Max Pool Size=10") 110 | .UseAutoSyncStructure(true) 111 | .UseNoneCommandParameter(true) 112 | 113 | .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) 114 | .Build(); 115 | 116 | public class Song { 117 | [Column(IsIdentity = true)] 118 | public int Id { get; set; } 119 | public string BigNumber { get; set; } 120 | 121 | [Column(IsVersion = true)] //乐观锁 122 | public long versionRow { get; set; } 123 | } 124 | public class Tag { 125 | [Column(IsIdentity = true)] 126 | public int Id { get; set; } 127 | 128 | public int? Parent_id { get; set; } 129 | public virtual Tag Parent { get; set; } 130 | 131 | public string Name { get; set; } 132 | 133 | public virtual ICollection Tags { get; set; } 134 | } 135 | 136 | public class SongContext : DbContext { 137 | public DbSet Songs { get; set; } 138 | public DbSet Tags { get; set; } 139 | 140 | protected override void OnConfiguring(DbContextOptionsBuilder builder) { 141 | builder.UseFreeSql(fsql); 142 | } 143 | } 144 | ``` 145 | 146 | # 过滤器与验证 147 | 148 | 假设我们有User(用户)、Topic(主题)两个实体,在领域类中定义了两个仓储: 149 | 150 | ```csharp 151 | var userRepository = fsql.GetGuidRepository(); 152 | var topicRepository = fsql.GetGuidRepository(); 153 | ``` 154 | 155 | 在开发过程中,总是担心 topicRepository 的数据安全问题,即有可能查询或操作到其他用户的主题。因此我们在v0.0.7版本进行了改进,增加了 filter lambad 表达式参数。 156 | 157 | ```csharp 158 | var userRepository = fsql.GetGuidRepository(a => a.Id == 1); 159 | var topicRepository = fsql.GetGuidRepository(a => a.UserId == 1); 160 | ``` 161 | 162 | * 在查询/修改/删除时附加此条件,从而达到不会修改其他用户的数据; 163 | * 在添加时,使用表达式验证数据的合法性,若不合法则抛出异常; 164 | 165 | # 分表与分库 166 | 167 | FreeSql 提供 AsTable 分表的基础方法,GuidRepository 作为分存式仓储将实现了分表与分库(不支持跨服务器分库)的封装。 168 | 169 | ```csharp 170 | var logRepository = fsql.GetGuidRepository(null, oldname => $"{oldname}_{DateTime.Now.ToString("YYYYMM")}"); 171 | ``` 172 | 173 | 上面我们得到一个日志仓储按年月分表,使用它 CURD 最终会操作 Log_201903 表。 174 | 175 | 合并两个仓储,实现分表下的联表查询: 176 | 177 | ```csharp 178 | fsql.GetGuidRepository().Select.FromRepository(logRepository) 179 | .LeftJoin(b => b.UserId == a.Id) 180 | .ToList(); 181 | ``` 182 | 183 | 注意事项: 184 | 185 | * 不能使用 CodeFirst 迁移分表,开发环境时仍然可以迁移 Log 表; 186 | * 不可在分表分库的实体类型中使用《延时加载》; 187 | 188 | # 历史版本 189 | 190 | ### v0.5.23 191 | 192 | - 增加 DbSet/Repository FlushState 手工清除状态管理数据; 193 | 194 | ### v0.5.21 195 | 196 | - 修复 AddOrUpdate/InsertOrUpdate 当主键无值时,仍然查询了一次数据库; 197 | - 增加 查询数据时 TrackToList 对导航集合的状态跟踪; 198 | - 完善 AddOrUpdateNavigateList 联级保存,忽略标记 IsIgnore 的集合属性; 199 | - 完成 IFreeSql.Include、IncludeMany 功能; 200 | 201 | ### v0.5.12 202 | 203 | - 增加 工作单元开关,可在任意 Insert/Update/Delete 之前调用,以关闭工作单元使其失效;[PR #1](https://github.com/2881099/FreeSql.DbContext/pull/1) 204 | 205 | ### v0.5.9 206 | 207 | - 增加 linq to sql 的查询语法,以及单元测试,[wiki](https://github.com/2881099/FreeSql/wiki/LinqToSql); 208 | - 修复 EnableAddOrUpdateNavigateList 设置对异步方法无效的 bug; 209 | 210 | ### v0.5.8 211 | 212 | - 增加 IFreeSql.SetDbContextOptions 设置 DbContext 的功能:开启或禁用连级一对多导航集合属性保存的功能,EnableAddOrUpdateNavigateList(默认开启); 213 | - 增加 IUnitOfWork.IsolationLevel 设置事务级别; 214 | 215 | ### v0.5.7 216 | 217 | - 修复 UnitOfWork.GetRepository() 事务 bug,原因:仓储的每步操作都提交了事务; 218 | 219 | ### v0.5.5 220 | 221 | - 修复 MapEntityValue 对 IsIgnore 未处理的 bug; 222 | 223 | ### v0.5.4 224 | 225 | - 修复 Repository 追加导航集合的保存 bug; 226 | - 公开 IRepository.Orm 对象; 227 | 228 | ### v0.5.3 229 | 230 | - 修复 实体跟踪的 bug,当查询到的实体自增值为 0 时重现; 231 | - 优化 状态管理字典为 ConcurrentDictionary; 232 | 233 | ### v0.5.2 234 | 235 | - 优化 SqlServer UnitOfWork 使用bug,在 FreeSql 内部解决的; 236 | - 补充 测试与支持联合主键的自增; 237 | 238 | ### v0.5.1 239 | 240 | - 补充 开放 DbContext.UnitOfWork 对象,方便扩展并保持在同一个事务执行; 241 | - 补充 增加 DbSet\、Repository\ 使用方法,配合 AsType(实体类型),实现弱类型操作; 242 | - 修复 DbContext.AddOrUpdate 传入 null 时,任然会查询一次数据库的 bug; 243 | - 优化 DbContext.AddOrUpdate 未添加实体主键的错误提醒; 244 | - 修复 DbContext.Set\ 缓存的 bug,使用多种弱类型时发生; 245 | - 修复 IsIgnore 过滤字段后,查询的错误; 246 | - 修复 全局过滤器功能迁移的遗留 bug; 247 | 248 | ### v0.4.14 249 | 250 | - 优化 Add 时未设置主键的错误提醒; 251 | 252 | ### v0.4.13 253 | 254 | - 补充 Repository 增加 Attach 方法; 255 | - 优化 Update/AddOrUpdate 实体的时候,若状态管理不存在,尝试查询一次数据库,以便跟踪对象; 256 | 257 | ### v0.4.12 258 | 259 | - 修复 非自增情况下,Add 后再 Update 该实体时,错误(需要先 Attach 或查询)的 bug; 260 | 261 | ### v0.4.10 262 | 263 | - 补充 开放 DbContext.Orm 对象; 264 | - 修复 OnConfiguring 未配置时注入获取失败的 bug; 265 | 266 | ### v0.4.6 267 | 268 | - 修复 DbSet AddRange/UpdateRange/RemoveRange 参数为空列表时报错,现在不用判断 data.Any() == true 再执行; 269 | - 增加 DbContext 对 DbSet 的快速代理方法(Add/Update/Remove/Attach); 270 | - 增加 DbContext 通用类,命名为:FreeContext,也可以通过 IFreeSql 扩展方法 CreateDbContext 创建; 271 | - 增加 ISelect NoTracking 扩展方法,查询数据时不追踪(从而提升查询性能); 272 | 273 | ### v0.4.5 274 | 275 | - 增加 DbSet Attach 方法附加实体,可用于不查询就更新或删除; 276 | 277 | ### v0.4.2 278 | 279 | - 增加 DbSet UpdateAsync/UpdateRangeAsync 方法,当一个实体被更新两次时,会先执行前面的队列; 280 | - 增加 GetRepository 获取联合主键的适用仓储类; 281 | - 增加 DbSet 在 Add/Update 时对导航属性(OneToMany) 的处理(AddOrUpdate); 282 | 283 | ### v0.4.1 284 | - 独立 FreeSql.DbContext 项目; 285 | - 实现 Repository + DbSet 统一的状态跟踪与工作单元; 286 | - 增加 DbSet AddOrUpdate 方法; 287 | - 增加 Repository InsertOrUpdate 方法; --------------------------------------------------------------------------------