├── .gitignore ├── LICENSE ├── README.md └── src ├── WebApiDemo ├── Controllers │ ├── BookController.cs │ ├── UserBookController.cs │ └── UserController.cs ├── DbContexts │ ├── BookDbContext.cs │ ├── UserBookDbContext.cs │ ├── UserDbContext.cs │ └── UserReadonlyDbContext.cs ├── Entities │ ├── Book.cs │ ├── User.cs │ └── UserBook.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Repositories │ ├── BookRepository.cs │ └── UserRepository.cs ├── SeedDataExtensions.cs ├── Services │ ├── IBookService.cs │ └── Impl │ │ └── BookService.cs ├── WebApiDemo.csproj ├── appsettings.Development.json └── appsettings.json ├── Wei.Repository.Test ├── AssemblyInfo.cs ├── DapperTest.cs ├── Data │ ├── Test.cs │ ├── TestDbContext.cs │ └── TestRepository.cs ├── RepositoryTest.cs ├── TestBase.cs └── Wei.Repository.Test.csproj ├── Wei.Repository.sln └── Wei.Repository ├── DbContextFactory.cs ├── Extensions ├── PageExtensions.cs ├── ServiceCollectionExtension.cs └── TypeExtensions.cs ├── IRepository.cs ├── IUnitOfWork.cs ├── Impl ├── DbContextBase.cs ├── Page.cs ├── Repository.cs ├── RepositoryBase.cs └── UnitOfWork.cs ├── ServiceAttribute.cs └── Wei.Repository.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 a34546 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 | # Wei.Repository 2 | 基于.Net6平台,EFCore+Dapper 封装Repository,实现UnitOfWork,提供基本的CURD操作,支持多数据库,多DbContext 3 | 4 | 5 | ## 快速开始 6 | 7 | > Nuget引用包:Wei.Repository 8 | 9 | 1. 定义实体类 10 | ```cs 11 | public class User 12 | { 13 | // 字段名为Id时,可省略主键标记 14 | //[Key] 15 | public string Id { get; set; } 16 | public string Name { get; set; } 17 | } 18 | ``` 19 | 2. 自定义DbContext,需要继承BaseDbContext 20 | ```cs 21 | public class UserDbContext : BaseDbContext 22 | { 23 | public DemoDbContext(DbContextOptions options) 24 | : base(options) 25 | { 26 | } 27 | public DbSet User { get; set; } 28 | } 29 | ``` 30 | 3. 注入服务,添加Repository组件 31 | ```cs 32 | builder.Services.AddRepository(ops => ops.UseSqlite("Data Source=user.db")); 33 | // Mysql,SqlServer 可以安装驱动后自行测试,demo暂只用sqllite测试 34 | //builder.Services.AddRepository(ops => ops.UseMysql("xxx")); 35 | //builder.Services.AddRepository(ops =>ops.UseSqlServer("xxx")); 36 | ``` 37 | 38 | 4. 【可选】自定义Repository,实现自己的业务逻辑,如果只是简单的crud,可以直接用泛型Repository 39 | ```cs 40 | // 如果只有一个DbContext,可以不用指定UserDbContext类型 41 | // public class UserRepository : Repository, IUserRepository 42 | 43 | public class UserRepository : Repository, IUserRepository 44 | { 45 | public UserRepository(DbContextFactory dbContextFactory) : base(dbContextFactory) 46 | { 47 | } 48 | 49 | // 重写基类新增方法,实现自己的业务逻辑 50 | public override Task InsertAsync(User entity, CancellationToken cancellationToken = default) 51 | { 52 | entity.Id = Guid.NewGuid().ToString(); 53 | return base.InsertAsync(entity, cancellationToken); 54 | } 55 | } 56 | 57 | public interface IUserRepository : IRepository 58 | { 59 | } 60 | ``` 61 | 62 | 5. 在Controller中使用 63 | ```cs 64 | public class UserController : ControllerBase 65 | { 66 | 67 | //自定义仓储 68 | private readonly IUserRepository _userRepository; 69 | 70 | // 工作单元, 71 | // 如果不传入指定DbContext,默认使用第一个注入的DbContext 72 | private readonly IUnitOfWork _unitOfWork; 73 | 74 | 75 | public UserController( 76 | IUserRepository userRepository, 77 | IUnitOfWork unitOfWork) 78 | { 79 | _userRepository = userRepository; 80 | _unitOfWork = unitOfWork; 81 | } 82 | 83 | [HttpPost] 84 | public async Task InsertAsync(string name, CancellationToken cancellationToken) 85 | { 86 | var entity = await _userRepository.InsertAsync(new User { Name = name }, cancellationToken); 87 | _unitOfWork.SaveChanges(); 88 | return entity; 89 | } 90 | } 91 | ``` 92 | 93 | ## 详细介绍 94 | 95 | **1. 泛型IRepository接口** 96 | ```cs 97 | #region Query 98 | // 查询 99 | IQueryable Query(); 100 | IQueryable Query(Expression> predicate); 101 | IQueryable QueryNoTracking(); 102 | IQueryable QueryNoTracking(Expression> predicate); 103 | // 根据主键获取(支持复合主键) 104 | TEntity Get(params object[] id); 105 | ValueTask GetAsync(params object[] id); 106 | ValueTask GetAsync(object[] ids, CancellationToken cancellationToken); 107 | // 获取所有 108 | IEnumerable GetAll(); 109 | Task> GetAllAsync(CancellationToken cancellationToken = default); 110 | IEnumerable GetAll(Expression> predicate); 111 | Task> GetAllAsync(Expression> predicate, CancellationToken cancellationToken = default); 112 | // 获取第一个或默认值 113 | TEntity FirstOrDefault(); 114 | Task FirstOrDefaultAsync(CancellationToken cancellationToken = default); 115 | TEntity FirstOrDefault(Expression> predicate); 116 | Task FirstOrDefaultAsync(Expression> predicate, CancellationToken cancellationToken = default); 117 | #endregion 118 | 119 | #region Insert 120 | // 新增 121 | TEntity Insert(TEntity entity); 122 | Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default); 123 | // 批量新增 124 | void Insert(IEnumerable entities); 125 | Task InsertAsync(IEnumerable entities, CancellationToken cancellationToken = default); 126 | #endregion Insert 127 | 128 | #region Update 129 | // 更新 130 | TEntity Update(TEntity entity); 131 | // 批量更新 132 | void Update(IEnumerable entities); 133 | // 根据表达式条件更新指定字段 134 | IEnumerable Update(Expression> predicate, Action updateAction); 135 | Task> UpdateAsync(Expression> predicate, Action updateAction, CancellationToken cancellationToken = default); 136 | #endregion Update 137 | 138 | #region Delete 139 | // 删除 140 | void Delete(TEntity entity); 141 | // 根据主键(支持复合主键)删除 142 | void Delete(params object[] id); 143 | // 根据表达式条件批量删除 144 | void Delete(Expression> predicate); 145 | #endregion 146 | 147 | #region Aggregate 148 | bool Any(); 149 | Task AnyAsync(CancellationToken cancellationToken = default); 150 | bool Any(Expression> predicate); 151 | Task AnyAsync(Expression> predicate, CancellationToken cancellationToken = default); 152 | int Count(); 153 | Task CountAsync(CancellationToken cancellationToken = default); 154 | int Count(Expression> predicate); 155 | Task CountAsync(Expression> predicate, CancellationToken cancellationToken = default); 156 | #endregion 157 | ``` 158 | 159 | **2. IUnitOfWork 工作单元接口** 160 | ```cs 161 | // 获取 DbContext 162 | public DbContext DbContext { get; } 163 | IDbConnection GetConnection(); 164 | // 工作单元 提交 165 | int SaveChanges(); 166 | Task SaveChangesAsync(CancellationToken cancellationToken = default); 167 | // Dapper 封装 168 | Task> QueryAsync(string sql, object param = null, IDbContextTransaction trans = null) where TEntity : class; 169 | Task ExecuteAsync(string sql, object param, IDbContextTransaction trans = null); 170 | // 开启事务 171 | IDbContextTransaction BeginTransaction(); 172 | public IDbContextTransaction BeginTransaction(IsolationLevel isolationLevel); 173 | public Task BeginTransactionAsync(CancellationToken cancellationToken = default); 174 | public Task BeginTransactionAsync(IsolationLevel isolationLevel, CancellationToken cancellationToken = default); 175 | ``` 176 | 177 | **3. Dapper事务** 178 | ```cs 179 | public async Task InsertWithTransaction(User user1, User user2) 180 | { 181 | using var tran = _unitOfWork.BeginTransaction(); 182 | try 183 | { 184 | await _unitOfWork.ExecuteAsync("INSERT INTO User (Id,Name) VALUES ('1','张三'", user1, tran); 185 | await _unitOfWork.ExecuteAsync("INSERT INTO User (Id,Name) VALUES ('2','李四'", user2, tran); 186 | // 提交事务 187 | tran.Commit(); 188 | } 189 | catch (Exception e) 190 | { 191 | // 异常回归事务 192 | tran.Rollback(); 193 | } 194 | } 195 | ``` 196 | **4. EF+Dapper混合事务** 197 | ```cs 198 | public async Task InsertWithTransaction(User user1, User user2) 199 | { 200 | using var tran = _unitOfWork.BeginTransaction(); 201 | try 202 | { 203 | await _userRepository.InsertAsync(user1); 204 | await _unitOfWork.SaveChangesAsync(); 205 | await _unitOfWork.ExecuteAsync("INSERT INTO User (Id,Name) VALUES ('2','李四'", user2, tran); 206 | // 提交事务 207 | tran.Commit(); 208 | } 209 | catch (Exception e) 210 | { 211 | // 异常回归事务 212 | tran.Rollback(); 213 | } 214 | } 215 | ``` 216 | 217 | **5.服务自动注入** 218 | > 实现了三种服务类型注入 219 | - SingletonServiceAttribute 220 | - ScopedServiceAttribute 221 | - TransientServiceAttribute 222 | 223 | ```cs 224 | [ScopedService] 225 | public class BookService : IBookService 226 | { 227 | } 228 | 229 | public interface IBookService 230 | { 231 | } 232 | ``` 233 | 需要在服务注入是调用配置特性服务注入 234 | ```cs 235 | // 自动注入服务 236 | builder.Services.ConfigureAttributeServices(); 237 | ``` 238 | 239 | **6.其他** 240 | > 支持多数据库连接,多个DbContext,具体请查看Demo 241 | 242 | -------------------------------------------------------------------------------- /src/WebApiDemo/Controllers/BookController.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a34546/Wei.Repository/08580bfae92bb850e04b2050acb0e60265ce59a0/src/WebApiDemo/Controllers/BookController.cs -------------------------------------------------------------------------------- /src/WebApiDemo/Controllers/UserBookController.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a34546/Wei.Repository/08580bfae92bb850e04b2050acb0e60265ce59a0/src/WebApiDemo/Controllers/UserBookController.cs -------------------------------------------------------------------------------- /src/WebApiDemo/Controllers/UserController.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a34546/Wei.Repository/08580bfae92bb850e04b2050acb0e60265ce59a0/src/WebApiDemo/Controllers/UserController.cs -------------------------------------------------------------------------------- /src/WebApiDemo/DbContexts/BookDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using WebApiDemo.Data; 3 | using Wei.Repository; 4 | 5 | namespace WebApiDemo 6 | { 7 | public class BookDbContext : BaseDbContext 8 | { 9 | public BookDbContext(DbContextOptions options) : base(options) 10 | { 11 | } 12 | 13 | public DbSet Book { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/WebApiDemo/DbContexts/UserBookDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using WebApiDemo.Data; 3 | using WebApiDemo.Entities; 4 | using Wei.Repository; 5 | 6 | namespace WebApiDemo 7 | { 8 | public class UserBookDbContext : BaseDbContext 9 | { 10 | public UserBookDbContext(DbContextOptions options) : base(options) 11 | { 12 | } 13 | 14 | public DbSet UserBook { get; set; } 15 | 16 | protected override void OnModelCreating(ModelBuilder modelBuilder) 17 | { 18 | // 指定复合主键 19 | modelBuilder.Entity().HasKey(x => new { x.UserId, x.BookId }); 20 | 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/WebApiDemo/DbContexts/UserDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using WebApiDemo.Data; 3 | using Wei.Repository; 4 | 5 | namespace WebApiDemo 6 | { 7 | public class UserDbContext : BaseDbContext 8 | { 9 | public UserDbContext(DbContextOptions options) : base(options) 10 | { 11 | } 12 | 13 | public DbSet User { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/WebApiDemo/DbContexts/UserReadonlyDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using WebApiDemo.Data; 3 | using Wei.Repository; 4 | 5 | namespace WebApiDemo 6 | { 7 | public class UserReadonlyDbContext : BaseDbContext 8 | { 9 | public UserReadonlyDbContext(DbContextOptions options) : base(options) 10 | { 11 | } 12 | 13 | public DbSet User { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/WebApiDemo/Entities/Book.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace WebApiDemo.Data 3 | { 4 | public class Book 5 | { 6 | public int Id { get; set; } 7 | public string Name { get; set; } 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/WebApiDemo/Entities/User.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiDemo.Data 2 | { 3 | public class User 4 | { 5 | public string Id { get; set; } 6 | public string Name { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/WebApiDemo/Entities/UserBook.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace WebApiDemo.Entities 4 | { 5 | // 复合主键使用场景 6 | 7 | public class UserBook 8 | { 9 | public string UserId { get; set; } 10 | public int BookId { get; set; } 11 | 12 | public string Name { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/WebApiDemo/Program.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a34546/Wei.Repository/08580bfae92bb850e04b2050acb0e60265ce59a0/src/WebApiDemo/Program.cs -------------------------------------------------------------------------------- /src/WebApiDemo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:29850", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "WebApiDemo": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5134", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "IIS Express": { 23 | "commandName": "IISExpress", 24 | "launchBrowser": true, 25 | "launchUrl": "swagger", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/WebApiDemo/Repositories/BookRepository.cs: -------------------------------------------------------------------------------- 1 | using WebApiDemo.Data; 2 | using Wei.Repository; 3 | 4 | namespace WebApiDemo 5 | { 6 | public class BookRepository : Repository, IBookRepository 7 | { 8 | public BookRepository(DbContextFactory dbContextFactory) : base(dbContextFactory) 9 | { 10 | } 11 | 12 | public override Book Update(Book entity) 13 | { 14 | entity.Name += $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}"; 15 | return base.Update(entity); 16 | } 17 | 18 | } 19 | 20 | public interface IBookRepository : IRepository 21 | { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/WebApiDemo/Repositories/UserRepository.cs: -------------------------------------------------------------------------------- 1 | using WebApiDemo.Data; 2 | using Wei.Repository; 3 | 4 | namespace WebApiDemo 5 | { 6 | public class UserRepository : Repository, IUserRepository 7 | { 8 | public UserRepository(DbContextFactory dbContextFactory) : base(dbContextFactory) 9 | { 10 | } 11 | 12 | public override Task InsertAsync(User entity, CancellationToken cancellationToken = default) 13 | { 14 | entity.Id = Guid.NewGuid().ToString(); 15 | return base.InsertAsync(entity, cancellationToken); 16 | } 17 | 18 | public override User Update(User entity) 19 | { 20 | entity.Name += $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}"; 21 | return base.Update(entity); 22 | } 23 | } 24 | 25 | public interface IUserRepository : IRepository 26 | { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/WebApiDemo/SeedDataExtensions.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using Wei.Repository; 3 | 4 | namespace WebApiDemo 5 | { 6 | public static class SeedDataExtensions 7 | { 8 | public static void InitSeedData(this IApplicationBuilder app) 9 | { 10 | InitBookTable(app); 11 | InitUserTable(app); 12 | InitUserBookTable(app); 13 | } 14 | 15 | private static void InitBookTable(IApplicationBuilder app) 16 | { 17 | using var scope = app.ApplicationServices.CreateScope(); 18 | var unitOfWork = scope.ServiceProvider.GetRequiredService>(); 19 | using var conn = unitOfWork.GetConnection(); 20 | var count = conn.ExecuteScalar("select count(*) from sqlite_master where type='table' and name = 'Book'"); 21 | if (count <= 0) 22 | { 23 | conn.Execute(@" 24 | CREATE TABLE ""Book"" ( 25 | Id INTEGER PRIMARY KEY AUTOINCREMENT, 26 | Name TEXT 27 | );"); 28 | conn.Execute("INSERT INTO \"Book\" (Name) VALUES\r\n\t ('C#');"); 29 | } 30 | } 31 | 32 | private static void InitUserTable(IApplicationBuilder app) 33 | { 34 | using var scope = app.ApplicationServices.CreateScope(); 35 | var unitOfWork = scope.ServiceProvider.GetRequiredService>(); 36 | using var conn = unitOfWork.GetConnection(); 37 | var count = conn.ExecuteScalar("select count(*) from sqlite_master where type='table' and name = 'User'"); 38 | if (count <= 0) 39 | { 40 | conn.Execute(@" 41 | CREATE TABLE ""User"" ( 42 | Id TEXT PRIMARY KEY, 43 | Name TEXT 44 | );"); 45 | conn.Execute("INSERT INTO \"User\" (Id,Name) VALUES\r\n\t ('u1','张三');"); 46 | } 47 | } 48 | 49 | private static void InitUserBookTable(IApplicationBuilder app) 50 | { 51 | using var scope = app.ApplicationServices.CreateScope(); 52 | var unitOfWork = scope.ServiceProvider.GetRequiredService>(); 53 | using var conn = unitOfWork.GetConnection(); 54 | var count = conn.ExecuteScalar("select count(*) from sqlite_master where type='table' and name = 'UserBook'"); 55 | if (count <= 0) 56 | { 57 | conn.Execute(@" 58 | CREATE TABLE ""UserBook"" ( 59 | UserId TEXT, 60 | BookId INTEGER, 61 | Name TEXT, 62 | constraint pk_UserBook primary key (UserId,BookId) 63 | );"); 64 | conn.Execute("INSERT INTO \"UserBook\" (UserId,BookId,Name) VALUES\r\n\t ('u1',1,'张三');"); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/WebApiDemo/Services/IBookService.cs: -------------------------------------------------------------------------------- 1 | using WebApiDemo.Data; 2 | 3 | namespace WebApiDemo.Services.Impl 4 | { 5 | public interface IBookService 6 | { 7 | Task> GetAllAsync(CancellationToken cancellationToken); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/WebApiDemo/Services/Impl/BookService.cs: -------------------------------------------------------------------------------- 1 | using WebApiDemo.Data; 2 | using Wei.Repository; 3 | 4 | namespace WebApiDemo.Services.Impl 5 | { 6 | // 标记后,会自动注入 7 | [ScopedService] 8 | public class BookService : IBookService 9 | { 10 | private readonly IRepository _repository; 11 | public BookService(IRepository repository) 12 | { 13 | _repository = repository; 14 | } 15 | 16 | public async Task> GetAllAsync(CancellationToken cancellationToken) 17 | { 18 | var books = await _repository.GetAllAsync(cancellationToken); 19 | return books.OrderByDescending(x => x.Id).ToList(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/WebApiDemo/WebApiDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/WebApiDemo/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/WebApiDemo/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/Wei.Repository.Test/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | //Optional 3 | [assembly: CollectionBehavior(DisableTestParallelization = true)] 4 | //Optional 5 | [assembly: TestCaseOrderer("Xunit.Extensions.Ordering.TestCaseOrderer", "Xunit.Extensions.Ordering")] 6 | //Optional 7 | [assembly: TestCollectionOrderer("Xunit.Extensions.Ordering.CollectionOrderer", "Xunit.Extensions.Ordering")] -------------------------------------------------------------------------------- /src/Wei.Repository.Test/DapperTest.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a34546/Wei.Repository/08580bfae92bb850e04b2050acb0e60265ce59a0/src/Wei.Repository.Test/DapperTest.cs -------------------------------------------------------------------------------- /src/Wei.Repository.Test/Data/Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text; 5 | 6 | namespace Wei.Repository.Test 7 | { 8 | public class Test 9 | { 10 | [Key] 11 | public string Id { get; set; } 12 | public string Name { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Wei.Repository.Test/Data/TestDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Wei.Repository; 3 | 4 | namespace Wei.Repository.Test 5 | { 6 | public class TestDbContext : BaseDbContext 7 | { 8 | public TestDbContext(DbContextOptions options) : base(options) 9 | { 10 | } 11 | 12 | public DbSet Test { get; set; } 13 | 14 | protected override void OnModelCreating(ModelBuilder modelBuilder) 15 | { 16 | 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Wei.Repository.Test/Data/TestRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace Wei.Repository.Test 7 | { 8 | public class TestRepository : Repository, ITestRepository 9 | { 10 | public TestRepository(DbContextFactory dbContextFactory) : base(dbContextFactory) 11 | { 12 | } 13 | 14 | public override Test FirstOrDefault() 15 | { 16 | return null; 17 | } 18 | } 19 | 20 | public interface ITestRepository : IRepository 21 | { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Wei.Repository.Test/RepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Xunit; 6 | using Xunit.Extensions.Ordering; 7 | 8 | namespace Wei.Repository.Test 9 | { 10 | public class RepositoryTest : TestBase 11 | { 12 | [Fact, Order(1)] 13 | public async Task InsertAsync() 14 | { 15 | var id1 = Guid.NewGuid().ToString(); 16 | var id2 = Guid.NewGuid().ToString(); 17 | var id3 = Guid.NewGuid().ToString(); 18 | var id4 = Guid.NewGuid().ToString(); 19 | var test1 = TestRepository.Insert(new Test { Id = id1, Name = "Insert1" }); 20 | var test2 = Repository.Insert(new Test { Id = id2, Name = "Insert2" }); 21 | var test3 = await TestRepository.InsertAsync(new Test { Id = id3, Name = "InsertAsync1" }); 22 | var test4 = await Repository.InsertAsync(new Test { Id = id4, Name = "InsertAsync2" }); 23 | await UnitOfWork.SaveChangesAsync(); 24 | Assert.NotNull(test1); 25 | Assert.NotNull(test2); 26 | Assert.NotNull(test3); 27 | Assert.NotNull(test4); 28 | Assert.Equal(test1.Id, id1); 29 | Assert.Equal(test2.Id, id2); 30 | Assert.Equal(test3.Id, id3); 31 | Assert.Equal(test4.Id, id4); 32 | } 33 | 34 | [Fact, Order(2)] 35 | public async Task InsertRangeAsync() 36 | { 37 | TestRepository.Insert(new List { 38 | new Test { Id = Guid.NewGuid().ToString(), Name = "InsertRange1" }, 39 | new Test { Id = Guid.NewGuid().ToString(), Name = "InsertRange2" }, 40 | }); 41 | Repository.Insert(new List { 42 | new Test { Id = Guid.NewGuid().ToString(), Name = "InsertRange3" }, 43 | new Test { Id = Guid.NewGuid().ToString(), Name = "InsertRange4" }, 44 | }); 45 | await TestRepository.InsertAsync(new List { 46 | new Test { Id = Guid.NewGuid().ToString(), Name = "InsertRangeAsync1" }, 47 | new Test { Id = Guid.NewGuid().ToString(), Name = "InsertRangeAsync2" }, 48 | }); 49 | await Repository.InsertAsync(new List { 50 | new Test { Id = Guid.NewGuid().ToString(), Name = "InsertRangeAsync3" }, 51 | new Test { Id = Guid.NewGuid().ToString(), Name = "InsertRangeAsync4" }, 52 | }); 53 | await UnitOfWork.SaveChangesAsync(); 54 | var test1 = await TestRepository.GetAllAsync(x => new string[] { "InsertRange1", "InsertRange2", "InsertRange3", "InsertRange4" }.Contains(x.Name)); 55 | var test2 = await Repository.GetAllAsync(x => new string[] { "InsertRangeAsync1", "InsertRangeAsync2", "InsertRangeAsync3", "InsertRangeAsync4" }.Contains(x.Name)); 56 | 57 | Assert.True(test1.Count() >= 4); 58 | Assert.True(test2.Count() >= 4); 59 | 60 | } 61 | 62 | [Fact, Order(3)] 63 | public void Update() 64 | { 65 | if (!TestRepository.Any(x => x.Name == "Update1")) 66 | { 67 | TestRepository.Insert(new List { 68 | new Test { Id = Guid.NewGuid().ToString(), Name = "Update1" }, 69 | }); 70 | UnitOfWork.SaveChanges(); 71 | } 72 | 73 | var test1 = TestRepository.FirstOrDefault(x => x.Name == "Update1"); 74 | test1.Name += "_1"; 75 | TestRepository.Update(test1); 76 | 77 | if (!TestRepository.Any(x => x.Name == "Update2")) 78 | { 79 | Repository.Insert(new List { 80 | new Test { Id = Guid.NewGuid().ToString(), Name = "Update2" }, 81 | }); 82 | UnitOfWork.SaveChanges(); 83 | } 84 | var test2 = Repository.FirstOrDefault(x => x.Name == "Update2"); 85 | test2.Name += "_1"; 86 | Repository.Update(test2); 87 | 88 | UnitOfWork.SaveChanges(); 89 | 90 | var res1 = TestRepository.Get(test1.Id); 91 | var res2 = Repository.Get(test2.Id); 92 | 93 | Assert.Equal(test1.Name, res1.Name); 94 | Assert.Equal(test2.Name, res2.Name); 95 | } 96 | 97 | [Fact, Order(4)] 98 | public async Task UpdateRange() 99 | { 100 | if (!TestRepository.Any(x => x.Name == "UpdateRange1")) 101 | { 102 | TestRepository.Insert(new List { 103 | new Test { Id = Guid.NewGuid().ToString(), Name = "UpdateRange1" }, 104 | new Test { Id = Guid.NewGuid().ToString(), Name = "UpdateRange1" }, 105 | }); 106 | UnitOfWork.SaveChanges(); 107 | } 108 | 109 | var update1list = TestRepository.Query(x => x.Name == "UpdateRange1").ToList(); 110 | foreach (var item in update1list) 111 | { 112 | item.Name = "UpdateRange111"; 113 | } 114 | TestRepository.Update(update1list); 115 | 116 | if (!Repository.Any(x => x.Name == "UpdateRange2")) 117 | { 118 | Repository.Insert(new List { 119 | new Test { Id = Guid.NewGuid().ToString(), Name = "UpdateRange2" }, 120 | new Test { Id = Guid.NewGuid().ToString(), Name = "UpdateRange2" }, 121 | }); 122 | UnitOfWork.SaveChanges(); 123 | } 124 | 125 | var update2list = Repository.Query(x => x.Name == "UpdateRange2").ToList(); 126 | foreach (var item in update2list) 127 | { 128 | item.Name = "UpdateRange222"; 129 | } 130 | Repository.Update(update2list); 131 | await UnitOfWork.SaveChangesAsync(); 132 | var res1 = TestRepository.QueryNoTracking(x => x.Name == "UpdateRange111").ToList(); 133 | var res2 = Repository.QueryNoTracking(x => x.Name == "UpdateRange222").ToList(); 134 | 135 | Assert.True(res1.Count >= 2); 136 | Assert.True(res2.Count >= 2); 137 | 138 | } 139 | 140 | [Fact, Order(4)] 141 | public async Task UpdateBy() 142 | { 143 | if (!TestRepository.Any(x => x.Name == "UpdateBy")) 144 | { 145 | TestRepository.Insert(new List { 146 | new Test { Id = Guid.NewGuid().ToString(), Name = "UpdateBy" }, 147 | new Test { Id = Guid.NewGuid().ToString(), Name = "UpdateBy" }, 148 | }); 149 | UnitOfWork.SaveChanges(); 150 | } 151 | var name = "UpdateBy" + Guid.NewGuid(); 152 | var entities = await TestRepository.UpdateAsync(x => x.Name == "UpdateBy", x => x.Name = name); 153 | await UnitOfWork.SaveChangesAsync(); 154 | var res1 = TestRepository.QueryNoTracking(x => x.Name == name).ToList(); 155 | Assert.True(entities.Count() == res1.Count); 156 | 157 | } 158 | 159 | [Fact, Order(5)] 160 | public async void Delete() 161 | { 162 | var id1 = Guid.NewGuid().ToString(); 163 | var id2 = Guid.NewGuid().ToString(); 164 | var id3 = Guid.NewGuid().ToString(); 165 | var id4 = Guid.NewGuid().ToString(); 166 | TestRepository.Insert(new List { 167 | new Test { Id = id1, Name = "Delete" }, 168 | new Test { Id = id2, Name = "Delete" }, 169 | new Test { Id = id3, Name = "Delete" }, 170 | new Test { Id = id4, Name = "Delete" }, 171 | }); 172 | UnitOfWork.SaveChanges(); 173 | 174 | TestRepository.Delete(id1); 175 | Repository.Delete(id2); 176 | TestRepository.Delete(x => x.Id == id3); 177 | Repository.Delete(x => x.Id == id4); 178 | UnitOfWork.SaveChanges(); 179 | 180 | var res1 = TestRepository.Get(id1); 181 | var res2 = Repository.Get(id1); 182 | var res3 = await TestRepository.GetAsync(id1); 183 | var res4 = await Repository.GetAsync(id1); 184 | 185 | Assert.Null(res1); 186 | Assert.Null(res2); 187 | Assert.Null(res3); 188 | Assert.Null(res4); 189 | } 190 | 191 | [Fact, Order(6)] 192 | public async Task AnyAsync() 193 | { 194 | var res1 = Repository.Any(); 195 | var res2 = await TestRepository.AnyAsync(); 196 | 197 | var res3 = Repository.Any(x => x.Name.Contains("Update")); 198 | var res4 = await TestRepository.AnyAsync(x => x.Name.Contains("Update")); 199 | 200 | Assert.True(res1 && res2 && res3 && res4); 201 | } 202 | 203 | [Fact, Order(7)] 204 | public async Task CountAsync() 205 | { 206 | var res1 = Repository.Count(); 207 | var res2 = await TestRepository.CountAsync(); 208 | var res3 = Repository.Count(x => x.Name.Contains("Update")); 209 | var res4 = await TestRepository.CountAsync(x => x.Name.Contains("Update")); 210 | Assert.True(res1 > 0); 211 | Assert.True(res2 > 0); 212 | Assert.True(res3 > 0); 213 | Assert.True(res4 > 0); 214 | } 215 | 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/Wei.Repository.Test/TestBase.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using Microsoft.Data.Sqlite; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using System; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | using Xunit.Extensions.Ordering; 11 | 12 | namespace Wei.Repository.Test 13 | { 14 | public class TestBase 15 | { 16 | readonly ServiceProvider _serviceProvider; 17 | public IUnitOfWork UnitOfWork { get; set; } 18 | public ITestRepository TestRepository { get; set; } 19 | public IRepository Repository { get; set; } 20 | public TestBase() 21 | { 22 | var services = new ServiceCollection(); 23 | services.AddRepository(ops => ops.UseSqlite("Data Source=user.db")); 24 | 25 | _serviceProvider = services.BuildServiceProvider(); 26 | UnitOfWork = _serviceProvider.GetRequiredService(); 27 | TestRepository = _serviceProvider.GetRequiredService(); 28 | Repository = _serviceProvider.GetRequiredService>(); 29 | InitUserTable(); 30 | } 31 | 32 | private void InitUserTable() 33 | { 34 | var dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "test.db"); 35 | if (!File.Exists(dbPath)) File.Create(dbPath); 36 | var unitOfWork = _serviceProvider.GetRequiredService>(); 37 | using var conn = unitOfWork.GetConnection(); 38 | var count = conn.ExecuteScalar("select count(*) from sqlite_master where type='table' and name = 'Test'"); 39 | if (count <= 0) 40 | { 41 | conn.Execute(@" 42 | CREATE TABLE Test ( 43 | Id TEXT PRIMARY KEY, 44 | Name TEXT 45 | );"); 46 | } 47 | } 48 | 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Wei.Repository.Test/Wei.Repository.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/Wei.Repository.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32804.467 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wei.Repository", "Wei.Repository\Wei.Repository.csproj", "{F05B885D-3428-4179-B3E4-67F9156BBE13}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Demo", "Demo", "{D670B26E-75C6-4B4A-89C9-1436A7D26CFC}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wei.Repository.Test", "Wei.Repository.Test\Wei.Repository.Test.csproj", "{4A83D019-9264-49DC-BC05-72131732CF76}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiDemo", "WebApiDemo\WebApiDemo.csproj", "{0CCCCA9F-2FCD-4DA0-9CCD-1BC2E196BA61}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {F05B885D-3428-4179-B3E4-67F9156BBE13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {F05B885D-3428-4179-B3E4-67F9156BBE13}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {F05B885D-3428-4179-B3E4-67F9156BBE13}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {F05B885D-3428-4179-B3E4-67F9156BBE13}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {4A83D019-9264-49DC-BC05-72131732CF76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {4A83D019-9264-49DC-BC05-72131732CF76}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {4A83D019-9264-49DC-BC05-72131732CF76}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {4A83D019-9264-49DC-BC05-72131732CF76}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {0CCCCA9F-2FCD-4DA0-9CCD-1BC2E196BA61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {0CCCCA9F-2FCD-4DA0-9CCD-1BC2E196BA61}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {0CCCCA9F-2FCD-4DA0-9CCD-1BC2E196BA61}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {0CCCCA9F-2FCD-4DA0-9CCD-1BC2E196BA61}.Release|Any CPU.Build.0 = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | GlobalSection(NestedProjects) = preSolution 37 | {0CCCCA9F-2FCD-4DA0-9CCD-1BC2E196BA61} = {D670B26E-75C6-4B4A-89C9-1436A7D26CFC} 38 | EndGlobalSection 39 | GlobalSection(ExtensibilityGlobals) = postSolution 40 | SolutionGuid = {4DFBECCF-39CA-4B99-BAA9-81DAD4082463} 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /src/Wei.Repository/DbContextFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | 8 | namespace Wei.Repository 9 | { 10 | public class DbContextFactory 11 | { 12 | private readonly IEnumerable _dbContexts; 13 | public DbContextFactory(IEnumerable dbContexts) 14 | { 15 | _dbContexts = dbContexts; 16 | } 17 | 18 | public DbContext GetFirstOrDefaultDbContext() 19 | { 20 | var dbContext = _dbContexts.FirstOrDefault(); 21 | if (dbContext == null) 22 | throw new ArgumentNullException($"DbContext获取失败,请检查是否已经注册到容器内。"); 23 | return dbContext; 24 | } 25 | 26 | public DbContext GetDbContext() where TDbContext : DbContext 27 | { 28 | var dbContext = _dbContexts.FirstOrDefault(x => x.GetType() == typeof(TDbContext)); 29 | if (dbContext == null) 30 | throw new ArgumentNullException(typeof(TDbContext).Name, $"{typeof(TDbContext).Name}获取失败,请检查是否已经注册到容器内。"); 31 | return dbContext; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Wei.Repository/Extensions/PageExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Wei.Repository 10 | { 11 | public static class PageExtensions 12 | { 13 | 14 | public static async Task> ToPageAsync( 15 | this IQueryable query, 16 | int pageIndex, 17 | int pageSize, 18 | CancellationToken cancellationToken = default) 19 | { 20 | if (pageIndex < 1) throw new ArgumentOutOfRangeException(nameof(pageIndex), "页码从1开始"); 21 | int startIndex = (pageIndex - 1) * pageSize; 22 | int count = await query.CountAsync(cancellationToken).ConfigureAwait(false); 23 | var items = await query.Skip(startIndex) 24 | .Take(pageSize) 25 | .ToListAsync(cancellationToken) 26 | .ConfigureAwait(false); 27 | return new Page(items, pageIndex, pageSize, count); 28 | } 29 | public static Page ToPage( 30 | this IQueryable query, 31 | int pageIndex, 32 | int pageSize) 33 | { 34 | if (pageIndex < 1) throw new ArgumentOutOfRangeException(nameof(pageIndex), "页码从1开始"); 35 | int startIndex = (pageIndex - 1) * pageSize; 36 | int count = query.Count(); 37 | var items = query.Skip(startIndex) 38 | .Take(pageSize) 39 | .ToList(); 40 | return new Page(items, pageIndex, pageSize, count); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Wei.Repository/Extensions/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Runtime.CompilerServices; 10 | using System.Runtime.Loader; 11 | using System.Text; 12 | 13 | namespace Wei.Repository 14 | { 15 | public static class ServiceCollectionExtension 16 | { 17 | 18 | public static IServiceCollection AddRepository(this IServiceCollection services, 19 | Action options, ServiceLifetime repositoryLifetime = ServiceLifetime.Scoped) where TDbContext : BaseDbContext 20 | { 21 | services.AddDbContext(options); 22 | services.AddScoped(); 23 | services.AddRepository(repositoryLifetime); 24 | return services; 25 | } 26 | 27 | private static IServiceCollection AddRepository(this IServiceCollection services, ServiceLifetime serviceLifetime) 28 | { 29 | services.TryAddScoped(); 30 | services.TryAddScoped(typeof(IUnitOfWork), typeof(UnitOfWork)); 31 | services.TryAddScoped(typeof(IUnitOfWork<>), typeof(UnitOfWork<>)); 32 | var assemblies = AppDomain.CurrentDomain.GetCurrentPathAssembly().Where(x => !(x.GetName().Name.Equals("Wei.Repository"))); 33 | services.AddRepository(assemblies, typeof(IRepository<>), serviceLifetime); 34 | services.AddRepository(assemblies, typeof(IRepository<,>), serviceLifetime); 35 | services.TryAdd(new ServiceDescriptor(typeof(IRepository<>), typeof(Repository<>), serviceLifetime)); 36 | services.TryAdd(new ServiceDescriptor(typeof(IRepository<,>), typeof(Repository<,>), serviceLifetime)); 37 | return services; 38 | } 39 | private static void AddRepository(this IServiceCollection services, IEnumerable assemblies, Type baseType, ServiceLifetime serviceLifetime) 40 | { 41 | 42 | foreach (var assembly in assemblies) 43 | { 44 | var types = assembly.GetTypes() 45 | .Where(x => x.IsClass 46 | && !x.IsAbstract 47 | && x.BaseType != null 48 | && x.HasImplementedRawGeneric(baseType)); 49 | foreach (var type in types) 50 | { 51 | var interfaces = type.GetInterfaces(); 52 | var interfaceType = interfaces.FirstOrDefault(x => x.Name == $"I{type.Name}"); 53 | if (interfaceType == null) interfaceType = type; 54 | var serviceDescriptor = new ServiceDescriptor(interfaceType, type, serviceLifetime); 55 | services.TryAdd(serviceDescriptor); 56 | } 57 | } 58 | 59 | } 60 | 61 | public static IServiceCollection ConfigureAttributeServices(this IServiceCollection services) 62 | { 63 | var types = AssemblyLoadContext 64 | .Default 65 | .Assemblies 66 | .Where(x => !x.GetName().Name.StartsWith("Microsoft") && !x.GetName().Name.StartsWith("System")) 67 | .SelectMany(x => x.GetTypes()) 68 | .Where(x => x.IsClass && !x.IsAbstract && x.IsPublic) 69 | .Where(x => x.GetCustomAttribute() != null); 70 | foreach (var type in types) 71 | { 72 | var serviceAttr = type.GetCustomAttribute(); 73 | serviceAttr.Configure(services, type); 74 | } 75 | return services; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Wei.Repository/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyModel; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | 8 | namespace Wei.Repository 9 | { 10 | public static class TypeExtensions 11 | { 12 | public static List GetCurrentPathAssembly(this AppDomain domain) 13 | { 14 | var dlls = DependencyContext.Default.CompileLibraries 15 | .Where(x => !x.Name.StartsWith("Microsoft") && !x.Name.StartsWith("System")) 16 | .ToList(); 17 | var list = new List(); 18 | if (dlls.Any()) 19 | { 20 | foreach (var dll in dlls) 21 | { 22 | if (dll.Type == "project") 23 | { 24 | list.Add(Assembly.Load(dll.Name)); 25 | } 26 | } 27 | } 28 | return list; 29 | } 30 | 31 | public static bool HasImplementedRawGeneric(this Type type, Type generic) 32 | { 33 | if (type == null) throw new ArgumentNullException(nameof(type)); 34 | if (generic == null) throw new ArgumentNullException(nameof(generic)); 35 | var isTheRawGenericType = type.GetInterfaces().Any(IsTheRawGenericType); 36 | if (isTheRawGenericType) return true; 37 | while (type != null && type != typeof(object)) 38 | { 39 | isTheRawGenericType = IsTheRawGenericType(type); 40 | if (isTheRawGenericType) return true; 41 | type = type.BaseType; 42 | } 43 | return false; 44 | 45 | bool IsTheRawGenericType(Type test) 46 | => generic == (test.IsGenericType ? test.GetGenericTypeDefinition() : test); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Wei.Repository/IRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Wei.Repository 10 | { 11 | 12 | public interface IRepository : IRepositoryBase 13 | where TEntity : class 14 | { 15 | 16 | } 17 | 18 | public interface IRepository : IRepositoryBase 19 | where TEntity : class 20 | where TDbContext : DbContext 21 | { 22 | 23 | } 24 | 25 | public interface IRepositoryBase 26 | where TEntity : class 27 | { 28 | #region Query 29 | 30 | /// 31 | /// 查询 32 | /// 33 | IQueryable Query(); 34 | /// 35 | /// 查询 36 | /// 37 | IQueryable Query(Expression> predicate); 38 | 39 | /// 40 | /// 查询不跟踪实体变化 41 | /// 42 | IQueryable QueryNoTracking(); 43 | /// 44 | /// 查询不跟踪实体变化 45 | /// 46 | IQueryable QueryNoTracking(Expression> predicate); 47 | 48 | /// 49 | /// 根据主键获取 50 | /// 51 | TEntity Get(params object[] id); 52 | /// 53 | /// 根据主键获取(支持复合主键) 54 | /// 55 | ValueTask GetAsync(params object[] id); 56 | /// 57 | /// 根据主键(复合主键) 58 | /// 59 | ValueTask GetAsync(object[] ids, CancellationToken cancellationToken); 60 | 61 | /// 62 | /// 获取所有 63 | /// 64 | IEnumerable GetAll(); 65 | /// 66 | /// 获取所有 67 | /// 68 | Task> GetAllAsync(CancellationToken cancellationToken = default); 69 | /// 70 | /// 根据表达式条件获取所有 71 | /// 72 | IEnumerable GetAll(Expression> predicate); 73 | /// 74 | /// 根据表达式条件获取所有 75 | /// 76 | Task> GetAllAsync(Expression> predicate, CancellationToken cancellationToken = default); 77 | 78 | /// 79 | /// 获取第一个或默认值 80 | /// 81 | TEntity FirstOrDefault(); 82 | /// 83 | /// 获取第一个或默认值 84 | /// 85 | Task FirstOrDefaultAsync(CancellationToken cancellationToken = default); 86 | /// 87 | /// 根据表达式条件获取第一个或默认值 88 | /// 89 | TEntity FirstOrDefault(Expression> predicate); 90 | /// 91 | /// 获取第一个或默认值 92 | /// 93 | Task FirstOrDefaultAsync(Expression> predicate, CancellationToken cancellationToken = default); 94 | 95 | #endregion 96 | 97 | #region Insert 98 | 99 | /// 100 | /// 新增 101 | /// 102 | TEntity Insert(TEntity entity); 103 | /// 104 | /// 新增 105 | /// 106 | Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default); 107 | 108 | /// 109 | /// 批量新增 110 | /// 111 | void Insert(IEnumerable entities); 112 | /// 113 | /// 批量新增 114 | /// 115 | Task InsertAsync(IEnumerable entities, CancellationToken cancellationToken = default); 116 | 117 | #endregion Insert 118 | 119 | #region Update 120 | 121 | /// 122 | /// 更新 123 | /// 124 | TEntity Update(TEntity entity); 125 | /// 126 | /// 批量更新 127 | /// 128 | void Update(IEnumerable entities); 129 | 130 | /// 131 | /// 根据查询条件更新指定字段 132 | /// 133 | IEnumerable Update(Expression> predicate, Action updateAction); 134 | 135 | /// 136 | /// 根据查询条件更新指定字段 137 | /// 138 | Task> UpdateAsync(Expression> predicate, Action updateAction, CancellationToken cancellationToken = default); 139 | 140 | #endregion Update 141 | 142 | #region Delete 143 | 144 | /// 145 | /// 根据传入的实体删除 146 | /// 147 | void Delete(TEntity entity); 148 | 149 | /// 150 | /// 根据主键(支持复合主键)删除 151 | /// 152 | void Delete(params object[] id); 153 | 154 | /// 155 | /// 根据表达式条件批量删除 156 | /// 157 | void Delete(Expression> predicate); 158 | 159 | #endregion 160 | 161 | #region Aggregate 162 | bool Any(); 163 | Task AnyAsync(CancellationToken cancellationToken = default); 164 | bool Any(Expression> predicate); 165 | Task AnyAsync(Expression> predicate, CancellationToken cancellationToken = default); 166 | int Count(); 167 | Task CountAsync(CancellationToken cancellationToken = default); 168 | int Count(Expression> predicate); 169 | Task CountAsync(Expression> predicate, CancellationToken cancellationToken = default); 170 | #endregion 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/Wei.Repository/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Storage; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Data; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Wei.Repository 11 | { 12 | public interface IUnitOfWork : IDisposable 13 | { 14 | public DbContext DbContext { get; } 15 | IDbConnection GetConnection(); 16 | int SaveChanges(); 17 | Task SaveChangesAsync(CancellationToken cancellationToken = default); 18 | 19 | /// 20 | /// 查询 21 | /// 用法:await _unitOfWork.QueryAsync`Demo`("select id,title from post where id = @id", new { id = 1 }); 22 | /// 23 | /// 24 | /// sql语句 25 | /// 参数 26 | /// 27 | /// 28 | Task> QueryAsync(string sql, object param = null, IDbContextTransaction trans = null) where TEntity : class; 29 | 30 | /// 31 | /// ExecuteAsync 32 | /// 用法:await _unitOfWork.ExecuteAsync("update post set title =@title where id =@id", new { title = "", id=1 }); 33 | /// 34 | /// 35 | /// sql语句 36 | /// 参数化 37 | /// 38 | /// 39 | Task ExecuteAsync(string sql, object param, IDbContextTransaction trans = null); 40 | 41 | /// 42 | /// 开启事务 43 | /// 44 | /// 45 | IDbContextTransaction BeginTransaction(); 46 | public IDbContextTransaction BeginTransaction(IsolationLevel isolationLevel); 47 | public Task BeginTransactionAsync(CancellationToken cancellationToken = default); 48 | public Task BeginTransactionAsync(IsolationLevel isolationLevel, CancellationToken cancellationToken = default); 49 | } 50 | 51 | public interface IUnitOfWork : IDisposable where TDbContext : DbContext 52 | { 53 | public DbContext DbContext { get; } 54 | IDbConnection GetConnection(); 55 | int SaveChanges(); 56 | Task SaveChangesAsync(CancellationToken cancellationToken = default); 57 | 58 | /// 59 | /// 查询 60 | /// 用法:await _unitOfWork.QueryAsync`Demo`("select id,title from post where id = @id", new { id = 1 }); 61 | /// 62 | /// 63 | /// sql语句 64 | /// 参数 65 | /// 66 | /// 67 | Task> QueryAsync(string sql, object param = null, IDbContextTransaction trans = null) where TEntity : class; 68 | 69 | /// 70 | /// ExecuteAsync 71 | /// 用法:await _unitOfWork.ExecuteAsync("update post set title =@title where id =@id", new { title = "", id=1 }); 72 | /// 73 | /// 74 | /// sql语句 75 | /// 参数 76 | /// 77 | /// 78 | Task ExecuteAsync(string sql, object param, IDbContextTransaction trans = null); 79 | 80 | /// 81 | /// 开启事务 82 | /// 83 | /// 84 | IDbContextTransaction BeginTransaction(); 85 | public IDbContextTransaction BeginTransaction(IsolationLevel isolationLevel); 86 | public Task BeginTransactionAsync(CancellationToken cancellationToken = default); 87 | public Task BeginTransactionAsync(IsolationLevel isolationLevel, CancellationToken cancellationToken = default); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Wei.Repository/Impl/DbContextBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using System; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace Wei.Repository 8 | { 9 | public class BaseDbContext : DbContext 10 | { 11 | 12 | public BaseDbContext(DbContextOptions options) : base(options) 13 | { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Wei.Repository/Impl/Page.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Wei.Repository 6 | { 7 | /// 8 | /// 分页列表 9 | /// 10 | public class Page 11 | { 12 | public Page() 13 | { 14 | 15 | } 16 | public Page(List items, int pageIndex, int pageSize, int totalCount) 17 | { 18 | PageIndex = pageIndex; 19 | PageSize = pageSize; 20 | Total = totalCount; 21 | PageTotal = (int)Math.Ceiling(totalCount / (double)pageSize); 22 | Items = items; 23 | } 24 | 25 | /// 26 | /// 当前页码 27 | /// 28 | public int PageIndex { get; set; } 29 | 30 | /// 31 | /// 每页记录数 32 | /// 33 | public int PageSize { get; set; } 34 | 35 | /// 36 | /// 总记录数 37 | /// 38 | public int Total { get; set; } 39 | 40 | /// 41 | /// 总页数 42 | /// 43 | public int PageTotal { get; set; } 44 | 45 | /// 46 | /// 分页数据 47 | /// 48 | public List Items { get; set; } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Wei.Repository/Impl/Repository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Wei.Repository 11 | { 12 | public class Repository 13 | : RepositoryBase, IRepository 14 | where TEntity : class 15 | { 16 | internal override DbContext DbContext { get; set; } 17 | public Repository(DbContextFactory dbContextFactory) 18 | { 19 | DbContext = dbContextFactory.GetFirstOrDefaultDbContext(); 20 | } 21 | 22 | } 23 | 24 | public class Repository 25 | : RepositoryBase, IRepository 26 | where TEntity : class 27 | where TDbContext : DbContext 28 | { 29 | internal override DbContext DbContext { get; set; } 30 | 31 | public Repository(DbContextFactory dbContextFactory) 32 | { 33 | DbContext = dbContextFactory.GetDbContext(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Wei.Repository/Impl/RepositoryBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Wei.Repository 11 | { 12 | public abstract class RepositoryBase : IRepositoryBase 13 | where TEntity : class 14 | { 15 | internal abstract DbContext DbContext { get; set; } 16 | internal DbSet Table => DbContext.Set(); 17 | 18 | public IQueryable Query() => Table.AsQueryable(); 19 | public IQueryable QueryNoTracking() => Table.AsQueryable().AsNoTracking(); 20 | public virtual TEntity Get(params object[] id) => Table.Find(id); 21 | public virtual ValueTask GetAsync(params object[] id) => Table.FindAsync(id); 22 | public virtual ValueTask GetAsync(object[] ids, CancellationToken cancellationToken = default) => Table.FindAsync(ids, cancellationToken: cancellationToken); 23 | public virtual IQueryable Query(Expression> predicate) => Query().Where(predicate); 24 | public virtual IQueryable QueryNoTracking(Expression> predicate) => QueryNoTracking().Where(predicate); 25 | public virtual IEnumerable GetAll() => Query().ToList(); 26 | public virtual async Task> GetAllAsync(CancellationToken cancellationToken = default) => await Query().ToListAsync(cancellationToken); 27 | public virtual IEnumerable GetAll(Expression> predicate) => Query().Where(predicate).ToList(); 28 | public virtual async Task> GetAllAsync(Expression> predicate, CancellationToken cancellationToken = default) => await Query().Where(predicate).ToListAsync(cancellationToken); 29 | public virtual TEntity FirstOrDefault() => Query().FirstOrDefault(); 30 | public virtual Task FirstOrDefaultAsync(CancellationToken cancellationToken = default) => Query().FirstOrDefaultAsync(cancellationToken); 31 | public virtual TEntity FirstOrDefault(Expression> predicate) => Query().FirstOrDefault(predicate); 32 | public virtual Task FirstOrDefaultAsync(Expression> predicate, CancellationToken cancellationToken = default) => Query().FirstOrDefaultAsync(predicate, cancellationToken); 33 | 34 | public virtual TEntity Insert(TEntity entity) => Table.Add(entity).Entity; 35 | public virtual async Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default) 36 | { 37 | var entityEntry = await Table.AddAsync(entity, cancellationToken); 38 | return entityEntry.Entity; 39 | } 40 | public virtual void Insert(IEnumerable entities) => Table.AddRange(entities); 41 | public virtual Task InsertAsync(IEnumerable entities, CancellationToken cancellationToken = default) => Table.AddRangeAsync(entities, cancellationToken); 42 | public virtual TEntity Update(TEntity entity) 43 | { 44 | AttachEntity(entity); 45 | DbContext.Update(entity); 46 | return entity; 47 | } 48 | public virtual void Update(IEnumerable entities) => DbContext.UpdateRange(entities); 49 | public virtual IEnumerable Update(Expression> predicate, Action updateAction) 50 | { 51 | var entities = Query(predicate).ToList(); 52 | entities?.ForEach(updateAction); 53 | return entities; 54 | } 55 | public virtual async Task> UpdateAsync(Expression> predicate, Action updateAction, CancellationToken cancellationToken = default) 56 | { 57 | var entities = await Query(predicate).ToListAsync(cancellationToken); 58 | entities?.ForEach(updateAction); 59 | return entities; 60 | } 61 | 62 | public virtual void Delete(TEntity entity) => Table.Remove(entity); 63 | public virtual void Delete(params object[] id) 64 | { 65 | var entity = Get(id); 66 | if (entity != null) 67 | Delete(entity); 68 | } 69 | public virtual void Delete(Expression> predicate) => Table.RemoveRange(Table.Where(predicate)); 70 | public virtual bool Any() => Query().Any(); 71 | public virtual Task AnyAsync(CancellationToken cancellationToken = default) => Query().AnyAsync(cancellationToken); 72 | public virtual bool Any(Expression> predicate) => Query().Any(predicate); 73 | public virtual Task AnyAsync(Expression> predicate, CancellationToken cancellationToken = default) => Query().AnyAsync(predicate, cancellationToken); 74 | public virtual int Count() => Query().Count(); 75 | public virtual Task CountAsync(CancellationToken cancellationToken = default) => Query().CountAsync(cancellationToken); 76 | public virtual int Count(Expression> predicate) => Query().Where(predicate).Count(); 77 | public virtual Task CountAsync(Expression> predicate, CancellationToken cancellationToken = default) => Query().CountAsync(predicate, cancellationToken); 78 | 79 | private void AttachEntity(TEntity entity) 80 | { 81 | var d = DbContext.ChangeTracker.Entries().ToList(); 82 | var entry = DbContext.ChangeTracker.Entries().FirstOrDefault(ent => ent.Entity == entity); 83 | if (entry == null) DbContext.Attach(entity); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Wei.Repository/Impl/UnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Storage; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Data; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Wei.Repository 11 | { 12 | public class UnitOfWork : UnitOfWorkBase, IUnitOfWork 13 | { 14 | public UnitOfWork(DbContextFactory dbContextFactory) 15 | { 16 | DbContext = dbContextFactory.GetFirstOrDefaultDbContext(); 17 | } 18 | 19 | public override DbContext DbContext { get; } 20 | } 21 | 22 | public class UnitOfWork : UnitOfWorkBase, IUnitOfWork where TDbContext : DbContext 23 | { 24 | public UnitOfWork(DbContextFactory dbContextFactory) 25 | { 26 | DbContext = dbContextFactory.GetDbContext(); 27 | } 28 | 29 | public override DbContext DbContext { get; } 30 | 31 | } 32 | 33 | public abstract class UnitOfWorkBase : IUnitOfWork 34 | { 35 | private bool _disposed = false; 36 | 37 | public abstract DbContext DbContext { get; } 38 | 39 | public int SaveChanges() 40 | { 41 | return DbContext.SaveChanges(); 42 | } 43 | 44 | public Task SaveChangesAsync(CancellationToken cancellationToken = default) 45 | { 46 | return DbContext.SaveChangesAsync(cancellationToken); 47 | } 48 | 49 | public Task> QueryAsync(string sql, object param = null, IDbContextTransaction trans = null) where TEntity : class 50 | { 51 | var conn = GetConnection(); 52 | var result = conn.QueryAsync(sql, param, trans?.GetDbTransaction()); 53 | return result; 54 | } 55 | 56 | public async Task ExecuteAsync(string sql, object param, IDbContextTransaction trans = null) 57 | { 58 | var conn = GetConnection(); 59 | return await conn.ExecuteAsync(sql, param, trans?.GetDbTransaction()); 60 | } 61 | public IDbContextTransaction BeginTransaction() => DbContext.Database.BeginTransaction(); 62 | public IDbContextTransaction BeginTransaction(IsolationLevel isolationLevel) => DbContext.Database.BeginTransaction(isolationLevel); 63 | public Task BeginTransactionAsync(CancellationToken cancellationToken = default) => DbContext.Database.BeginTransactionAsync(cancellationToken); 64 | public Task BeginTransactionAsync(IsolationLevel isolationLevel, CancellationToken cancellationToken = default) => DbContext.Database.BeginTransactionAsync(isolationLevel, cancellationToken); 65 | 66 | public void Dispose() 67 | { 68 | Dispose(true); 69 | GC.SuppressFinalize(this); 70 | } 71 | 72 | protected virtual void Dispose(bool disposing) 73 | { 74 | if (!_disposed) 75 | { 76 | if (disposing) 77 | { 78 | DbContext.Dispose(); 79 | } 80 | } 81 | _disposed = true; 82 | } 83 | 84 | public IDbConnection GetConnection() => DbContext.Database.GetDbConnection(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Wei.Repository/ServiceAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection.Extensions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Microsoft.Extensions.DependencyInjection 9 | { 10 | public abstract class ServiceAttribute : Attribute 11 | { 12 | public ServiceAttribute(ServiceLifetime lifetime = ServiceLifetime.Scoped) => Lifetime = lifetime; 13 | 14 | 15 | public ServiceAttribute(Type serviceType, ServiceLifetime lifetime) 16 | { 17 | Lifetime = lifetime; 18 | ServiceType = serviceType; 19 | } 20 | 21 | public ServiceLifetime Lifetime { get; } 22 | 23 | public Type ServiceType { get; } 24 | 25 | public void Configure(IServiceCollection services, Type type) 26 | { 27 | var interfaceType = ServiceType; 28 | if (interfaceType == null) 29 | { 30 | var interfaces = type.GetInterfaces(); 31 | interfaceType = interfaces.FirstOrDefault(x => x.Name == $"I{type.Name}") ?? interfaces.FirstOrDefault() ?? type; 32 | } 33 | 34 | services.TryAdd(new ServiceDescriptor(interfaceType, type, Lifetime)); 35 | } 36 | } 37 | 38 | 39 | 40 | [AttributeUsage(AttributeTargets.Class)] 41 | public class SingletonServiceAttribute : ServiceAttribute 42 | { 43 | public SingletonServiceAttribute() : base(ServiceLifetime.Singleton) 44 | { 45 | 46 | } 47 | 48 | 49 | public SingletonServiceAttribute(Type serviceType) : base(serviceType, ServiceLifetime.Singleton) 50 | { 51 | 52 | 53 | } 54 | } 55 | 56 | [AttributeUsage(AttributeTargets.Class)] 57 | public class ScopedServiceAttribute : ServiceAttribute 58 | { 59 | public ScopedServiceAttribute() : base(ServiceLifetime.Scoped) 60 | { 61 | 62 | } 63 | 64 | 65 | public ScopedServiceAttribute(Type serviceType) : base(serviceType, ServiceLifetime.Scoped) 66 | { 67 | 68 | 69 | } 70 | } 71 | 72 | [AttributeUsage(AttributeTargets.Class)] 73 | public class TransientServiceAttribute : ServiceAttribute 74 | { 75 | public TransientServiceAttribute() : base(ServiceLifetime.Transient) 76 | { 77 | 78 | } 79 | 80 | 81 | public TransientServiceAttribute(Type serviceType) : base(serviceType, ServiceLifetime.Transient) 82 | { 83 | 84 | 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Wei.Repository/Wei.Repository.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | true 6 | EFCore+Dapper Repository 7 | 基于EFCore+Dapper 封装Repository,实现UnitOfWork,提供基本的CURD操作,支持多数据 8 | 基于EFCore+Dapper 封装Repository,实现UnitOfWork,提供基本的CURD操作,支持多数据 9 | false 10 | zh 11 | https://github.com/a34546/Wei.Repository.git 12 | 2.0.0.2 13 | 2.0.0.2 14 | GitHub 15 | https://github.com/a34546/Wei.Repository.git 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | --------------------------------------------------------------------------------