├── .gitignore ├── LICENSE ├── README.md └── src ├── iQuarc.DataAccess.AppBoot ├── DataAccessConfigurations.cs ├── Properties │ └── AssemblyInfo.cs ├── app.config ├── iQuarc.DataAccess.AppBoot.csproj ├── iQuarc.DataAccess.nuspec ├── make-package.bat └── packages.config ├── iQuarc.DataAccess.IntegrationTests ├── DataAccessIntegrationTests.cs ├── Properties │ └── AssemblyInfo.cs ├── app.config ├── iQuarc.DataAccess.IntegrationTests.csproj └── packages.config ├── iQuarc.DataAccess.UnitTests ├── App.config ├── ExceptionHandlingTests │ ├── AssertEx.cs │ ├── ConcurrencyExceptionHandlerTests.cs │ ├── DataValidationExceptionTests.cs │ ├── DbEntityValidationExceptionHandlerTests.cs │ ├── ExceptionHandlerTests.cs │ ├── ExceptionHandlersBaseTests.cs │ └── UpdateExceptionHandlerTests.cs ├── Properties │ └── AssemblyInfo.cs ├── RepositoryBaseTests.cs ├── RepositoryTests.cs ├── TestDoubles │ ├── ContextUtilitiesDouble.cs │ ├── DbContextFakeWrapper.cs │ ├── DbUtilities.cs │ ├── EntityEntryDouble.cs │ ├── EntryExtensions.cs │ ├── FakeExceptionHandler.cs │ ├── FakeSet.cs │ └── InterceptorDouble.cs ├── UnitOfWork.AddTests.cs ├── UnitOfWork.DeleteTests.cs ├── UnitOfWork.RepositoryTests.cs ├── UnitOfWork.SaveTests.cs ├── iQuarc.DataAccess.UnitTests.csproj └── packages.config ├── iQuarc.DataAccess.sln └── iQuarc.DataAccess ├── App.config ├── CollectionExtensions.cs ├── DbContextBuilder.cs ├── DbContextFactory.cs ├── DbContextWrapper.cs ├── EntityEntry.cs ├── EntityEntryState.cs ├── ExceptionHandling ├── ConcurrencyExceptionHandler.cs ├── DbEntityValidationExceptionHandler.cs ├── DefaultExceptionHandler.cs ├── ExceptionHandler.cs ├── IExceptionHandler.cs ├── SqlExceptionHandler.cs └── UpdateExceptionHandler.cs ├── Exceptions ├── ConcurrencyRepositoryViolationException.cs ├── DataValidationException.cs ├── DateTimeRangeRepositoryViolationException.cs ├── DeadlockVictimRepositoryViolationException.cs ├── DeleteConstraintRepositoryViolationException.cs ├── RepositoryUpdateException.cs ├── RepositoryViolationException.cs └── UniqueConstraintRepositoryViolationException.cs ├── GlobalEntityInterceptor.cs ├── IDbContextFactory.cs ├── IDbContextUtilities.cs ├── IDbContextWrapper.cs ├── IEntityEntry.cs ├── IEntityInterceptor.cs ├── IInterceptorsResolver.cs ├── IRepository.cs ├── IUnitOfWork.cs ├── IUnitOfWorkFactory.cs ├── InterceptorsResolver.cs ├── Properties └── AssemblyInfo.cs ├── Repository.cs ├── SimplifiedIsolationLevel.cs ├── UnitOfWork.cs ├── UnitOfWorkFactory.cs ├── iQuarc.DataAccess.csproj ├── iQuarc.DataAccess.nuspec ├── make-package.bat └── packages.config /.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 | *.sln.docstates 8 | *.sln.DotSettings 9 | *.vs 10 | 11 | # Build results 12 | [Dd]ebug/ 13 | [Dd]ebugPublic/ 14 | [Rr]elease/ 15 | [Rr]eleases/ 16 | x64/ 17 | x86/ 18 | build/ 19 | bld/ 20 | [Bb]in/ 21 | [Oo]bj/ 22 | 23 | # Roslyn cache directories 24 | *.ide/ 25 | 26 | # MSTest test Results 27 | [Tt]est[Rr]esult*/ 28 | [Bb]uild[Ll]og.* 29 | 30 | #NUNIT 31 | *.VisualState.xml 32 | TestResult.xml 33 | 34 | # Build Results of an ATL Project 35 | [Dd]ebugPS/ 36 | [Rr]eleasePS/ 37 | dlldata.c 38 | 39 | *_i.c 40 | *_p.c 41 | *_i.h 42 | *.ilk 43 | *.meta 44 | *.obj 45 | *.pch 46 | *.pdb 47 | *.pgc 48 | *.pgd 49 | *.rsp 50 | *.sbr 51 | *.tlb 52 | *.tli 53 | *.tlh 54 | *.tmp 55 | *.tmp_proj 56 | *.log 57 | *.vspscc 58 | *.vssscc 59 | .builds 60 | *.pidb 61 | *.svclog 62 | *.scc 63 | 64 | # Chutzpah Test files 65 | _Chutzpah* 66 | 67 | # Visual C++ cache files 68 | ipch/ 69 | *.aps 70 | *.ncb 71 | *.opensdf 72 | *.sdf 73 | *.cachefile 74 | 75 | # Visual Studio profiler 76 | *.psess 77 | *.vsp 78 | *.vspx 79 | 80 | # TFS 2012 Local Workspace 81 | $tf/ 82 | 83 | # Guidance Automation Toolkit 84 | *.gpState 85 | 86 | # ReSharper is a .NET coding add-in 87 | _ReSharper*/ 88 | *.[Rr]e[Ss]harper 89 | *.DotSettings.user 90 | 91 | # JustCode is a .NET coding addin-in 92 | .JustCode 93 | 94 | # TeamCity is a build add-in 95 | _TeamCity* 96 | 97 | # DotCover is a Code Coverage Tool 98 | *.dotCover 99 | 100 | # NCrunch 101 | _NCrunch_* 102 | .*crunch*.local.xml 103 | 104 | # MightyMoose 105 | *.mm.* 106 | AutoTest.Net/ 107 | 108 | # Web workbench (sass) 109 | .sass-cache/ 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.[Pp]ublish.xml 129 | *.azurePubxml 130 | # TODO: Comment the next line if you want to checkin your web deploy settings 131 | # but database connection strings (with potential passwords) will be unencrypted 132 | *.pubxml 133 | *.publishproj 134 | 135 | # NuGet Packages 136 | *.nupkg 137 | # NuGet folder of Package Restor 138 | .nuget 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | sql/ 155 | *.Cache 156 | ClientBin/ 157 | [Ss]tyle[Cc]op.* 158 | ~$* 159 | *~ 160 | *.dbmdl 161 | *.dbproj.schemaview 162 | *.pfx 163 | *.publishsettings 164 | node_modules/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 iQuarc 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DataAccess 2 | ========== 3 | Repository and Unit Of Work implementation over a relational database. 4 | 5 | Overview 6 | ----------- 7 | This library provides an abstraction and implementation of data access to a relational database. It targets two main goals: 8 | 9 | - support for consistent development model for reading or modifying data in the entire application 10 | - enforce good separation of concerns by separating data access from business logic 11 | 12 | The implementation is done with Entity Framework, but the client code should not have any direct dependencies (reference assemblies) to it. The client code depends only on the abstract interface provided by the library and does not know details about the underlying ORM. 13 | 14 | Usage Patterns 15 | ------------------ 16 | **`IRepository`** is a generic interface for querying data for read-only purposes: 17 | ```csharp 18 | private readonly IRepository rep; // injected w/ Dependency Injection 19 | public IEnumerable GetAllLargeOrders(int amount) 20 | { 21 | var orders = rep.GetEntities() 22 | .Where(o => o.OrderLines.Any(ol => ol.Amount > amount) 23 | return orders.ToList(); 24 | } 25 | ``` 26 | Queries may be returned to be composed and executed by caller code: 27 | ```csharp 28 | private readonly IRepository rep; // injected w/ Dependency Injection 29 | private IQueriable GetAllLargeOrders(int amount) 30 | { 31 | var orders = rep.GetEntities() 32 | .Where(o => o.OrderLines.Any(ol => ol.Amount > amount) 33 | return orders; 34 | } 35 | 36 | public IEnumerable GetRecentLargeOrders(int amount) 37 | { 38 | int thisYear = DateTime.UtcNow.Year; 39 | var orders = GetAllLargeOrders(amount) 40 | .Where(o.Year == thisYear) 41 | .Select(o => new OrderSummary 42 | { 43 | ... 44 | }); 45 | return orders; 46 | } 47 | ``` 48 | 49 | **`IUnitOfWork`** is a generic interface for creating a well defined scope for adding, modifying or deleting data: 50 | ```csharp 51 | public void ReviewLargeAmountOrders(int amount, ReviewData data) 52 | { 53 | using (IUnitOfWork uof = rep.CreateUnitOfWork()) 54 | { 55 | IQueryable orders = uof.GetEntities() 56 | .Where(o => o.OrderLines.Any(ol => ol.Ammount > amount); 57 | foreach(var order in orders) 58 | { 59 | order.Status = Status.Reviewed; 60 | order.Cutomer.Name = data.CustomerNameUpdate; 61 | ... 62 | } 63 | 64 | ReviewEvent re = new ReviewEvent {...} 65 | uof.Add(re) 66 | 67 | uof.SaveCanges(); 68 | } 69 | } 70 | ``` 71 | 72 | Getting Started 73 | ----------------- 74 | Create a data model project which contains the POCOs that are mapped to the database model. This will also contain the Entity Framework `DbContext`. 75 | 76 | Register into your Dependency Injection container the following mappings: 77 | 78 | Contract | Implementation 79 | -------- | ------------- 80 | `IDbContextFactory` | `DbContextFactory` 81 | `IRepository` | `Repository` 82 | `IInterceptorsResolver` | `InterceptorsResolver` 83 | 84 | Related Repository 85 | ------------------------- 86 | [iQuarc.AppBoot](https://github.com/iQuarc/AppBoot) 87 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.AppBoot/DataAccessConfigurations.cs: -------------------------------------------------------------------------------- 1 | using iQuarc.AppBoot; 2 | 3 | namespace iQuarc.DataAccess.AppBoot 4 | { 5 | public static class DataAccessConfigurations 6 | { 7 | public static ConventionRegistrationBehavior DefaultRegistrationConventions 8 | { 9 | get { return GetDefaultConventions(); } 10 | } 11 | 12 | private static ConventionRegistrationBehavior GetDefaultConventions() 13 | { 14 | var conventions = new ConventionRegistrationBehavior(); 15 | 16 | conventions.ForType().Export(b => b.AsContractType()); 17 | conventions.ForType().Export(b => b.AsContractType().WithLifetime(Lifetime.Application)); 18 | 19 | return conventions; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.AppBoot/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | 8 | [assembly: AssemblyTitle("iQuarc.DataAccess.AppBoot")] 9 | [assembly:AssemblyDescription( 10 | "This is a configuration package that links the DataAccess with AppBoot. The DataAccess provides an abstraction over a relational DB. The implementation is done with EF, which is well hiden from the clients.")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("iQuarc")] 13 | [assembly: AssemblyProduct("iQuarc.DataAccess.AppBoot")] 14 | [assembly: AssemblyCopyright("Copyright © 2015")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | 22 | [assembly: ComVisible(false)] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | 26 | [assembly: Guid("a23dd31d-7b0d-454c-ba2c-e256dd173ad8")] 27 | 28 | // Versioning: 29 | // Adopting SemVer both for NuGet packages versions and also for Assembly Version (http://semver.org/ | https://docs.nuget.org/create/versioning). 30 | // We prefer nuspec to take the package version from assembly version. 31 | 32 | [assembly: AssemblyVersion(Version.Assembly)] 33 | [assembly: AssemblyFileVersion(Version.Assembly)] 34 | [assembly: AssemblyInformationalVersion(Version.NugetReleasePackage)] 35 | 36 | static class Version 37 | { 38 | /// 39 | /// Breaking changes. 40 | /// 41 | private const string Major = "1"; 42 | 43 | /// 44 | /// New features, but backwards compatible. 45 | /// 46 | private const string Minor = "0"; 47 | 48 | /// 49 | /// Backwards compatible bug fixes only. 50 | /// 51 | private const string Patch = "1"; 52 | 53 | /// 54 | /// Build number. Prefix with 0 for NuGet version ranges 55 | /// 56 | private const string Build = "0"; 57 | 58 | /// 59 | /// NuGet Pre-Release package versions 60 | /// 61 | private const string Prerelease = "beta"; 62 | 63 | /// 64 | /// Used to set the assembly version 65 | /// 66 | public const string Assembly = Major + "." + Minor + "." + Patch + "." + Build; 67 | 68 | /// 69 | /// Used to set the version of a release NuGet Package 70 | /// 71 | public const string NugetReleasePackage = Major + "." + Minor + "." + Patch; 72 | 73 | /// 74 | /// Used to set the version of a pre-release NuGet Package 75 | /// 76 | public const string NugetPrereleasePackage = Major + "." + Minor + "." + Patch + "-" + Prerelease + Build; 77 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.AppBoot/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.AppBoot/iQuarc.DataAccess.AppBoot.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {5C839D26-2F63-4E00-A907-DD535A9846DF} 8 | Library 9 | Properties 10 | iQuarc.DataAccess.AppBoot 11 | iQuarc.DataAccess.AppBoot 12 | v4.5 13 | 512 14 | ..\ 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll 36 | True 37 | 38 | 39 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll 40 | True 41 | 42 | 43 | ..\packages\iQuarc.AppBoot.2.0.2\lib\net45\iQuarc.AppBoot.dll 44 | True 45 | 46 | 47 | ..\packages\iQuarc.DataAccess.1.0.4\lib\net45\iQuarc.DataAccess.dll 48 | True 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 71 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.AppBoot/iQuarc.DataAccess.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | false 10 | $description$ 11 | $description$ 12 | Basic functionality for hiding EF and for creating an Unit of Work 13 | Copyright 2014 14 | DataAccess Repository UnitOfWork EntityFramework ORM 15 | https://raw.githubusercontent.com/iQuarc/DataAccess/master/LICENSE 16 | https://github.com/iQuarc/DataAccess 17 | http://iquarc.com/images/iquarc-icon-BW-32x32.png 18 | 19 | Version 1.0.3 20 | - Added GetEntityEntry method to IUnitOfWork to allow getting IEntityEntry for any entity 21 | - 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.AppBoot/make-package.bat: -------------------------------------------------------------------------------- 1 | c:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe iQuarc.DataAccess.AppBoot.csproj /p:Configuration=Release 2 | nuget pack iQuarc.DataAccess.AppBoot.csproj -Prop Configuration=Release -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.AppBoot/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.IntegrationTests/DataAccessIntegrationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Entity; 4 | using System.IO; 5 | using System.Reflection; 6 | using System.Threading.Tasks; 7 | using iQuarc.AppBoot; 8 | using iQuarc.AppBoot.Unity; 9 | using iQuarc.DataAccess.AppBoot; 10 | using Microsoft.VisualStudio.TestTools.UnitTesting; 11 | 12 | namespace iQuarc.DataAccess.IntegrationTests 13 | { 14 | [TestClass] 15 | public class DataAccessIntegrationTests 16 | { 17 | private Bootstrapper bootstrapper; 18 | 19 | [TestInitialize] 20 | public void Init() 21 | { 22 | IEnumerable assemblies = GetAssemblies(); 23 | 24 | bootstrapper = new Bootstrapper(assemblies); 25 | bootstrapper.ConfigureWithUnity(); 26 | bootstrapper.AddRegistrationBehavior(new ServiceRegistrationBehavior()); 27 | bootstrapper.AddRegistrationBehavior(DataAccessConfigurations.DefaultRegistrationConventions); 28 | 29 | bootstrapper.Run(); 30 | } 31 | 32 | [TestMethod] 33 | public void TestContainerIntegration() 34 | { 35 | var repository = bootstrapper.ServiceLocator.GetInstance(); 36 | Assert.IsNotNull(repository); 37 | } 38 | 39 | private IEnumerable GetAssemblies() 40 | { 41 | string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 42 | 43 | foreach (string dll in Directory.GetFiles(path, "*.dll")) 44 | { 45 | string filename = Path.GetFileName(dll); 46 | if (filename != null && (filename.StartsWith("iQuarc.DataAccess"))) 47 | { 48 | Assembly assembly = Assembly.LoadFile(dll); 49 | yield return assembly; 50 | } 51 | } 52 | } 53 | } 54 | 55 | 56 | [Service(typeof(IDbContextFactory))] 57 | public class DbContextFactory : DbContextFactory 58 | { 59 | } 60 | 61 | public class DummyContext : DbContext 62 | { 63 | 64 | } 65 | 66 | [Service(typeof(IEntityInterceptor))] 67 | public class DummyInterceptor : IEntityInterceptor 68 | { 69 | public void OnLoad(IEntityEntry entry, IRepository repository) 70 | { 71 | 72 | } 73 | 74 | public void OnSave(IEntityEntry entry, IUnitOfWork unitOfWork) 75 | { 76 | 77 | } 78 | 79 | public void OnDelete(IEntityEntry entry, IUnitOfWork unitOfWork) 80 | { 81 | 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.IntegrationTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("iQuarc.DataAccess.IntegrationTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("iQuarc.DataAccess.IntegrationTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("74216d64-cdd7-4a47-81cb-a9b7407d721f")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.IntegrationTests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.IntegrationTests/iQuarc.DataAccess.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {74216D64-CDD7-4A47-81CB-A9B7407D721F} 7 | Library 8 | Properties 9 | iQuarc.DataAccess.IntegrationTests 10 | iQuarc.DataAccess.IntegrationTests 11 | v4.6 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll 40 | True 41 | 42 | 43 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll 44 | True 45 | 46 | 47 | ..\packages\iQuarc.AppBoot.2.0.2\lib\net45\iQuarc.AppBoot.dll 48 | True 49 | 50 | 51 | ..\packages\iQuarc.AppBoot.Unity.2.0.2\lib\net45\iQuarc.AppBoot.Unity.dll 52 | True 53 | 54 | 55 | ..\packages\iQuarc.SystemEx.1.0.0.0\lib\net40\iQuarc.SystemEx.dll 56 | True 57 | 58 | 59 | ..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll 60 | True 61 | 62 | 63 | ..\packages\Unity.3.5.1404.0\lib\net45\Microsoft.Practices.Unity.dll 64 | True 65 | 66 | 67 | ..\packages\Unity.3.5.1404.0\lib\net45\Microsoft.Practices.Unity.Configuration.dll 68 | True 69 | 70 | 71 | ..\packages\Unity.3.5.1404.0\lib\net45\Microsoft.Practices.Unity.RegistrationByConvention.dll 72 | True 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 | {5C839D26-2F63-4E00-A907-DD535A9846DF} 100 | iQuarc.DataAccess.AppBoot 101 | 102 | 103 | {53FBDBE7-3B53-4FC1-9676-1932C6CE6A2F} 104 | iQuarc.DataAccess 105 | 106 | 107 | 108 | 109 | 110 | 111 | False 112 | 113 | 114 | False 115 | 116 | 117 | False 118 | 119 | 120 | False 121 | 122 | 123 | 124 | 125 | 126 | 127 | 134 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.IntegrationTests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/ExceptionHandlingTests/AssertEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace iQuarc.DataAccess.UnitTests 8 | { 9 | // TODO move this to a package with extensions to MS Tests 10 | public static class AssertEx 11 | { 12 | /// 13 | /// Assertion that fails if the specified Action does not throw any exception. 14 | /// 15 | public static void ShouldThrow(this Action act) 16 | { 17 | ShouldThrow(act); 18 | } 19 | 20 | /// 21 | /// Assertion that fails if the specified Action does not throw the specified type of exception (TException). 22 | /// If other types of exceptions are thrown or no exception is thrown, the assert succeeds. 23 | /// 24 | public static void ShouldThrow(this Action act) 25 | where TException : Exception 26 | { 27 | string message = String.Empty; 28 | ShouldThrow(act, message); 29 | } 30 | 31 | public static void ShouldThrow(this Action act, string message) 32 | where TException : Exception 33 | { 34 | ShouldThrow(act, assert => true, message); 35 | } 36 | 37 | public static void ShouldThrow(this Action act, Func assert, string message = null) 38 | where TException : Exception 39 | { 40 | try 41 | { 42 | act(); 43 | } 44 | catch (TException expected) 45 | { 46 | Assert.IsTrue(assert(expected), message); 47 | return; 48 | } 49 | catch (Exception ex) 50 | { 51 | if (string.IsNullOrEmpty(message)) 52 | message = "Exception was thrown but it was not of the expected type: " + ex.GetType().Name + " - " + ex.Message; 53 | Assert.Fail(message); 54 | } 55 | 56 | if (string.IsNullOrEmpty(message)) 57 | message = "No exception was thrown."; 58 | Assert.Fail(message); 59 | } 60 | 61 | /// 62 | /// Assertion fails if any type of exception is thrown 63 | /// 64 | public static void ShouldNotThrow(this Action act) 65 | { 66 | Exception exception = TryExec(act); 67 | if (exception != null) 68 | Assert.IsTrue(false, string.Format("Exception of type {0} was thrown when it shouldn't have been.", exception.GetType())); 69 | } 70 | 71 | /// 72 | /// Assertion that fails if the specified Action throws the specified type of exception (TException). 73 | /// If other types of exceptions are thrown or no exception is thrown, the assert succeeds. 74 | /// 75 | public static void ShouldNotThrow(this Action act) 76 | where TException : Exception 77 | { 78 | string message = "Exception of type " + typeof (TException) + " was thrown when it shouldn't have been."; 79 | ShouldNotThrow(act, message); 80 | } 81 | 82 | /// 83 | /// Assertion that fails with the specified message if the specified Action throws the specified type of exception 84 | /// (TException) or a derived type. 85 | /// If other types of exceptions are thrown or no exception is thrown, the assert succeeds. 86 | /// 87 | public static void ShouldNotThrow(this Action act, string message) 88 | where TException : Exception 89 | { 90 | if (TryExec(act) is TException) 91 | Assert.Fail(message); 92 | } 93 | 94 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] 95 | private static Exception TryExec(Action act) 96 | { 97 | try 98 | { 99 | act(); 100 | return null; 101 | } 102 | catch (Exception ex) 103 | { 104 | return ex; 105 | } 106 | } 107 | 108 | /// 109 | /// Verifies that the specified collections are equivalent. 110 | /// Two collections are equivalent if they have the same elements in the same quantity, but in any order. 111 | /// Elements are equal if their values are equal, not if they refer to the same object. 112 | /// 113 | public static void AreEquivalent(IEnumerable actual, params T[] expected) 114 | { 115 | Func equality = EqualityComparer.Default.Equals; 116 | AreEquivalent(actual, equality, expected); 117 | } 118 | 119 | /// 120 | /// Verifies that the specified collections are equivalent. 121 | /// Two collections are equivalent if they have the same elements in the same quantity, but in any order. 122 | /// 123 | public static void AreEquivalent(IEnumerable actual, Func equality, params T[] expected) 124 | { 125 | Assert.IsTrue(actual.Count() == expected.Length, 126 | string.Format("Collections do not have same number of elements. Expected: {0}; Actual: {1}", expected.Length, actual.Count())); 127 | 128 | if (expected.Length == 0) 129 | return; 130 | 131 | 132 | int expectedCount; 133 | int actualCount; 134 | T mismatchedElement; 135 | bool isMismatch = FindMismatchedElement(actual, expected, equality, out expectedCount, out actualCount, out mismatchedElement); 136 | 137 | Assert.IsFalse(isMismatch, 138 | string.Format("Collections are not equivalent. Mismatch element {0}. Expected count: {1}; Actual: {2}", 139 | mismatchedElement, expectedCount, actualCount)); 140 | } 141 | 142 | private static bool FindMismatchedElement(IEnumerable actual, T[] expected, Func equality, out int expectedCount, out int actualCount, 143 | out T mismatchedElement) 144 | { 145 | List> expectedElementCounts = GetElementCounts(expected, equality); 146 | List> actualElementCounts = GetElementCounts(actual, equality); 147 | foreach (ElementCount expectedElement in expectedElementCounts) 148 | { 149 | ElementCount actualElement = actualElementCounts.Find(e => equality(e.Element, expectedElement.Element)); 150 | 151 | if (actualElement == null) 152 | { 153 | expectedCount = expectedElement.Count; 154 | actualCount = 0; 155 | mismatchedElement = expectedElement.Element; 156 | return true; 157 | } 158 | 159 | if (expectedElement.Count != actualElement.Count) 160 | { 161 | expectedCount = expectedElement.Count; 162 | actualCount = actualElement.Count; 163 | mismatchedElement = expectedElement.Element; 164 | return true; 165 | } 166 | } 167 | 168 | expectedCount = 0; 169 | actualCount = 0; 170 | mismatchedElement = default(T); 171 | return false; 172 | } 173 | 174 | private static List> GetElementCounts(IEnumerable collection, Func equality) 175 | { 176 | List> elementCounts = new List>(); 177 | foreach (T element in collection) 178 | { 179 | ElementCount count = elementCounts.Find(p => equality(p.Element, element)); 180 | if (count == null) 181 | elementCounts.Add(new ElementCount(element)); 182 | else 183 | count.Increment(); 184 | } 185 | return elementCounts; 186 | } 187 | 188 | private class ElementCount 189 | { 190 | public ElementCount(T element) 191 | { 192 | Element = element; 193 | Count = 1; 194 | } 195 | 196 | public T Element { get; private set; } 197 | public int Count { get; private set; } 198 | 199 | public void Increment() 200 | { 201 | Count++; 202 | } 203 | } 204 | } 205 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/ExceptionHandlingTests/ConcurrencyExceptionHandlerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity.Core; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Moq; 5 | 6 | namespace iQuarc.DataAccess.UnitTests 7 | { 8 | [TestClass] 9 | public class ConcurrencyExceptionHandlerTests : ExceptionHandlersBaseTests 10 | { 11 | protected override IExceptionHandler GetTarget(Mock mock) 12 | { 13 | return new ConcurrencyExceptionHandler(mock.Object); 14 | } 15 | 16 | protected override Exception GetExpectedInnerException() 17 | { 18 | return new OptimisticConcurrencyException(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/ExceptionHandlingTests/DataValidationExceptionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.Serialization.Formatters.Binary; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace iQuarc.DataAccess.UnitTests 9 | { 10 | [TestClass] 11 | public class DataValidationExceptionTests 12 | { 13 | [TestMethod] 14 | public void Serialize_MoreEntitiesWithMoreErrors_DeserializedIsSame() 15 | { 16 | var errors1 = new[] 17 | { 18 | new ValidationError("UserName", "error1"), 19 | new ValidationError("UserEmail", "error2") 20 | }; 21 | var entityErrors1 = new DataValidationResult(new EntryDouble(new User(1)), errors1); 22 | var errors2 = new[] 23 | { 24 | new ValidationError("RoleName", "error3"), 25 | new ValidationError("AccessRights", "error4") 26 | }; 27 | var entityErrors2 = new DataValidationResult(new EntryDouble(new Role(2)), errors2); 28 | 29 | DataValidationException e = new DataValidationException(string.Empty, new[] {entityErrors1, entityErrors2}); 30 | 31 | using (Stream s = new MemoryStream()) 32 | { 33 | BinaryFormatter formatter = new BinaryFormatter(); 34 | formatter.Serialize(s, e); 35 | s.Position = 0; // Reset stream position 36 | e = (DataValidationException) formatter.Deserialize(s); 37 | } 38 | 39 | DataValidationResult[] actual = e.ValidationErrors.ToArray(); 40 | AssertAreEqual(entityErrors1, actual[0]); 41 | AssertAreEqual(entityErrors2, actual[1]); 42 | } 43 | 44 | private void AssertAreEqual(DataValidationResult expected, DataValidationResult actual) 45 | { 46 | Func equalityFunc = (e1, e2) => e1.ErrorMessage == e2.ErrorMessage && e1.PropertyName == e2.PropertyName; 47 | 48 | Assert.AreEqual(expected.Entry, actual.Entry); 49 | AssertEx.AreEquivalent(actual.Errors, equalityFunc, expected.Errors.ToArray()); 50 | } 51 | 52 | [Serializable] 53 | private class EntryDouble : IEntityEntry 54 | { 55 | public EntryDouble(object entity) 56 | { 57 | Entity = entity; 58 | } 59 | 60 | public object Entity { get; set; } 61 | 62 | public EntityEntryState State 63 | { 64 | get { throw new NotImplementedException(); } 65 | set { } 66 | } 67 | 68 | public object GetOriginalValue(string propertyName) 69 | { 70 | throw new NotImplementedException(); 71 | } 72 | 73 | public object GetCurrentValue(string propertyName) 74 | { 75 | throw new NotImplementedException(); 76 | } 77 | 78 | public IEntityEntry Convert() where T : class 79 | { 80 | throw new NotImplementedException(); 81 | } 82 | 83 | public void SetOriginalValue(string propertyName, object value) 84 | { 85 | throw new NotImplementedException(); 86 | } 87 | 88 | public void Reload() 89 | { 90 | throw new NotImplementedException(); 91 | } 92 | 93 | public IEnumerable GetProperties() 94 | { 95 | throw new NotImplementedException(); 96 | } 97 | 98 | public IPropertyEntry Property(string name) 99 | { 100 | throw new NotImplementedException(); 101 | } 102 | 103 | protected bool Equals(EntryDouble other) 104 | { 105 | return Equals(Entity, other.Entity); 106 | } 107 | 108 | public override bool Equals(object obj) 109 | { 110 | if (ReferenceEquals(null, obj)) return false; 111 | if (ReferenceEquals(this, obj)) return true; 112 | if (obj.GetType() != this.GetType()) return false; 113 | return Equals((EntryDouble) obj); 114 | } 115 | 116 | public override int GetHashCode() 117 | { 118 | return (Entity != null ? Entity.GetHashCode() : 0); 119 | } 120 | } 121 | 122 | [Serializable] 123 | private class User 124 | { 125 | public User(int id) 126 | { 127 | Id = id; 128 | } 129 | 130 | public int Id { get; set; } 131 | 132 | protected bool Equals(User other) 133 | { 134 | return Id == other.Id; 135 | } 136 | 137 | public override bool Equals(object obj) 138 | { 139 | if (ReferenceEquals(null, obj)) return false; 140 | if (ReferenceEquals(this, obj)) return true; 141 | if (obj.GetType() != this.GetType()) return false; 142 | return Equals((User) obj); 143 | } 144 | 145 | public override int GetHashCode() 146 | { 147 | return Id; 148 | } 149 | } 150 | 151 | [Serializable] 152 | private class Role 153 | { 154 | public Role(int id) 155 | { 156 | Id = id; 157 | } 158 | 159 | public int Id { get; set; } 160 | 161 | protected bool Equals(Role other) 162 | { 163 | return Id == other.Id; 164 | } 165 | 166 | public override bool Equals(object obj) 167 | { 168 | if (ReferenceEquals(null, obj)) return false; 169 | if (ReferenceEquals(this, obj)) return true; 170 | if (obj.GetType() != this.GetType()) return false; 171 | return Equals((Role) obj); 172 | } 173 | 174 | public override int GetHashCode() 175 | { 176 | return Id; 177 | } 178 | } 179 | } 180 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/ExceptionHandlingTests/DbEntityValidationExceptionHandlerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity.Validation; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Moq; 5 | 6 | namespace iQuarc.DataAccess.UnitTests 7 | { 8 | [TestClass] 9 | public class DbEntityValidationExceptionHandlerTests : ExceptionHandlersBaseTests 10 | { 11 | protected override IExceptionHandler GetTarget(Mock mock) 12 | { 13 | return new DbEntityValidationExceptionHandler(mock.Object); 14 | } 15 | 16 | protected override Exception GetExpectedInnerException() 17 | { 18 | return new DbEntityValidationException(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/ExceptionHandlingTests/ExceptionHandlerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace iQuarc.DataAccess.UnitTests 5 | { 6 | [TestClass] 7 | public class ExceptionHandlerTests 8 | { 9 | [TestMethod] 10 | public void Handle_ExceptionWhichNoSpecificHandlerCanHandle_RepositoryViolationExceptionThrown() 11 | { 12 | ExceptionHandler handler = new ExceptionHandler(); 13 | Exception e = new Exception(); 14 | 15 | Action act = () => handler.Handle(e); 16 | 17 | act.ShouldThrow(actual => ReferenceEquals(actual.InnerException, e)); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/ExceptionHandlingTests/ExceptionHandlersBaseTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Moq; 4 | 5 | namespace iQuarc.DataAccess.UnitTests 6 | { 7 | public abstract class ExceptionHandlersBaseTests 8 | where TException : Exception 9 | { 10 | [TestMethod] 11 | public void Handle_InnerIsConcurrencyException_ExceptionWrappedAndThrown() 12 | { 13 | Exception innerException = GetExpectedInnerException(); 14 | Exception ex = new Exception(string.Empty, innerException); 15 | 16 | IExceptionHandler target = GetTarget(); 17 | 18 | Action act = () => target.Handle(ex); 19 | 20 | act.ShouldThrow( 21 | e => ReferenceEquals(e.InnerException, innerException)); 22 | } 23 | 24 | 25 | [TestMethod] 26 | public void Handle_InnerIsNotExpectedException_SuccessorHandles() 27 | { 28 | Mock mock = new Mock(); 29 | IExceptionHandler target = GetTarget(mock); 30 | Exception ex = new Exception(string.Empty, new Exception()); 31 | 32 | target.Handle(ex); 33 | 34 | mock.Verify(h => h.Handle(ex), Times.Once); 35 | } 36 | 37 | private IExceptionHandler GetTarget() 38 | { 39 | return GetTarget(new Mock()); 40 | } 41 | 42 | protected abstract IExceptionHandler GetTarget(Mock mock); 43 | protected abstract Exception GetExpectedInnerException(); 44 | } 45 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/ExceptionHandlingTests/UpdateExceptionHandlerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity.Core; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Moq; 5 | 6 | namespace iQuarc.DataAccess.UnitTests 7 | { 8 | [TestClass] 9 | public class UpdateExceptionHandlerTests : ExceptionHandlersBaseTests 10 | { 11 | protected override IExceptionHandler GetTarget(Mock mock) 12 | { 13 | return new UpdateExceptionHandler(mock.Object); 14 | } 15 | 16 | protected override Exception GetExpectedInnerException() 17 | { 18 | return new UpdateException(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("iQuarc.DataAccess.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("iQuarc.DataAccess.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("d7ab5ef3-cabb-4db9-8150-e7f069cd6c36")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/RepositoryBaseTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Entity; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using iQuarc.DataAccess.UnitTests.TestDoubles; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | using Moq; 9 | 10 | namespace iQuarc.DataAccess.UnitTests 11 | { 12 | public abstract class RepositoryBaseTests 13 | { 14 | [TestMethod] 15 | public void GetEntities_FilterById_ReturnsOneRecord() 16 | { 17 | IInterceptorsResolver resolver = GetEmptyInterceptors(); 18 | IDbContextFactory factory = GetFactory(); 19 | 20 | IRepository repository = GetTarget(factory, resolver); 21 | 22 | IQueryable users = repository.GetEntities(); 23 | User actual = users.FirstOrDefault(x => x.Id == 2); 24 | 25 | User expected = new User {Id = 2}; 26 | Assert.AreEqual(expected, actual); 27 | } 28 | 29 | [TestMethod] 30 | public void Dispose_ContextUsed_ContextDisposed() 31 | { 32 | DbContextFakeWrapper contextFakeWrapper = CreateContextWithTestData(); 33 | IDbContextFactory factory = GetFactory(contextFakeWrapper); 34 | IInterceptorsResolver resolver = GetEmptyInterceptors(); 35 | 36 | IRepository repository = GetTarget(factory, resolver); 37 | 38 | var u = repository.GetEntities().First(); 39 | 40 | 41 | ((IDisposable) repository).Dispose(); 42 | 43 | Assert.IsTrue(contextFakeWrapper.WasDisposed); 44 | } 45 | 46 | [TestMethod] 47 | public void GetEntities_GlobalInterceptors_LoadedEntitiesIntercepted() 48 | { 49 | ParamTest__GetEntities_EntitiesExists_LoadedEntitiesIntercepted( 50 | r => r.GetGlobalInterceptors()); 51 | } 52 | 53 | private void ParamTest__GetEntities_EntitiesExists_LoadedEntitiesIntercepted( 54 | Expression>> getInterceptorsFunction) 55 | { 56 | InterceptorDouble interceptorMock = new InterceptorDouble(); 57 | IInterceptorsResolver resolverStub = GetResolver(getInterceptorsFunction, interceptorMock); 58 | 59 | DbContextFakeWrapper contextStub = CreateContextWithTestData(); 60 | IDbContextFactory factory = GetFactory(contextStub); 61 | 62 | IRepository rep = GetTarget(factory, resolverStub); 63 | User u = rep.GetEntities().First(); 64 | 65 | AssertInterceptedOnLoad(interceptorMock, u); 66 | } 67 | 68 | private void AssertInterceptedOnLoad(InterceptorDouble interceptorMock, User user) 69 | { 70 | interceptorMock.AssertIntercepted(i => i.InterceptedOnOnLoad, new[] {user}, u => u.Name); 71 | } 72 | 73 | protected abstract IRepository GetTarget(IDbContextFactory factory, IInterceptorsResolver resolver); 74 | 75 | private static IInterceptorsResolver GetResolver(Expression>> getInterceptorsFunction, 76 | InterceptorDouble interceptorMock) 77 | { 78 | Mock stub = new Mock(); 79 | stub.Setup(getInterceptorsFunction).Returns(new[] {interceptorMock}); 80 | return stub.Object; 81 | } 82 | 83 | private IDbContextFactory GetFactory(DbContextFakeWrapper contextStub) 84 | { 85 | Mock factoryStub = new Mock(); 86 | factoryStub.Setup(f => f.CreateContext()).Returns(contextStub); 87 | return factoryStub.Object; 88 | } 89 | 90 | private static IDbContextFactory GetFactory() 91 | { 92 | DbContextFakeWrapper fakeWrapper = CreateContextWithTestData(); 93 | return fakeWrapper.ContextDouble.BuildFactoryStub(); 94 | } 95 | 96 | private static IInterceptorsResolver GetEmptyInterceptors() 97 | { 98 | Mock resolver = new Mock(); 99 | resolver.Setup(x => x.GetEntityInterceptors(It.IsAny())) 100 | .Returns(new IEntityInterceptor[0]); 101 | resolver.Setup(x => x.GetGlobalInterceptors()) 102 | .Returns(new IEntityInterceptor[0]); 103 | return resolver.Object; 104 | } 105 | 106 | private static DbContextFakeWrapper CreateContextWithTestData() 107 | { 108 | Role roleAdmin = new Role 109 | { 110 | Id = 1, 111 | Name = "Admin", 112 | Users = new List 113 | { 114 | new User {Id = 1, Name = "John", RoleId = 1}, 115 | new User {Id = 2, Name = "Alex", RoleId = 1}, 116 | } 117 | }; 118 | 119 | Role roleUser = new Role 120 | { 121 | Id = 2, 122 | Name = "User", 123 | Users = new List 124 | { 125 | new User {Id = 3, Name = "Matt", RoleId = 1}, 126 | new User {Id = 4, Name = "Mike", RoleId = 1}, 127 | } 128 | }; 129 | 130 | List roles = new List {roleAdmin, roleUser}; 131 | foreach (Role role in roles) 132 | { 133 | foreach (User user in role.Users) 134 | user.Role = role; 135 | } 136 | 137 | List users = roles.SelectMany(r => r.Users).ToList(); 138 | 139 | DbContextFakeWrapper wrapper = new DbContextFakeWrapper(); 140 | 141 | DbSet userSet = users.MockDbSet(wrapper); 142 | DbSet roleSet = roles.MockDbSet(wrapper); 143 | 144 | wrapper.ContextDouble.Setup(x => x.Set()).Returns(() => userSet); 145 | wrapper.ContextDouble.Setup(x => x.Set()).Returns(() => roleSet); 146 | 147 | return wrapper; 148 | } 149 | 150 | private class User 151 | { 152 | public int Id { get; set; } 153 | 154 | public string Name { get; set; } 155 | 156 | public int RoleId { get; set; } 157 | 158 | public Role Role { get; set; } 159 | 160 | public override bool Equals(object obj) 161 | { 162 | if (ReferenceEquals(null, obj)) return false; 163 | if (ReferenceEquals(this, obj)) return true; 164 | if (obj.GetType() != this.GetType()) return false; 165 | return Equals((User) obj); 166 | } 167 | 168 | protected bool Equals(User other) 169 | { 170 | return Id == other.Id; 171 | } 172 | 173 | public override int GetHashCode() 174 | { 175 | return Id; 176 | } 177 | } 178 | 179 | private class Role 180 | { 181 | public int Id { get; set; } 182 | public string Name { get; set; } 183 | 184 | public IList Users { get; set; } 185 | } 186 | } 187 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/RepositoryTests.cs: -------------------------------------------------------------------------------- 1 | using iQuarc.DataAccess.UnitTests.TestDoubles; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace iQuarc.DataAccess.UnitTests 5 | { 6 | [TestClass] 7 | public class RepositoryTests : RepositoryBaseTests 8 | { 9 | protected override IRepository GetTarget(IDbContextFactory factory, IInterceptorsResolver resolver) 10 | { 11 | return new Repository(factory, resolver, new ContextUtilitiesDouble()); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/TestDoubles/ContextUtilitiesDouble.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Entity; 4 | using System.Linq; 5 | using Moq; 6 | 7 | namespace iQuarc.DataAccess.UnitTests.TestDoubles 8 | { 9 | class ContextUtilitiesDouble : IDbContextUtilities 10 | { 11 | private readonly Dictionary> changedAtCall = new Dictionary>(); 12 | private int getChangedEntitiesCount = -1; 13 | 14 | public ContextUtilitiesDouble() 15 | : this(Enumerable.Empty()) 16 | { 17 | } 18 | 19 | public ContextUtilitiesDouble(IEnumerable changedEntities, EntityEntryState state = EntityEntryState.Modified) 20 | { 21 | changedAtCall[0] = changedEntities.Select(e => new EntityEntryDouble(e, state)); 22 | } 23 | 24 | public void AddEntitiesByCallNumber(int callCount, IEnumerable entities, EntityEntryState state = EntityEntryState.Modified) 25 | { 26 | changedAtCall[callCount - 1] = entities.Select(e => new EntityEntryDouble(e, state)); 27 | } 28 | 29 | public void AddEntriesByCallNumber(int callCount, IEnumerable entries, EntityEntryState state = EntityEntryState.Modified) 30 | { 31 | changedAtCall[callCount - 1] = entries; 32 | } 33 | 34 | public IEnumerable GetChangedEntities(DbContext context, Predicate statePredicate) 35 | { 36 | getChangedEntitiesCount++; 37 | 38 | IEnumerable changedEntities; 39 | if (changedAtCall.TryGetValue(getChangedEntitiesCount, out changedEntities)) 40 | return changedEntities; 41 | 42 | return changedAtCall[0]; 43 | } 44 | 45 | public IEntityEntry GetEntry(object entity, DbContext context) 46 | { 47 | Mock entryStub = new Mock(); 48 | entryStub.Setup(e => e.Entity).Returns(entity); 49 | return entryStub.Object; 50 | } 51 | 52 | public IEntityEntry GetEntry(T entity, DbContext context) where T : class 53 | { 54 | Mock> entryStub = new Mock>(); 55 | entryStub.Setup(e => e.Entity).Returns(entity); 56 | return entryStub.Object; 57 | } 58 | 59 | public IEnumerable GetEntries(DbContext context) 60 | { 61 | yield break; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/TestDoubles/DbContextFakeWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Entity; 2 | using Moq; 3 | 4 | namespace iQuarc.DataAccess.UnitTests.TestDoubles 5 | { 6 | sealed class DbContextFakeWrapper : IDbContextWrapper 7 | { 8 | private readonly Mock contextDouble; 9 | 10 | public DbContextFakeWrapper() 11 | { 12 | this.contextDouble = new Mock(); 13 | } 14 | 15 | public DbContextFakeWrapper(Mock dbContext) 16 | { 17 | this.contextDouble = dbContext; 18 | } 19 | 20 | public Mock ContextDouble 21 | { 22 | get { return contextDouble; } 23 | } 24 | 25 | public DbContext Context 26 | { 27 | get { return contextDouble.Object; } 28 | } 29 | 30 | public event EntityLoadedEventHandler EntityLoaded; 31 | 32 | public void RaiseEntityLoaded(EntityLoadedEventHandlerArgs args) 33 | { 34 | if (EntityLoaded != null) 35 | EntityLoaded(this, args); 36 | } 37 | 38 | public void Dispose() 39 | { 40 | WasDisposed = true; 41 | } 42 | 43 | public bool WasDisposed { get; private set; } 44 | } 45 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/TestDoubles/DbUtilities.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data.Entity; 3 | using Moq; 4 | 5 | namespace iQuarc.DataAccess.UnitTests.TestDoubles 6 | { 7 | static class DbUtilities 8 | { 9 | public static DbSet MockDbSet(this IEnumerable values) where T : class 10 | { 11 | return new FakeSet(values); 12 | } 13 | 14 | public static DbSet MockDbSet(this IEnumerable values, DbContextFakeWrapper wrapper) where T : class 15 | { 16 | return new FakeSet(values, wrapper); 17 | } 18 | 19 | //TODO: write an overload for DbContextFakeWrapper 20 | public static IDbContextFactory BuildFactoryStub(this Mock contextStub) 21 | { 22 | Mock contextFactoryStub = new Mock(); 23 | contextFactoryStub.Setup(s => s.CreateContext()) 24 | .Returns(new DbContextFakeWrapper(contextStub)); 25 | return contextFactoryStub.Object; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/TestDoubles/EntityEntryDouble.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace iQuarc.DataAccess.UnitTests.TestDoubles 5 | { 6 | class EntityEntryDouble : IEntityEntry 7 | { 8 | public EntityEntryDouble() 9 | { 10 | } 11 | 12 | public EntityEntryDouble(object entity, EntityEntryState state) 13 | { 14 | Entity = entity; 15 | State = state; 16 | } 17 | 18 | public object Entity { get; private set; } 19 | public EntityEntryState State { get; set; } 20 | public object GetOriginalValue(string propertyName) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | 25 | public object GetCurrentValue(string propertyName) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | 30 | public IEntityEntry Convert() where T : class 31 | { 32 | throw new NotImplementedException(); 33 | } 34 | 35 | public void SetOriginalValue(string propertyName, object value) 36 | { 37 | throw new NotImplementedException(); 38 | } 39 | 40 | public void Reload() 41 | { 42 | throw new NotImplementedException(); 43 | } 44 | 45 | public IEnumerable GetProperties() 46 | { 47 | throw new NotImplementedException(); 48 | } 49 | 50 | public IPropertyEntry Property(string name) 51 | { 52 | throw new NotImplementedException(); 53 | } 54 | 55 | protected bool Equals(EntityEntryDouble other) 56 | { 57 | return Equals(Entity, other.Entity) && State == other.State; 58 | } 59 | 60 | public override bool Equals(object obj) 61 | { 62 | if (ReferenceEquals(null, obj)) return false; 63 | if (ReferenceEquals(this, obj)) return true; 64 | if (obj.GetType() != this.GetType()) return false; 65 | return Equals((EntityEntryDouble) obj); 66 | } 67 | 68 | public override int GetHashCode() 69 | { 70 | unchecked 71 | { 72 | return ((Entity != null ? Entity.GetHashCode() : 0)*397) ^ (int) State; 73 | } 74 | } 75 | } 76 | 77 | class EntityEntryDouble : IEntityEntry where T : class 78 | { 79 | public EntityEntryDouble() 80 | { 81 | } 82 | 83 | public EntityEntryDouble(T entity, EntityEntryState state) 84 | { 85 | Entity = entity; 86 | State = state; 87 | } 88 | 89 | public T Entity { get; set; } 90 | public EntityEntryState State { get; set; } 91 | public object GetOriginalValue(string propertyName) 92 | { 93 | throw new NotImplementedException(); 94 | } 95 | 96 | public object GetCurrentValue(string propertyName) 97 | { 98 | throw new NotImplementedException(); 99 | } 100 | 101 | public void SetOriginalValue(string propertyName, object value) 102 | { 103 | throw new NotImplementedException(); 104 | } 105 | 106 | public void Reload() 107 | { 108 | throw new NotImplementedException(); 109 | } 110 | 111 | public IEnumerable GetProperties() 112 | { 113 | throw new NotImplementedException(); 114 | } 115 | 116 | public IPropertyEntry Property(string name) 117 | { 118 | throw new NotImplementedException(); 119 | } 120 | 121 | protected bool Equals(EntityEntryDouble other) 122 | { 123 | return EqualityComparer.Default.Equals(Entity, other.Entity) && State == other.State; 124 | } 125 | 126 | public override bool Equals(object obj) 127 | { 128 | if (ReferenceEquals(null, obj)) return false; 129 | if (ReferenceEquals(this, obj)) return true; 130 | if (obj.GetType() != this.GetType()) return false; 131 | return Equals((EntityEntryDouble) obj); 132 | } 133 | 134 | public override int GetHashCode() 135 | { 136 | unchecked 137 | { 138 | return (EqualityComparer.Default.GetHashCode(Entity)*397) ^ (int) State; 139 | } 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/TestDoubles/EntryExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace iQuarc.DataAccess.UnitTests.TestDoubles 2 | { 3 | static class EntryExtensions 4 | { 5 | public static IEntityEntry AsEntry(this object entity, EntityEntryState state = EntityEntryState.Modified) 6 | { 7 | return new EntityEntryDouble(entity, state); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/TestDoubles/FakeExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace iQuarc.DataAccess.UnitTests.TestDoubles 4 | { 5 | public class FakeExceptionHandler : IExceptionHandler 6 | { 7 | public void Handle(Exception exception) 8 | { 9 | Handled = exception; 10 | } 11 | 12 | public Exception Handled 13 | { 14 | get; 15 | private set; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/TestDoubles/FakeSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Entity; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | 7 | namespace iQuarc.DataAccess.UnitTests.TestDoubles 8 | { 9 | class FakeSet : DbSet, IQueryable where T : class 10 | { 11 | private readonly DbContextFakeWrapper wrapper; 12 | private readonly List values = new List(); 13 | 14 | private IQueryable queryable; 15 | 16 | private IQueryable Queryable 17 | { 18 | get 19 | { 20 | if (queryable == null) 21 | queryable = EnumerateAndRaiseEvent().AsQueryable(); 22 | return queryable; 23 | } 24 | } 25 | 26 | private IEnumerable EnumerateAndRaiseEvent() 27 | { 28 | foreach (var value in values) 29 | { 30 | RaiseEntityLoaded(value); 31 | yield return value; 32 | } 33 | } 34 | 35 | public FakeSet() 36 | { 37 | values = new List(); 38 | } 39 | 40 | public FakeSet(IEnumerable values) 41 | : this(values, null) 42 | { 43 | } 44 | 45 | public FakeSet(IEnumerable values, DbContextFakeWrapper wrapper) 46 | { 47 | this.wrapper = wrapper; 48 | this.values.AddRange(values); 49 | } 50 | 51 | IQueryProvider IQueryable.Provider 52 | { 53 | get { return Queryable.Provider; } 54 | } 55 | 56 | Expression IQueryable.Expression 57 | { 58 | get { return Queryable.Expression; } 59 | } 60 | 61 | Type IQueryable.ElementType 62 | { 63 | get { return Queryable.ElementType; } 64 | } 65 | 66 | private void RaiseEntityLoaded(T value) 67 | { 68 | if (wrapper != null) 69 | { 70 | wrapper.RaiseEntityLoaded(new EntityLoadedEventHandlerArgs(value)); 71 | } 72 | } 73 | 74 | public IList Values 75 | { 76 | get { return values; } 77 | } 78 | 79 | public override T Add(T entity) 80 | { 81 | values.Add(entity); 82 | return entity; 83 | } 84 | 85 | public override T Remove(T entity) 86 | { 87 | values.Remove(entity); 88 | return entity; 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/TestDoubles/InterceptorDouble.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace iQuarc.DataAccess.UnitTests.TestDoubles 8 | { 9 | class InterceptorDouble : IEntityInterceptor 10 | { 11 | private readonly List onLoad = new List(); 12 | private readonly List onSave = new List(); 13 | private readonly List onDelete = new List(); 14 | 15 | public IEnumerable InterceptedOnOnLoad 16 | { 17 | get { return onLoad.Select(e => e.Entity); } 18 | } 19 | 20 | public IEnumerable InterceptedOnSave 21 | { 22 | get { return onSave.Select(e => e.Entity); } 23 | } 24 | 25 | public IEnumerable InterceptedOnDelete 26 | { 27 | get { return onDelete.Select(e => e.Entity); } 28 | } 29 | 30 | public void OnLoad(IEntityEntry entry, IRepository repository) 31 | { 32 | onLoad.Add(entry); 33 | } 34 | 35 | public void OnSave(IEntityEntry entry, IUnitOfWork repository) 36 | { 37 | onSave.Add(entry); 38 | } 39 | 40 | public void OnDelete(IEntityEntry entry, IUnitOfWork repository) 41 | { 42 | onDelete.Add(entry); 43 | } 44 | } 45 | 46 | static class InterceptorDoubleExtensions 47 | { 48 | public static void AssertIntercepted(this InterceptorDouble interceptor, Func> onFunc, T[] entities, Func msgFunc = null ) 49 | { 50 | if (msgFunc == null) 51 | msgFunc = arg => arg.ToString(); 52 | 53 | StringBuilder errors = new StringBuilder(); 54 | for (int i = 0; i < entities.Length; i++) 55 | { 56 | if (!onFunc(interceptor).Contains(entities[i])) 57 | errors.AppendLine(string.Format("User Name='{0}' was not intercepted", msgFunc(entities[i]))); 58 | } 59 | if (errors.Length > 0) 60 | Assert.Fail(errors.ToString()); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/UnitOfWork.AddTests.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Entity; 2 | using iQuarc.DataAccess.UnitTests.TestDoubles; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Moq; 5 | 6 | namespace iQuarc.DataAccess.UnitTests 7 | { 8 | [TestClass] 9 | public class UnitOfWorkAddTests 10 | { 11 | [TestMethod] 12 | public void Add_ValidEntity_EntityAddedToDbContext() 13 | { 14 | FakeSet setStub = new FakeSet(); 15 | Mock contextStub = new Mock(); 16 | contextStub.Setup(c => c.Set()).Returns(setStub); 17 | 18 | IInterceptorsResolver interceptorsResolver = new Mock().Object; 19 | UnitOfWork uof = new UnitOfWork(contextStub.BuildFactoryStub(), interceptorsResolver); 20 | 21 | User u = new User(); 22 | uof.Add(u); 23 | 24 | Assert.IsTrue(setStub.Values.Contains(u)); 25 | } 26 | 27 | private class User 28 | { 29 | public int Id { get; set; } 30 | public string Name { get; set; } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/UnitOfWork.DeleteTests.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Entity; 2 | using iQuarc.DataAccess.UnitTests.TestDoubles; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Moq; 5 | 6 | namespace iQuarc.DataAccess.UnitTests 7 | { 8 | [TestClass] 9 | public class UnitOfWorkDeleteTests 10 | { 11 | [TestMethod] 12 | public void Delete_ExistentEntity_EntityRemoved() 13 | { 14 | User user = new User(); 15 | FakeSet set = new FakeSet {user}; 16 | UnitOfWork uof = GetTargetWith(set); 17 | 18 | uof.Delete(user); 19 | 20 | Assert.IsFalse(set.Values.Contains(user), "User found, but not expected"); 21 | } 22 | 23 | private UnitOfWork GetTargetWith(FakeSet set) 24 | { 25 | IInterceptorsResolver interceptorsResolver = new Mock().Object; 26 | Mock context = GetContextWith(set); 27 | IDbContextFactory contextFactory = context.BuildFactoryStub(); 28 | 29 | IExceptionHandler handler = new Mock().Object; 30 | return new UnitOfWork(contextFactory, interceptorsResolver, new DbContextUtilities(), handler); 31 | } 32 | 33 | private Mock GetContextWith(FakeSet set) 34 | { 35 | Mock contextStub = new Mock(); 36 | contextStub.Setup(c => c.Set()).Returns(set); 37 | return contextStub; 38 | } 39 | 40 | private class User 41 | { 42 | public User() 43 | { 44 | } 45 | 46 | public User(int id, string name) 47 | { 48 | Id = id; 49 | Name = name; 50 | } 51 | 52 | public int Id { get; set; } 53 | public string Name { get; set; } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/UnitOfWork.RepositoryTests.cs: -------------------------------------------------------------------------------- 1 | using iQuarc.DataAccess.UnitTests.TestDoubles; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Moq; 4 | 5 | namespace iQuarc.DataAccess.UnitTests 6 | { 7 | [TestClass] 8 | public class UnitOfWorkAsRepositoryTests : RepositoryBaseTests 9 | { 10 | protected override IRepository GetTarget(IDbContextFactory factory, IInterceptorsResolver resolver) 11 | { 12 | IExceptionHandler handler = new Mock().Object; 13 | return new UnitOfWork(factory, resolver, new ContextUtilitiesDouble(), handler); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/UnitOfWork.SaveTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Entity; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using iQuarc.DataAccess.UnitTests.TestDoubles; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | using Moq; 9 | 10 | namespace iQuarc.DataAccess.UnitTests 11 | { 12 | [TestClass] 13 | public class UnitOfWorkSaveTests 14 | { 15 | [TestMethod] 16 | public void SaveChanges_ModifiedEntitiesAndGlobalInterceptors_EntitiesIntercepted() 17 | { 18 | ParamTest__ModifiedEntities_EntitiesIntercepted( 19 | resolver => resolver.GetGlobalInterceptors()); 20 | } 21 | 22 | [TestMethod] 23 | public void SaveChanges_ModifiedEntitiesAndEntityInterceptors_EntitiesIntercepted() 24 | { 25 | ParamTest__ModifiedEntities_EntitiesIntercepted( 26 | resolver => resolver.GetEntityInterceptors(It.IsAny())); 27 | } 28 | 29 | private void ParamTest__ModifiedEntities_EntitiesIntercepted(Expression>> getInterceptorsFunction) 30 | { 31 | User user1 = new User(1, "John"); 32 | User user2 = new User(2, "Mary"); 33 | 34 | InterceptorDouble interceptorMock = new InterceptorDouble(); 35 | 36 | Mock interceptorResolverStub = new Mock(); 37 | interceptorResolverStub.Setup(getInterceptorsFunction).Returns(new[] {interceptorMock}); 38 | 39 | UnitOfWork uof = GetTargetWith(new[] {user1, user2}, interceptorResolverStub.Object); 40 | 41 | uof.SaveChanges(); 42 | 43 | AssertInterceptedOnSave(interceptorMock, user1, user2); 44 | } 45 | 46 | [TestMethod] 47 | public void SaveChanges_DeletedEntitiesAndGlobalInterceptors_DeletedEntitiesIntercepted() 48 | { 49 | ParamTest__DeletedEntities_DeletedEntitiesIntercepted( 50 | i => i.GetGlobalInterceptors()); 51 | } 52 | 53 | [TestMethod] 54 | public void SaveChanges_DeletedEntitiesAndEntityInterceptors_DeletedEntitiesIntercepted() 55 | { 56 | ParamTest__DeletedEntities_DeletedEntitiesIntercepted( 57 | i => i.GetEntityInterceptors(It.IsAny())); 58 | } 59 | 60 | private void ParamTest__DeletedEntities_DeletedEntitiesIntercepted(Expression>> getInterceptorsFunction) 61 | { 62 | InterceptorDouble interceptorMock = new InterceptorDouble(); 63 | Mock resolverMock = new Mock(); 64 | resolverMock.Setup(getInterceptorsFunction).Returns(new[] {interceptorMock}); 65 | 66 | User u1 = new User(1, "John"); 67 | User u2 = new User(2, "Mary"); 68 | 69 | ContextUtilitiesDouble contextStub = new ContextUtilitiesDouble(new[] {u1, u2}, EntityEntryState.Deleted); 70 | UnitOfWork uof = GetTargetWith(contextStub, resolverMock.Object); 71 | 72 | uof.SaveChanges(); 73 | 74 | AssertInterceptedOnDelete(interceptorMock, u1, u2); 75 | } 76 | 77 | 78 | [TestMethod] 79 | public void SaveChanges_OnSaveModifiesNewEntitiesAndGlobalInterceptor_NewModifiedEntityAlsoIntercepted() 80 | { 81 | ParamTest__OnSaveModifiesOtherEntities_NewModifiedEntitiesAlsoIntercepted( 82 | resolver => resolver.GetGlobalInterceptors()); 83 | } 84 | 85 | [TestMethod] 86 | public void SaveChanges_OnSaveModifiesNewEntitiesAndEntityInterceptors_NewModifiedEntityAlsoIntercepted() 87 | { 88 | ParamTest__OnSaveModifiesOtherEntities_NewModifiedEntitiesAlsoIntercepted( 89 | resolver => resolver.GetEntityInterceptors(It.IsAny())); 90 | } 91 | 92 | private void ParamTest__OnSaveModifiesOtherEntities_NewModifiedEntitiesAlsoIntercepted( 93 | Expression>> getInterceptorsFunction) 94 | { 95 | User user1 = new User(1, "John"); 96 | User user2 = new User(2, "Mary"); 97 | ContextUtilitiesDouble contextStub = new ContextUtilitiesDouble(); 98 | contextStub.AddEntitiesByCallNumber(1, new[] {user1}); // 1st call to GetChangedEntities --> user1 99 | contextStub.AddEntitiesByCallNumber(2, new[] {user1, user2}); // 2nd call to GetChangedEntities --> user1 and user2 100 | 101 | InterceptorDouble interceptorMock = new InterceptorDouble(); 102 | 103 | Mock interceptorResolverStub = new Mock(); 104 | interceptorResolverStub.Setup(getInterceptorsFunction).Returns(new[] {interceptorMock}); 105 | 106 | 107 | UnitOfWork uof = GetTargetWith(contextStub, interceptorResolverStub.Object); 108 | 109 | 110 | uof.SaveChanges(); 111 | 112 | AssertInterceptedOnSave(interceptorMock, user1, user2); 113 | } 114 | 115 | [TestMethod] 116 | public void SaveChanges_OnSaveDeletesEntitiesAndGlobalInterceptor_DeletedEntitiesIntercepted() 117 | { 118 | ParamTest__OnSaveDeletesEntities_DeletedEntitiesAlsoIntercepted( 119 | resolver => resolver.GetGlobalInterceptors()); 120 | } 121 | 122 | [TestMethod] 123 | public void SaveChanges_OnSaveDeletesEntitiesAndEntityInterceptors_DeletedEntitiesIntercepted() 124 | { 125 | ParamTest__OnSaveDeletesEntities_DeletedEntitiesAlsoIntercepted( 126 | resolver => resolver.GetEntityInterceptors(It.IsAny())); 127 | } 128 | 129 | private void ParamTest__OnSaveDeletesEntities_DeletedEntitiesAlsoIntercepted( 130 | Expression>> getInterceptorsFunction) 131 | { 132 | var u1 = new User(1, "John").AsEntry(); 133 | var u2 = new User(2, "Mary").AsEntry(EntityEntryState.Deleted); 134 | ContextUtilitiesDouble contextStub = new ContextUtilitiesDouble(); 135 | contextStub.AddEntriesByCallNumber(1, new[] { u1 }); // 1st call to GetChangedEntities --> modified entity 136 | contextStub.AddEntriesByCallNumber(2, new[] { u1, u2 }); // 2nd call to GetChangedEntities --> modified and deleted entities 137 | 138 | InterceptorDouble interceptorMock = new InterceptorDouble(); 139 | 140 | Mock interceptorResolverStub = new Mock(); 141 | interceptorResolverStub.Setup(getInterceptorsFunction).Returns(new[] { interceptorMock }); 142 | 143 | UnitOfWork uof = GetTargetWith(contextStub, interceptorResolverStub.Object); 144 | 145 | 146 | uof.SaveChanges(); 147 | 148 | AssertInterceptedOnDelete(interceptorMock, u2.Entity as User); 149 | } 150 | 151 | [TestMethod] 152 | public void SaveChanges_OnDeleteDeletesEntitiesAndGlobalInterceptor_DeletedEntitiesIntercepted() 153 | { 154 | ParamTest__OnDeleteDeletesEntities_DeletedEntitiesAlsoIntercepted( 155 | resolver => resolver.GetGlobalInterceptors()); 156 | } 157 | 158 | [TestMethod] 159 | public void SaveChanges_OnDeleteDeletesEntitiesAndEntityInterceptors_DeletedEntitiesIntercepted() 160 | { 161 | ParamTest__OnDeleteDeletesEntities_DeletedEntitiesAlsoIntercepted( 162 | resolver => resolver.GetEntityInterceptors(It.IsAny())); 163 | } 164 | 165 | private void ParamTest__OnDeleteDeletesEntities_DeletedEntitiesAlsoIntercepted( 166 | Expression>> getInterceptorsFunction) 167 | { 168 | var u1 = new User(1, "John").AsEntry(EntityEntryState.Deleted); 169 | var u2 = new User(2, "Mary").AsEntry(EntityEntryState.Deleted); 170 | ContextUtilitiesDouble contextStub = new ContextUtilitiesDouble(); 171 | contextStub.AddEntriesByCallNumber(1, new[] { u1 }); // 1st call to GetChangedEntities --> one deleted entity 172 | contextStub.AddEntriesByCallNumber(2, new[] { u1, u2 }); // 2nd call to GetChangedEntities --> two deleted entities 173 | 174 | InterceptorDouble interceptorMock = new InterceptorDouble(); 175 | 176 | Mock interceptorResolverStub = new Mock(); 177 | interceptorResolverStub.Setup(getInterceptorsFunction).Returns(new[] { interceptorMock }); 178 | 179 | UnitOfWork uof = GetTargetWith(contextStub, interceptorResolverStub.Object); 180 | 181 | 182 | uof.SaveChanges(); 183 | 184 | AssertInterceptedOnDelete(interceptorMock, u1.Entity as User, u2.Entity as User); 185 | } 186 | 187 | [TestMethod] 188 | public void SaveChanges_OnDeleteModifiesEntitiesAndGlobalInterceptor_ModifiedEntitiesIntercepted() 189 | { 190 | ParamTest__OnDeleteModifiesEntities_ModifiedEntitiesAlsoIntercepted( 191 | resolver => resolver.GetGlobalInterceptors()); 192 | } 193 | 194 | [TestMethod] 195 | public void SaveChanges_OnDeleteModifiesEntitiesAndEntityInterceptors_ModifiedEntitiesIntercepted() 196 | { 197 | ParamTest__OnDeleteModifiesEntities_ModifiedEntitiesAlsoIntercepted( 198 | resolver => resolver.GetEntityInterceptors(It.IsAny())); 199 | } 200 | 201 | private void ParamTest__OnDeleteModifiesEntities_ModifiedEntitiesAlsoIntercepted( 202 | Expression>> getInterceptorsFunction) 203 | { 204 | var u1 = new User(1, "John").AsEntry(EntityEntryState.Deleted); 205 | var u2 = new User(2, "Mary").AsEntry(EntityEntryState.Modified); 206 | ContextUtilitiesDouble contextStub = new ContextUtilitiesDouble(); 207 | contextStub.AddEntriesByCallNumber(1, new[] { u1 }); // 1st call to GetChangedEntities --> one deleted entity 208 | contextStub.AddEntriesByCallNumber(2, new[] { u1, u2 }); // 2nd call to GetChangedEntities --> deleted and modified entities 209 | 210 | InterceptorDouble interceptorMock = new InterceptorDouble(); 211 | 212 | Mock interceptorResolverStub = new Mock(); 213 | interceptorResolverStub.Setup(getInterceptorsFunction).Returns(new[] { interceptorMock }); 214 | 215 | UnitOfWork uof = GetTargetWith(contextStub, interceptorResolverStub.Object); 216 | 217 | 218 | uof.SaveChanges(); 219 | 220 | AssertInterceptedOnSave(interceptorMock, u2.Entity as User); 221 | } 222 | 223 | private static void AssertInterceptedOnSave(InterceptorDouble interceptorMock, params User[] users) 224 | { 225 | interceptorMock.AssertIntercepted(d => d.InterceptedOnSave, users, u => u.Name); 226 | } 227 | 228 | private void AssertInterceptedOnDelete(InterceptorDouble interceptorMock, params User[] users) 229 | { 230 | interceptorMock.AssertIntercepted(d => d.InterceptedOnDelete, users, u => u.Name); 231 | } 232 | 233 | [TestMethod] 234 | public void SaveChanges_WhenCalled_SaveChangesOnContext() 235 | { 236 | Mock contextMock = new Mock(); 237 | UnitOfWork uof = GetTargetWith(contextMock); 238 | 239 | uof.SaveChanges(); 240 | 241 | contextMock.Verify(c => c.SaveChanges(), Times.AtLeastOnce); 242 | } 243 | 244 | [TestMethod] 245 | public void SaveChanges_InterceptorThrowsException_ExceptionHandlerGetsIt() 246 | { 247 | Exception e = new Exception(); 248 | Mock interceptorStub = new Mock(); 249 | interceptorStub.Setup(i => i.OnSave(It.IsAny(), It.IsAny())) 250 | .Throws(e); 251 | 252 | FakeExceptionHandler handler = new FakeExceptionHandler(); 253 | UnitOfWork uof = GetTargetWith(interceptorStub.Object, handler); 254 | 255 | uof.SaveChanges(); 256 | 257 | Assert.AreSame(e, handler.Handled); 258 | } 259 | 260 | [TestMethod] 261 | public void SaveChanges_ContextSaveThrowsException_ExceptionHandlerGetsIt() 262 | { 263 | Exception e = new Exception(); 264 | Mock dbContextStub = new Mock(); 265 | dbContextStub.Setup(c => c.SaveChanges()).Throws(e); 266 | 267 | FakeExceptionHandler handler = new FakeExceptionHandler(); 268 | UnitOfWork uof = GetTargetWith(dbContextStub, handler); 269 | 270 | uof.SaveChanges(); 271 | 272 | Assert.AreSame(e, handler.Handled); 273 | } 274 | 275 | private UnitOfWork GetTargetWith(Mock context, FakeExceptionHandler handler) 276 | { 277 | ContextUtilitiesDouble utilitiesStub = new ContextUtilitiesDouble(new[] {new User()}); 278 | Mock resolverStub = new Mock(); 279 | 280 | return GetTargetWith(utilitiesStub, resolverStub.Object, context, handler); 281 | } 282 | 283 | private UnitOfWork GetTargetWith(IEntityInterceptor interceptor, IExceptionHandler handler) 284 | { 285 | ContextUtilitiesDouble utilitiesStub = new ContextUtilitiesDouble(new[] {new User()}); 286 | 287 | Mock resolverStub = new Mock(); 288 | resolverStub.Setup(r => r.GetGlobalInterceptors()).Returns(new[] {interceptor}); 289 | 290 | return GetTargetWith(utilitiesStub, resolverStub.Object, new Mock(), handler); 291 | } 292 | 293 | private UnitOfWork GetTargetWith(Mock context) 294 | { 295 | ContextUtilitiesDouble contextUtilitiesStub = new ContextUtilitiesDouble(Enumerable.Empty()); 296 | return GetTargetWith(contextUtilitiesStub, new Mock().Object, context); 297 | } 298 | 299 | private UnitOfWork GetTargetWith(IEnumerable changedEntities, IInterceptorsResolver interceptorsResolver) 300 | { 301 | ContextUtilitiesDouble contextUtilitiesStub = new ContextUtilitiesDouble(changedEntities); 302 | return GetTargetWith(contextUtilitiesStub, interceptorsResolver, new Mock()); 303 | } 304 | 305 | private UnitOfWork GetTargetWith(IDbContextUtilities contextUtilities, IInterceptorsResolver interceptorsResolver) 306 | { 307 | return GetTargetWith(contextUtilities, interceptorsResolver, new Mock()); 308 | } 309 | 310 | private UnitOfWork GetTargetWith(IDbContextUtilities contextUtilitiesStub, IInterceptorsResolver interceptorsResolver, Mock contextStub) 311 | { 312 | IExceptionHandler handler = new Mock().Object; 313 | return GetTargetWith(contextUtilitiesStub, interceptorsResolver, contextStub, handler); 314 | } 315 | 316 | private UnitOfWork GetTargetWith(IDbContextUtilities contextUtilitiesStub, IInterceptorsResolver interceptorsResolver, Mock contextStub, IExceptionHandler handler) 317 | { 318 | var contextFactoryStub = contextStub.BuildFactoryStub(); 319 | return new UnitOfWork(contextFactoryStub, interceptorsResolver, contextUtilitiesStub, handler); 320 | } 321 | 322 | 323 | private class User 324 | { 325 | public User() 326 | { 327 | } 328 | 329 | public User(int id, string name) 330 | { 331 | Id = id; 332 | Name = name; 333 | } 334 | 335 | public int Id { get; set; } 336 | public string Name { get; set; } 337 | } 338 | } 339 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/iQuarc.DataAccess.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {DFE330D3-8DFD-45CB-BDAD-5E1BA3D60569} 7 | Library 8 | Properties 9 | iQuarc.DataAccess.UnitTests 10 | iQuarc.DataAccess.UnitTests 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | ..\ 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll 41 | True 42 | 43 | 44 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll 45 | True 46 | 47 | 48 | False 49 | ..\packages\Moq.4.2.1502.0911\lib\net40\Moq.dll 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 | {53FBDBE7-3B53-4FC1-9676-1932C6CE6A2F} 98 | iQuarc.DataAccess 99 | 100 | 101 | 102 | 103 | 104 | 105 | False 106 | 107 | 108 | False 109 | 110 | 111 | False 112 | 113 | 114 | False 115 | 116 | 117 | 118 | 119 | 120 | 121 | 128 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.UnitTests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iQuarc.DataAccess", "iQuarc.DataAccess\iQuarc.DataAccess.csproj", "{53FBDBE7-3B53-4FC1-9676-1932C6CE6A2F}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iQuarc.DataAccess.UnitTests", "iQuarc.DataAccess.UnitTests\iQuarc.DataAccess.UnitTests.csproj", "{DFE330D3-8DFD-45CB-BDAD-5E1BA3D60569}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iQuarc.DataAccess.AppBoot", "iQuarc.DataAccess.AppBoot\iQuarc.DataAccess.AppBoot.csproj", "{5C839D26-2F63-4E00-A907-DD535A9846DF}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iQuarc.DataAccess.IntegrationTests", "iQuarc.DataAccess.IntegrationTests\iQuarc.DataAccess.IntegrationTests.csproj", "{74216D64-CDD7-4A47-81CB-A9B7407D721F}" 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 | {53FBDBE7-3B53-4FC1-9676-1932C6CE6A2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {53FBDBE7-3B53-4FC1-9676-1932C6CE6A2F}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {53FBDBE7-3B53-4FC1-9676-1932C6CE6A2F}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {53FBDBE7-3B53-4FC1-9676-1932C6CE6A2F}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {DFE330D3-8DFD-45CB-BDAD-5E1BA3D60569}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {DFE330D3-8DFD-45CB-BDAD-5E1BA3D60569}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {DFE330D3-8DFD-45CB-BDAD-5E1BA3D60569}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {DFE330D3-8DFD-45CB-BDAD-5E1BA3D60569}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {5C839D26-2F63-4E00-A907-DD535A9846DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {5C839D26-2F63-4E00-A907-DD535A9846DF}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {5C839D26-2F63-4E00-A907-DD535A9846DF}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {5C839D26-2F63-4E00-A907-DD535A9846DF}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {74216D64-CDD7-4A47-81CB-A9B7407D721F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {74216D64-CDD7-4A47-81CB-A9B7407D721F}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {74216D64-CDD7-4A47-81CB-A9B7407D721F}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {74216D64-CDD7-4A47-81CB-A9B7407D721F}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/CollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace iQuarc.DataAccess 4 | { 5 | internal static class CollectionExtensions 6 | { 7 | public static void AddIfNotExists(this ICollection list, T item) 8 | { 9 | if (!list.Contains(item)) 10 | list.Add(item); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/DbContextBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Entity; 4 | using System.Data.Entity.Core.Objects; 5 | 6 | namespace iQuarc.DataAccess 7 | { 8 | sealed class DbContextBuilder : IDisposable 9 | { 10 | private readonly IDbContextFactory factory; 11 | private readonly IInterceptorsResolver interceptorsResolver; 12 | private readonly IRepository repository; 13 | private readonly IDbContextUtilities contextUtilities; 14 | 15 | private IDbContextWrapper contextWrapper; 16 | private IEnumerable globalInterceptors; 17 | 18 | public DbContextBuilder(IDbContextFactory factory, IInterceptorsResolver interceptorsResolver, IRepository repository, IDbContextUtilities contextUtilities) 19 | { 20 | this.factory = factory; 21 | this.interceptorsResolver = interceptorsResolver; 22 | this.repository = repository; 23 | this.contextUtilities = contextUtilities; 24 | } 25 | 26 | public DbContext Context 27 | { 28 | get 29 | { 30 | if (contextWrapper == null) 31 | Init(); 32 | 33 | return contextWrapper.Context; 34 | } 35 | } 36 | 37 | private void Init() 38 | { 39 | globalInterceptors = interceptorsResolver.GetGlobalInterceptors(); 40 | 41 | contextWrapper = factory.CreateContext(); 42 | contextWrapper.EntityLoaded += OnEntityLoaded; 43 | } 44 | 45 | private void OnEntityLoaded(object sender, EntityLoadedEventHandlerArgs e) 46 | { 47 | InterceptLoad(globalInterceptors, e.Entity); 48 | 49 | Type entityType = ObjectContext.GetObjectType(e.Entity.GetType()); 50 | IEnumerable entityInterceptors = interceptorsResolver.GetEntityInterceptors(entityType); 51 | InterceptLoad(entityInterceptors, e.Entity); 52 | } 53 | 54 | private void InterceptLoad(IEnumerable interceptors, object entity) 55 | { 56 | IEntityEntry entry = contextUtilities.GetEntry(entity, Context); 57 | foreach (var interceptor in interceptors) 58 | { 59 | interceptor.OnLoad(entry, repository); 60 | } 61 | } 62 | 63 | public void Dispose() 64 | { 65 | if (contextWrapper != null) 66 | { 67 | contextWrapper.EntityLoaded -= OnEntityLoaded; 68 | contextWrapper.Dispose(); 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/DbContextFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Entity; 2 | 3 | namespace iQuarc.DataAccess 4 | { 5 | public class DbContextFactory : IDbContextFactory where T : DbContext, new() 6 | { 7 | public IDbContextWrapper CreateContext() 8 | { 9 | T context = new T(); 10 | return new DbContextWrapper(context); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/DbContextWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Entity; 2 | using System.Data.Entity.Core.Objects; 3 | using System.Data.Entity.Infrastructure; 4 | 5 | namespace iQuarc.DataAccess 6 | { 7 | public sealed class DbContextWrapper : IDbContextWrapper 8 | { 9 | private readonly ObjectContext objectContext; 10 | 11 | public DbContextWrapper(DbContext context) 12 | { 13 | Context = context; 14 | 15 | objectContext = ((IObjectContextAdapter) context).ObjectContext; 16 | objectContext.ObjectMaterialized += ObjectMaterializedHandler; 17 | } 18 | 19 | private void ObjectMaterializedHandler(object sender, ObjectMaterializedEventArgs e) 20 | { 21 | EntityLoadedEventHandler handler = EntityLoaded; 22 | if (handler != null) 23 | handler(this, new EntityLoadedEventHandlerArgs(e.Entity)); 24 | } 25 | 26 | public DbContext Context { get; private set; } 27 | 28 | public event EntityLoadedEventHandler EntityLoaded; 29 | 30 | public void Dispose() 31 | { 32 | objectContext.ObjectMaterialized -= ObjectMaterializedHandler; 33 | Context.Dispose(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/EntityEntry.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data.Entity; 3 | using System.Data.Entity.Infrastructure; 4 | using System.Linq; 5 | 6 | namespace iQuarc.DataAccess 7 | { 8 | sealed class EntityEntry : IEntityEntry 9 | where T : class 10 | { 11 | private readonly DbEntityEntry entry; 12 | 13 | public EntityEntry(DbEntityEntry entry) 14 | { 15 | this.entry = entry; 16 | } 17 | 18 | public T Entity 19 | { 20 | get { return entry.Entity; } 21 | } 22 | 23 | public EntityEntryState State 24 | { 25 | get { return (EntityEntryState)entry.State; } 26 | set { entry.State = (EntityState)value; } 27 | } 28 | 29 | public object GetOriginalValue(string propertyName) 30 | { 31 | return entry.OriginalValues[propertyName]; 32 | } 33 | 34 | public object GetCurrentValue(string propertyName) 35 | { 36 | return entry.CurrentValues[propertyName]; 37 | } 38 | 39 | public void SetOriginalValue(string propertyName, object value) 40 | { 41 | if (entry.OriginalValues.PropertyNames.Contains(propertyName)) 42 | entry.OriginalValues[propertyName] = value; 43 | } 44 | 45 | public void Reload() 46 | { 47 | entry.Reload(); 48 | } 49 | 50 | public IEnumerable GetProperties() 51 | { 52 | return entry.CurrentValues.PropertyNames; 53 | } 54 | 55 | public IPropertyEntry Property(string name) 56 | { 57 | return new PropertyEntry(entry.Property(name)); 58 | } 59 | 60 | private bool Equals(EntityEntry other) 61 | { 62 | return Equals(entry, other.entry); 63 | } 64 | 65 | public override bool Equals(object obj) 66 | { 67 | if (ReferenceEquals(null, obj)) return false; 68 | if (ReferenceEquals(this, obj)) return true; 69 | return obj is EntityEntry && Equals((EntityEntry)obj); 70 | } 71 | 72 | public override int GetHashCode() 73 | { 74 | return (entry != null ? entry.GetHashCode() : 0); 75 | } 76 | } 77 | 78 | sealed class EntityEntry : IEntityEntry 79 | { 80 | private readonly DbEntityEntry entry; 81 | 82 | public EntityEntry(DbEntityEntry entry) 83 | { 84 | this.entry = entry; 85 | } 86 | 87 | public object Entity 88 | { 89 | get { return this.entry.Entity; } 90 | } 91 | 92 | public EntityEntryState State 93 | { 94 | get { return (EntityEntryState)entry.State; } 95 | set { entry.State = (EntityState)value; } 96 | } 97 | 98 | public object GetOriginalValue(string propertyName) 99 | { 100 | return entry.OriginalValues[propertyName]; 101 | } 102 | 103 | public object GetCurrentValue(string propertyName) 104 | { 105 | return entry.CurrentValues[propertyName]; 106 | } 107 | 108 | public IEntityEntry Convert() where T : class 109 | { 110 | return new EntityEntry(entry.Cast()); 111 | } 112 | 113 | public void SetOriginalValue(string propertyName, object value) 114 | { 115 | if (entry.OriginalValues.PropertyNames.Contains(propertyName)) 116 | entry.OriginalValues[propertyName] = value; 117 | } 118 | 119 | public void Reload() 120 | { 121 | entry.Reload(); 122 | } 123 | 124 | public IEnumerable GetProperties() 125 | { 126 | return entry.CurrentValues.PropertyNames; 127 | } 128 | 129 | public IPropertyEntry Property(string name) 130 | { 131 | return new PropertyEntry(entry.Property(name)); 132 | } 133 | 134 | private bool Equals(EntityEntry other) 135 | { 136 | return Equals(entry, other.entry); 137 | } 138 | 139 | public override bool Equals(object obj) 140 | { 141 | if (ReferenceEquals(null, obj)) return false; 142 | if (ReferenceEquals(this, obj)) return true; 143 | return obj is EntityEntry && Equals((EntityEntry)obj); 144 | } 145 | 146 | public override int GetHashCode() 147 | { 148 | return (entry != null ? entry.GetHashCode() : 0); 149 | } 150 | } 151 | 152 | sealed class PropertyEntry : IPropertyEntry 153 | { 154 | private readonly DbPropertyEntry entry; 155 | 156 | public PropertyEntry(DbPropertyEntry entry) 157 | { 158 | this.entry = entry; 159 | } 160 | 161 | public string Name 162 | { 163 | get { return entry.Name; } 164 | } 165 | public object CurentValue 166 | { 167 | get { return entry.CurrentValue; } 168 | set { entry.CurrentValue = value; } 169 | } 170 | public object OriginalValue 171 | { 172 | get { return entry.OriginalValue; } 173 | set { entry.OriginalValue = value; } 174 | } 175 | public bool IsModified 176 | { 177 | get { return entry.IsModified; } 178 | set { entry.IsModified = value; } 179 | } 180 | } 181 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/EntityEntryState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity; 3 | 4 | namespace iQuarc.DataAccess 5 | { 6 | [Flags] 7 | public enum EntityEntryState 8 | { 9 | Detached = EntityState.Detached, 10 | Unchanged = EntityState.Unchanged, 11 | Added = EntityState.Added, 12 | Deleted = EntityState.Deleted, 13 | Modified = EntityState.Modified, 14 | } 15 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/ExceptionHandling/ConcurrencyExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity.Core; 3 | using iQuarc.SystemEx; 4 | 5 | namespace iQuarc.DataAccess 6 | { 7 | internal class ConcurrencyExceptionHandler : IExceptionHandler 8 | { 9 | private readonly IExceptionHandler successor; 10 | 11 | public ConcurrencyExceptionHandler(IExceptionHandler successor) 12 | { 13 | this.successor = successor; 14 | } 15 | 16 | public void Handle(Exception exception) 17 | { 18 | var concurrencyException = exception.FirstInner(); 19 | if (concurrencyException != null) 20 | { 21 | throw new ConcurrencyRepositoryViolationException(concurrencyException); 22 | } 23 | 24 | successor.Handle(exception); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/ExceptionHandling/DbEntityValidationExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Entity.Validation; 4 | using iQuarc.SystemEx; 5 | 6 | namespace iQuarc.DataAccess 7 | { 8 | class DbEntityValidationExceptionHandler : IExceptionHandler 9 | { 10 | private readonly IExceptionHandler successor; 11 | 12 | public DbEntityValidationExceptionHandler(IExceptionHandler successor) 13 | { 14 | this.successor = successor; 15 | } 16 | 17 | public void Handle(Exception exception) 18 | { 19 | var validationException = exception.FirstInner(); 20 | if (validationException != null) 21 | { 22 | IEnumerable errors = GetErrors(validationException); 23 | throw new DataValidationException(validationException.Message, errors, validationException); 24 | } 25 | 26 | successor.Handle(exception); 27 | } 28 | 29 | private IEnumerable GetErrors(DbEntityValidationException validationException) 30 | { 31 | foreach (var dbEntityValidationResult in validationException.EntityValidationErrors) 32 | { 33 | EntityEntry entry = new EntityEntry(dbEntityValidationResult.Entry); 34 | IEnumerable entryErrors = GetEntryErrors(dbEntityValidationResult.ValidationErrors); 35 | yield return new DataValidationResult(entry, entryErrors); 36 | } 37 | } 38 | 39 | private IEnumerable GetEntryErrors(IEnumerable validationErrors) 40 | { 41 | foreach (var dbValidationError in validationErrors) 42 | { 43 | yield return new ValidationError(dbValidationError.PropertyName, dbValidationError.ErrorMessage); 44 | } 45 | } 46 | 47 | public IExceptionHandler Successor { get; private set; } 48 | } 49 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/ExceptionHandling/DefaultExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace iQuarc.DataAccess 4 | { 5 | internal class DefaultExceptionHandler : IExceptionHandler 6 | { 7 | public void Handle(Exception exception) 8 | { 9 | throw new RepositoryViolationException(exception); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/ExceptionHandling/ExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace iQuarc.DataAccess 4 | { 5 | internal class ExceptionHandler : IExceptionHandler 6 | { 7 | private readonly IExceptionHandler chainHead = 8 | new SqlExceptionHandler( 9 | new ConcurrencyExceptionHandler( 10 | new UpdateExceptionHandler( 11 | new DbEntityValidationExceptionHandler( 12 | new DefaultExceptionHandler()))) 13 | ); 14 | 15 | public void Handle(Exception exception) 16 | { 17 | chainHead.Handle(exception); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/ExceptionHandling/IExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace iQuarc.DataAccess 4 | { 5 | public interface IExceptionHandler 6 | { 7 | void Handle(Exception exception); 8 | } 9 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/ExceptionHandling/SqlExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using iQuarc.SystemEx; 4 | 5 | namespace iQuarc.DataAccess 6 | { 7 | internal class SqlExceptionHandler : IExceptionHandler 8 | { 9 | private readonly IExceptionHandler successor; 10 | 11 | public SqlExceptionHandler(IExceptionHandler successor) 12 | { 13 | this.successor = successor; 14 | } 15 | 16 | public void Handle(Exception exception) 17 | { 18 | var sqlException = exception.FirstInner(); 19 | if (sqlException != null) 20 | { 21 | switch (sqlException.Number) 22 | { 23 | case 242: 24 | throw new DateTimeRangeRepositoryViolationException(sqlException); 25 | case 547: 26 | throw new DeleteConstraintRepositoryViolationException(sqlException); 27 | case 1205: 28 | throw new DeadlockVictimRepositoryViolationException(sqlException); 29 | case 2601: 30 | case 2627: 31 | throw new UniqueConstraintRepositoryViolationException(sqlException); 32 | default: 33 | throw new RepositoryViolationException(sqlException); 34 | } 35 | } 36 | 37 | successor.Handle(exception); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/ExceptionHandling/UpdateExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity.Core; 3 | using iQuarc.SystemEx; 4 | 5 | namespace iQuarc.DataAccess 6 | { 7 | internal class UpdateExceptionHandler : IExceptionHandler 8 | { 9 | private readonly IExceptionHandler successor; 10 | 11 | public UpdateExceptionHandler(IExceptionHandler successor) 12 | { 13 | this.successor = successor; 14 | } 15 | 16 | public void Handle(Exception exception) 17 | { 18 | var updateException = exception.FirstInner(); 19 | if (updateException != null) 20 | { 21 | throw new RepositoryUpdateException(updateException); 22 | } 23 | 24 | successor.Handle(exception); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/Exceptions/ConcurrencyRepositoryViolationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity.Core; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | 6 | namespace iQuarc.DataAccess 7 | { 8 | [Serializable] 9 | public class ConcurrencyRepositoryViolationException : RepositoryViolationException 10 | { 11 | private const string RepositoryEntityKey = "Entity"; 12 | public object Entity { get; set; } 13 | 14 | public ConcurrencyRepositoryViolationException() 15 | { 16 | } 17 | 18 | public ConcurrencyRepositoryViolationException(string errorMessage) 19 | : base(errorMessage) 20 | { 21 | } 22 | 23 | public ConcurrencyRepositoryViolationException(UpdateException exception) 24 | : this(string.Empty, exception) 25 | { 26 | 27 | } 28 | 29 | public ConcurrencyRepositoryViolationException(string message, UpdateException exception) 30 | : base(message, exception) 31 | { 32 | if (exception.StateEntries != null) 33 | { 34 | var entry = exception.StateEntries.FirstOrDefault(); 35 | if (entry != null) 36 | this.Entity = entry.Entity; 37 | } 38 | } 39 | 40 | 41 | protected ConcurrencyRepositoryViolationException(SerializationInfo info, StreamingContext context) 42 | : base(info, context) 43 | { 44 | this.Entity = info.GetValue(RepositoryEntityKey, typeof (object)); 45 | } 46 | 47 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 48 | { 49 | base.GetObjectData(info, context); 50 | info.AddValue(RepositoryEntityKey, this.Entity, typeof (string)); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/Exceptions/DataValidationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | 6 | namespace iQuarc.DataAccess 7 | { 8 | [Serializable] 9 | public class DataValidationException : RepositoryViolationException 10 | { 11 | private const string ErrorsKey = "ErrorsKey"; 12 | private readonly DataValidationResult[] errors = new DataValidationResult[0]; 13 | 14 | public DataValidationException() 15 | { 16 | } 17 | 18 | public DataValidationException(string message) 19 | : base(message) 20 | { 21 | } 22 | 23 | public DataValidationException(string message, Exception inner) 24 | : base(message, inner) 25 | { 26 | } 27 | 28 | public DataValidationException(string message, IEnumerable validationErrors) 29 | : this(message) 30 | { 31 | errors = validationErrors.ToArray(); 32 | } 33 | 34 | public DataValidationException(string message, IEnumerable validationErrors, Exception innerException) 35 | : this(message, innerException) 36 | { 37 | errors = validationErrors.ToArray(); 38 | } 39 | 40 | protected DataValidationException(SerializationInfo info, StreamingContext context) 41 | : base(info, context) 42 | { 43 | this.errors = (DataValidationResult[]) info.GetValue(ErrorsKey, typeof (DataValidationResult[])); 44 | } 45 | 46 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 47 | { 48 | base.GetObjectData(info, context); 49 | info.AddValue(ErrorsKey, errors); 50 | } 51 | 52 | public IEnumerable ValidationErrors 53 | { 54 | get { return errors; } 55 | } 56 | } 57 | 58 | [Serializable] 59 | public class DataValidationResult 60 | { 61 | private readonly List errors; 62 | 63 | public DataValidationResult(IEntityEntry entry, IEnumerable errors) 64 | { 65 | Entry = entry; 66 | this.errors = errors.ToList(); 67 | } 68 | 69 | public IEntityEntry Entry { get; private set; } 70 | 71 | public IEnumerable Errors 72 | { 73 | get { return errors; } 74 | } 75 | } 76 | 77 | [Serializable] 78 | public class ValidationError 79 | { 80 | public ValidationError(string propertyName, string errorMessage) 81 | { 82 | PropertyName = propertyName; 83 | ErrorMessage = errorMessage; 84 | } 85 | 86 | public string PropertyName { get; private set; } 87 | public string ErrorMessage { get; private set; } 88 | } 89 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/Exceptions/DateTimeRangeRepositoryViolationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using System.Runtime.Serialization; 4 | 5 | namespace iQuarc.DataAccess 6 | { 7 | [Serializable] 8 | public class DateTimeRangeRepositoryViolationException : RepositoryViolationException 9 | { 10 | public DateTimeRangeRepositoryViolationException() 11 | { 12 | } 13 | 14 | public DateTimeRangeRepositoryViolationException(string errorMessage) 15 | : base(errorMessage) 16 | { 17 | } 18 | 19 | public DateTimeRangeRepositoryViolationException(SqlException exception) 20 | : base(exception) 21 | { 22 | } 23 | 24 | public DateTimeRangeRepositoryViolationException(string message, Exception exception) 25 | : base(message, exception) 26 | { 27 | } 28 | 29 | protected DateTimeRangeRepositoryViolationException(SerializationInfo info, StreamingContext context) 30 | : base(info, context) 31 | { 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/Exceptions/DeadlockVictimRepositoryViolationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using System.Runtime.Serialization; 4 | 5 | namespace iQuarc.DataAccess 6 | { 7 | [Serializable] 8 | public class DeadlockVictimRepositoryViolationException : RepositoryViolationException 9 | { 10 | public DeadlockVictimRepositoryViolationException() 11 | { 12 | } 13 | 14 | public DeadlockVictimRepositoryViolationException(string errorMessage) 15 | : base(errorMessage) 16 | { 17 | } 18 | 19 | public DeadlockVictimRepositoryViolationException(SqlException exception) 20 | : base(exception) 21 | { 22 | } 23 | 24 | public DeadlockVictimRepositoryViolationException(string message, Exception exception) 25 | : base(message, exception) 26 | { 27 | } 28 | 29 | protected DeadlockVictimRepositoryViolationException(SerializationInfo info, StreamingContext context) 30 | : base(info, context) 31 | { 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/Exceptions/DeleteConstraintRepositoryViolationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using System.Runtime.Serialization; 4 | 5 | namespace iQuarc.DataAccess 6 | { 7 | [Serializable] 8 | public class DeleteConstraintRepositoryViolationException : RepositoryViolationException 9 | { 10 | public DeleteConstraintRepositoryViolationException() 11 | { 12 | } 13 | 14 | public DeleteConstraintRepositoryViolationException(string errorMessage) 15 | : base(errorMessage) 16 | { 17 | } 18 | 19 | public DeleteConstraintRepositoryViolationException(SqlException exception) 20 | : base(exception) 21 | { 22 | } 23 | 24 | public DeleteConstraintRepositoryViolationException(string message, Exception exception) 25 | : base(message, exception) 26 | { 27 | } 28 | 29 | protected DeleteConstraintRepositoryViolationException(SerializationInfo info, StreamingContext context) 30 | : base(info, context) 31 | { 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/Exceptions/RepositoryUpdateException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity.Core; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | 6 | namespace iQuarc.DataAccess 7 | { 8 | [Serializable] 9 | public class RepositoryUpdateException : RepositoryViolationException 10 | { 11 | private const string RepositoryEntityKey = "Entity"; 12 | public object Entity { get; set; } 13 | 14 | public RepositoryUpdateException() 15 | { 16 | } 17 | 18 | public RepositoryUpdateException(string errorMessage) 19 | : base(errorMessage) 20 | { 21 | } 22 | 23 | public RepositoryUpdateException(UpdateException exception) 24 | : this(string.Empty, exception) 25 | { 26 | } 27 | 28 | public RepositoryUpdateException(string message, UpdateException exception) 29 | : base(message, exception) 30 | { 31 | if (exception.StateEntries != null) 32 | { 33 | var entry = exception.StateEntries.FirstOrDefault(); 34 | if (entry != null) 35 | this.Entity = entry.Entity; 36 | } 37 | } 38 | 39 | protected RepositoryUpdateException(SerializationInfo info, StreamingContext context) 40 | : base(info, context) 41 | { 42 | this.Entity = info.GetValue(RepositoryEntityKey, typeof(object)); 43 | } 44 | 45 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 46 | { 47 | base.GetObjectData(info, context); 48 | info.AddValue(RepositoryEntityKey, this.Entity, typeof(string)); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/Exceptions/RepositoryViolationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace iQuarc.DataAccess 5 | { 6 | [Serializable] 7 | public class RepositoryViolationException : Exception 8 | { 9 | private const string ErrorMessageKey = "ErrorMessage"; 10 | private readonly string errorMessage; 11 | 12 | public string ErrorMessage 13 | { 14 | get { return this.errorMessage; } 15 | } 16 | 17 | public RepositoryViolationException() 18 | : this(string.Empty) 19 | { 20 | } 21 | 22 | public RepositoryViolationException(string errorMessage) 23 | : base(errorMessage) 24 | { 25 | this.errorMessage = errorMessage; 26 | } 27 | 28 | public RepositoryViolationException(Exception exception) 29 | : base(exception.Message, exception) 30 | { 31 | this.errorMessage = exception.Message; 32 | } 33 | 34 | public RepositoryViolationException(string message, Exception innerException) 35 | : base(message, innerException) 36 | { 37 | } 38 | 39 | protected RepositoryViolationException(SerializationInfo info, StreamingContext context) 40 | : base(info, context) 41 | { 42 | this.errorMessage = info.GetString(ErrorMessageKey); 43 | } 44 | 45 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 46 | { 47 | base.GetObjectData(info, context); 48 | info.AddValue(ErrorMessageKey, this.errorMessage, typeof(string)); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/Exceptions/UniqueConstraintRepositoryViolationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using System.Runtime.Serialization; 4 | 5 | namespace iQuarc.DataAccess 6 | { 7 | [Serializable] 8 | public class UniqueConstraintRepositoryViolationException : RepositoryViolationException 9 | { 10 | public UniqueConstraintRepositoryViolationException() 11 | { 12 | } 13 | 14 | public UniqueConstraintRepositoryViolationException(string errorMessage) 15 | : base(errorMessage) 16 | { 17 | } 18 | 19 | public UniqueConstraintRepositoryViolationException(SqlException exception) 20 | : base(exception) 21 | { 22 | } 23 | 24 | public UniqueConstraintRepositoryViolationException(string message, Exception exception) 25 | : base(message, exception) 26 | { 27 | } 28 | 29 | protected UniqueConstraintRepositoryViolationException(SerializationInfo info, StreamingContext context) 30 | : base(info, context) 31 | { 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/GlobalEntityInterceptor.cs: -------------------------------------------------------------------------------- 1 | namespace iQuarc.DataAccess 2 | { 3 | /// 4 | /// A template to build global entity interceptors. 5 | /// Concrete implementation of this should be registered as IEntityInterceptor and they will be applied to all entities 6 | /// of any type which inherits T 7 | /// 8 | /// The type which should be inherited / implemented by the entity that is going to be intercepted by this interceptor 9 | public abstract class GlobalEntityInterceptor : IEntityInterceptor 10 | where T : class 11 | { 12 | public abstract void OnLoad(IEntityEntry entry, IRepository repository); 13 | public abstract void OnSave(IEntityEntry entry, IUnitOfWork repository); 14 | public abstract void OnDelete(IEntityEntry entry, IUnitOfWork repository); 15 | 16 | void IEntityInterceptor.OnLoad(IEntityEntry entry, IRepository repository) 17 | { 18 | if (entry.Entity is T) 19 | this.OnLoad(entry.Convert(), repository); 20 | } 21 | 22 | void IEntityInterceptor.OnSave(IEntityEntry entry, IUnitOfWork repository) 23 | { 24 | if (entry.Entity is T) 25 | this.OnSave(entry.Convert(), repository); 26 | } 27 | 28 | void IEntityInterceptor.OnDelete(IEntityEntry entry, IUnitOfWork repository) 29 | { 30 | if (entry.Entity is T) 31 | this.OnDelete(entry.Convert(), repository); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/IDbContextFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace iQuarc.DataAccess 4 | { 5 | public interface IDbContextFactory 6 | { 7 | IDbContextWrapper CreateContext(); 8 | } 9 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/IDbContextUtilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Entity; 4 | using System.Data.Entity.Infrastructure; 5 | using System.Linq; 6 | 7 | namespace iQuarc.DataAccess 8 | { 9 | interface IDbContextUtilities 10 | { 11 | IEnumerable GetChangedEntities(DbContext context, Predicate statePredicate); 12 | IEntityEntry GetEntry(object entity, DbContext context); 13 | IEntityEntry GetEntry(T entity, DbContext context) where T:class; 14 | IEnumerable GetEntries(DbContext context); 15 | } 16 | 17 | class DbContextUtilities : IDbContextUtilities 18 | { 19 | public IEnumerable GetChangedEntities(DbContext context, Predicate statePredicate) 20 | { 21 | context.ChangeTracker.DetectChanges(); 22 | return context.ChangeTracker.Entries() 23 | .Where(entry => statePredicate(entry.State)) 24 | .Select(entity => new EntityEntry(entity)); 25 | } 26 | 27 | public IEntityEntry GetEntry(object entity, DbContext context) 28 | { 29 | DbEntityEntry dbEntry = context.Entry(entity); 30 | return new EntityEntry(dbEntry); 31 | } 32 | 33 | public IEntityEntry GetEntry(T entity, DbContext context) where T : class 34 | { 35 | DbEntityEntry dbEntityEntry = context.Entry(entity); 36 | return new EntityEntry(dbEntityEntry); 37 | } 38 | 39 | public IEnumerable GetEntries(DbContext context) 40 | { 41 | return context.ChangeTracker.Entries().Select(e => new EntityEntry(e)); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/IDbContextWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity; 3 | 4 | namespace iQuarc.DataAccess 5 | { 6 | public interface IDbContextWrapper : IDisposable 7 | { 8 | DbContext Context { get; } 9 | event EntityLoadedEventHandler EntityLoaded; 10 | } 11 | 12 | public delegate void EntityLoadedEventHandler(object sender, EntityLoadedEventHandlerArgs e); 13 | 14 | public class EntityLoadedEventHandlerArgs : EventArgs 15 | { 16 | public object Entity { get; private set; } 17 | 18 | public EntityLoadedEventHandlerArgs(object entity) 19 | { 20 | Entity = entity; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/IEntityEntry.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace iQuarc.DataAccess 4 | { 5 | public interface IEntityEntry 6 | where T : class 7 | { 8 | T Entity { get; } 9 | EntityEntryState State { get; set; } 10 | object GetOriginalValue(string propertyName); 11 | object GetCurrentValue(string propertyName); 12 | void SetOriginalValue(string propertyName, object value); 13 | void Reload(); 14 | IEnumerable GetProperties(); 15 | IPropertyEntry Property(string name); 16 | } 17 | 18 | public interface IEntityEntry 19 | { 20 | object Entity { get; } 21 | EntityEntryState State { get; set; } 22 | object GetOriginalValue(string propertyName); 23 | object GetCurrentValue(string propertyName); 24 | IEntityEntry Convert() where T : class; 25 | void SetOriginalValue(string propertyName, object value); 26 | void Reload(); 27 | IEnumerable GetProperties(); 28 | IPropertyEntry Property(string name); 29 | } 30 | 31 | public interface IPropertyEntry 32 | { 33 | string Name { get; } 34 | object CurentValue { get; set; } 35 | object OriginalValue { get; set; } 36 | bool IsModified { get; set; } 37 | } 38 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/IEntityInterceptor.cs: -------------------------------------------------------------------------------- 1 | namespace iQuarc.DataAccess 2 | { 3 | /// 4 | /// Defines an interceptor for a specific entity type. 5 | /// Any implementation registered into the Service Locator container with this interface as contract will be applied to 6 | /// entities of type T only 7 | /// 8 | /// The type of the entities this interceptor will intercept 9 | public interface IEntityInterceptor : IEntityInterceptor 10 | where T : class 11 | { 12 | /// 13 | /// Logic to execute after the entity was read from the database 14 | /// 15 | /// The entry that was read 16 | /// A reference to the repository that read this entry. It may be used to read additional data. 17 | void OnLoad(IEntityEntry entry, IRepository repository); 18 | 19 | /// 20 | /// Logic to execute before the entity is written into the database. This runs in the same transaction with the Save 21 | /// operation. 22 | /// This applies to Add, Update or Insert operation 23 | /// 24 | /// The entity being saved 25 | /// A reference to the unitOfWork that read this entry. It may be used to read additional data. 26 | void OnSave(IEntityEntry entry, IUnitOfWork unitOfWork); 27 | 28 | /// 29 | /// Logic to execute before the entity is deleted the database. This runs in the same transaction with the Save 30 | /// operation. 31 | /// 32 | /// The entity being deleted 33 | /// A reference to the unitOfWork that read this entry. It may be used to read additional data. 34 | void OnDelete(IEntityEntry entry, IUnitOfWork unitOfWork); 35 | } 36 | 37 | /// 38 | /// Defines a global entity interceptor. 39 | /// Any implementation registered into the Service Locator container with this interface as contract will be applied to 40 | /// all entities of any type 41 | /// 42 | public interface IEntityInterceptor 43 | { 44 | /// 45 | /// Logic to execute after the entity was read from the database 46 | /// 47 | /// The entry that was read 48 | /// A reference to the repository that read this entry. It may be used to read additional data. 49 | void OnLoad(IEntityEntry entry, IRepository repository); 50 | 51 | /// 52 | /// Logic to execute before the entity is written into the database. This runs in the same transaction with the Save 53 | /// operation. 54 | /// This applies to Add, Update or Insert operation 55 | /// 56 | /// The entity being saved 57 | /// A reference to the unitOfWork that read this entry. It may be used to read additional data. 58 | void OnSave(IEntityEntry entry, IUnitOfWork unitOfWork); 59 | 60 | /// 61 | /// Logic to execute before the entity is deleted the database. This runs in the same transaction with the Save 62 | /// operation. 63 | /// 64 | /// The entity being deleted 65 | /// A reference to the unitOfWork that read this entry. It may be used to read additional data. 66 | void OnDelete(IEntityEntry entry, IUnitOfWork unitOfWork); 67 | } 68 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/IInterceptorsResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace iQuarc.DataAccess 5 | { 6 | public interface IInterceptorsResolver 7 | { 8 | IEnumerable GetGlobalInterceptors(); 9 | IEnumerable GetEntityInterceptors(Type entityType); 10 | } 11 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/IRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace iQuarc.DataAccess 4 | { 5 | /// 6 | /// Generic repository contract for database read operations. 7 | /// 8 | public interface IRepository : IUnitOfWorkFactory 9 | { 10 | /// 11 | /// Gets the entities from the database. 12 | /// 13 | /// The type of the entities to be retrieved from the database. 14 | /// A for the entities from the database. 15 | IQueryable GetEntities() where T : class; 16 | } 17 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace iQuarc.DataAccess 7 | { 8 | /// 9 | /// A unit of work that allows to modify and save entities in the database 10 | /// 11 | public interface IUnitOfWork : IRepository, IDisposable 12 | { 13 | /// 14 | /// Saves the changes that were done on the entities on the current unit of work 15 | /// 16 | void SaveChanges(); 17 | 18 | /// 19 | /// Saves the changes that were done on the entities on the current unit of work 20 | /// 21 | Task SaveChangesAsync(); 22 | 23 | /// 24 | /// Adds to the current unit of work a new entity of type T 25 | /// 26 | /// Entity type 27 | /// The entity to be added 28 | void Add(T entity) where T : class; 29 | 30 | /// 31 | /// Deletes from the current unit of work an entity of type T 32 | /// 33 | /// Entity type 34 | /// The entity to be deleted 35 | void Delete(T entity) where T : class; 36 | 37 | 38 | /// 39 | /// Begins a TransactionScope with specified isolation level 40 | /// 41 | void BeginTransactionScope(SimplifiedIsolationLevel isolationLevel); 42 | 43 | /// 44 | /// Gets the for the given entity 45 | /// 46 | /// is thrown when is null 47 | /// Entity Type 48 | /// the entity instance 49 | /// and instance for the given entity 50 | IEntityEntry GetEntityEntry(T entity) 51 | where T : class; 52 | 53 | /// 54 | /// Returns all the entity entries tracked by this context 55 | /// 56 | /// 57 | IEnumerable GetEntityEntries(); 58 | } 59 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/IUnitOfWorkFactory.cs: -------------------------------------------------------------------------------- 1 | namespace iQuarc.DataAccess 2 | { 3 | public interface IUnitOfWorkFactory 4 | { 5 | /// 6 | /// Creates a new unit of work. 7 | /// 8 | /// 9 | IUnitOfWork CreateUnitOfWork(); 10 | } 11 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/InterceptorsResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using iQuarc.SystemEx.Priority; 5 | using Microsoft.Practices.ServiceLocation; 6 | 7 | namespace iQuarc.DataAccess 8 | { 9 | public class InterceptorsResolver : IInterceptorsResolver 10 | { 11 | private readonly IServiceLocator servicelocator; 12 | private static readonly Type interceptorGenericType = typeof(IEntityInterceptor<>); 13 | 14 | public InterceptorsResolver(IServiceLocator servicelocator) 15 | { 16 | this.servicelocator = servicelocator; 17 | } 18 | 19 | public IEnumerable GetGlobalInterceptors() 20 | { 21 | return servicelocator.GetAllInstances().OrderByPriority(); 22 | } 23 | 24 | public IEnumerable GetEntityInterceptors(Type entityType) 25 | { 26 | Type interceptorType = interceptorGenericType.MakeGenericType(entityType); 27 | return servicelocator.GetAllInstances(interceptorType).Cast(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("iQuarc.DataAccess")] 9 | [assembly: AssemblyDescription("Provides a DataAccess abstraction over a relational DB. The implementation is done with EF, which is well hiden from the clients")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("iQuarc")] 12 | [assembly: AssemblyProduct("iQuarc.DataAccess")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("70308d82-bb38-4dbd-a874-e66bacd83a72")] 24 | 25 | [assembly: InternalsVisibleTo("iQuarc.DataAccess.UnitTests")] 26 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 27 | 28 | // Versioning: 29 | // Adopting SemVer both for NuGet packages versions and also for Assembly Version (http://semver.org/ | https://docs.nuget.org/create/versioning). 30 | // We prefer nuspec to take the package version from assembly version. 31 | [assembly: AssemblyVersion(Version.Assembly)] 32 | [assembly: AssemblyFileVersion(Version.Assembly)] 33 | [assembly: AssemblyInformationalVersion(Version.NugetReleasePackage)] 34 | 35 | static class Version 36 | { 37 | /// 38 | /// Breaking changes. 39 | /// 40 | private const string Major = "1"; 41 | 42 | /// 43 | /// New features, but backwards compatible. 44 | /// 45 | private const string Minor = "0"; 46 | 47 | /// 48 | /// Backwards compatible bug fixes only. 49 | /// 50 | private const string Patch = "6"; 51 | 52 | /// 53 | /// Build number. Prefix with 0 for NuGet version ranges 54 | /// 55 | private const string Build = "0"; 56 | 57 | /// 58 | /// NuGet Pre-Release package versions 59 | /// 60 | private const string Prerelease = "beta"; 61 | 62 | /// 63 | /// Used to set the assembly version 64 | /// 65 | public const string Assembly = Major + "." + Minor + "." + Patch + "." + Build; 66 | 67 | /// 68 | /// Used to set the version of a release NuGet Package 69 | /// 70 | public const string NugetReleasePackage = Major + "." + Minor + "." + Patch; 71 | 72 | /// 73 | /// Used to set the version of a pre-release NuGet Package 74 | /// 75 | public const string NugetPrereleasePackage = Major + "." + Minor + "." + Patch + Prerelease + Build; 76 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/Repository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity; 3 | using System.Linq; 4 | 5 | namespace iQuarc.DataAccess 6 | { 7 | /// 8 | /// Implements a repository for reading data with Entity Framework 9 | /// The entities retrieved through this repository are not meant to be modified and persisted back. 10 | /// This implementation is optimized for read-only operations. For reading data for edit, or delete create and use an IUnitOfWork 11 | /// 12 | public class Repository : IRepository, IDisposable 13 | { 14 | private readonly IInterceptorsResolver interceptorsResolver; 15 | private readonly IDbContextFactory contextFactory; 16 | 17 | private readonly DbContextBuilder contextBuilder; 18 | 19 | public Repository(IDbContextFactory contextFactory, IInterceptorsResolver interceptorsResolver) 20 | : this(contextFactory, interceptorsResolver, new DbContextUtilities()) 21 | { 22 | } 23 | 24 | internal Repository(IDbContextFactory contextFactory, IInterceptorsResolver interceptorsResolver, IDbContextUtilities contextUtilities) 25 | { 26 | this.interceptorsResolver = interceptorsResolver; 27 | this.contextFactory = contextFactory; 28 | 29 | contextBuilder = new DbContextBuilder(contextFactory, interceptorsResolver, this, contextUtilities); 30 | } 31 | 32 | public IQueryable GetEntities() where T : class 33 | { 34 | return Context.Set().AsNoTracking(); 35 | } 36 | 37 | public IUnitOfWork CreateUnitOfWork() 38 | { 39 | return new UnitOfWork(contextFactory, interceptorsResolver); 40 | } 41 | 42 | protected DbContext Context 43 | { 44 | get { return contextBuilder.Context; } 45 | } 46 | 47 | public void Dispose() 48 | { 49 | Dispose(true); 50 | GC.SuppressFinalize(this); 51 | } 52 | 53 | protected virtual void Dispose(bool disposing) 54 | { 55 | if (disposing) 56 | { 57 | if (contextBuilder != null) 58 | contextBuilder.Dispose(); 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/SimplifiedIsolationLevel.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace iQuarc.DataAccess 4 | { 5 | [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] 6 | public enum SimplifiedIsolationLevel 7 | { 8 | /// 9 | /// Specifies the following: 10 | /// - Statements cannot read data that has been modified but not yet committed by other transactions. 11 | /// - No other transactions can modify data that has been read by the current transaction until the current transaction completes. 12 | /// - Other transactions cannot insert new rows with key values that would fall in the range of keys read by any statements in the current transaction until the current transaction completes. 13 | /// 14 | /// Range locks are placed in the range of key values that match the search conditions of each statement executed in a transaction. 15 | /// This blocks other transactions from updating or inserting any rows that would qualify for any of the statements executed by the current transaction. 16 | /// This means that if any of the statements in a transaction are executed a second time, they will read the same set of rows. 17 | /// The range locks are held until the transaction completes. 18 | /// This is the most restrictive of the isolation levels because it locks entire ranges of keys and holds the locks until the transaction completes. 19 | /// Because concurrency is lower, use this option only when necessary. 20 | /// 21 | Serializable = System.Transactions.IsolationLevel.Serializable, 22 | 23 | /// 24 | /// Specifies that statements cannot read data that has been modified but not yet committed by other transactions 25 | /// and that no other transactions can modify data that has been read by the current transaction until the current transaction completes. 26 | /// 27 | /// Shared locks are placed on all data read by each statement in the transaction and are held until the transaction completes. 28 | /// This prevents other transactions from modifying any rows that have been read by the current transaction. 29 | /// Other transactions can insert new rows that match the search conditions of statements issued by the current transaction. 30 | /// If the current transaction then retries the statement it will retrieve the new rows, which results in phantom reads. 31 | /// Because shared locks are held to the end of a transaction instead of being released at the end of each statement, concurrency is lower than the default READ COMMITTED isolation level. 32 | /// Use this option only when necessary. 33 | /// 34 | RepeatableRead = System.Transactions.IsolationLevel.RepeatableRead, 35 | 36 | /// 37 | /// Specifies that statements cannot read data that has been modified but not committed by other transactions. 38 | /// 39 | /// This prevents dirty reads. 40 | /// Data can be changed by other transactions between individual statements within the current transaction, 41 | /// resulting in nonrepeatable reads or phantom data. 42 | /// This option is the SQL Server default. 43 | /// 44 | ReadCommitted = System.Transactions.IsolationLevel.ReadCommitted, 45 | 46 | 47 | /// 48 | /// Specifies that data read by any statement in a transaction will be the transactionally consistent version of the data that existed at the start of the transaction. 49 | /// 50 | /// The transaction can only recognize data modifications that were committed before the start of the transaction. 51 | /// Data modifications made by other transactions after the start of the current transaction are not visible to statements executing in the current transaction. 52 | /// The effect is as if the statements in a transaction get a snapshot of the committed data as it existed at the start of the transaction. 53 | /// 54 | Snapshot = System.Transactions.IsolationLevel.Snapshot 55 | } 56 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/UnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Data.Entity; 6 | using System.Data.Entity.Core.Objects; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using System.Transactions; 10 | 11 | namespace iQuarc.DataAccess 12 | { 13 | /// 14 | /// Implements an unit of work for modifying, deleting or adding new data with Entity Framework. 15 | /// An instance of this class should have a well defined and short scope. It should be disposed once the changes were 16 | /// saved into the database 17 | /// 18 | public class UnitOfWork : IUnitOfWork 19 | { 20 | private readonly IEnumerable globalInterceptors; 21 | private readonly IInterceptorsResolver interceptorsResolver; 22 | private readonly IDbContextUtilities contextUtilities; 23 | 24 | private readonly DbContextBuilder contextBuilder; 25 | private DbContextTransaction transactionScope; 26 | 27 | private readonly IExceptionHandler exceptionHandler; 28 | 29 | internal UnitOfWork(IDbContextFactory contextFactory, IInterceptorsResolver interceptorsResolver) 30 | : this(contextFactory, interceptorsResolver, new DbContextUtilities(), new ExceptionHandler()) 31 | { 32 | } 33 | 34 | internal UnitOfWork(IDbContextFactory contextFactory, IInterceptorsResolver interceptorsResolver, IDbContextUtilities contextUtilities, IExceptionHandler exceptionHandler) 35 | { 36 | this.interceptorsResolver = interceptorsResolver; 37 | this.contextUtilities = contextUtilities; 38 | this.globalInterceptors = interceptorsResolver.GetGlobalInterceptors(); 39 | this.exceptionHandler = exceptionHandler; 40 | 41 | contextBuilder = new DbContextBuilder(contextFactory, interceptorsResolver, this, contextUtilities); 42 | } 43 | 44 | public IQueryable GetEntities() where T : class 45 | { 46 | return contextBuilder.Context.Set(); 47 | } 48 | 49 | public IUnitOfWork CreateUnitOfWork() 50 | { 51 | return this; 52 | } 53 | 54 | public void SaveChanges() 55 | { 56 | try 57 | { 58 | InterceptSave(new List()); 59 | contextBuilder.Context.SaveChanges(); 60 | if (transactionScope != null) 61 | transactionScope.Commit(); 62 | } 63 | catch (Exception e) 64 | { 65 | Handle(e); 66 | } 67 | } 68 | 69 | private void Handle(Exception exception) 70 | { 71 | exceptionHandler.Handle(exception); 72 | } 73 | 74 | public async Task SaveChangesAsync() 75 | { 76 | try 77 | { 78 | InterceptSave(new List()); 79 | 80 | await contextBuilder.Context.SaveChangesAsync(); 81 | 82 | if (transactionScope != null) 83 | { 84 | transactionScope.Commit(); 85 | } 86 | } 87 | catch (Exception e) 88 | { 89 | Handle(e); 90 | } 91 | } 92 | 93 | private void InterceptSave(List interceptedEntities) 94 | { 95 | List modifiedAndNotIntercepted = GetModifiedEntities(contextBuilder.Context) 96 | .Where(e => !interceptedEntities.Contains(e.Entity)).ToList(); 97 | 98 | if (modifiedAndNotIntercepted.Count == 0) 99 | return; 100 | 101 | foreach (IEntityEntry entry in modifiedAndNotIntercepted) 102 | { 103 | object entity = entry.Entity; 104 | 105 | Type entityType = ObjectContext.GetObjectType(entity.GetType()); 106 | IEnumerable entityInterceptors = interceptorsResolver.GetEntityInterceptors(entityType); 107 | 108 | if (entry.State == EntityEntryState.Deleted) 109 | { 110 | Intercept(globalInterceptors, entity, (i, e) => i.OnDelete(e, this)); 111 | Intercept(entityInterceptors, entity, (i, e) => i.OnDelete(e, this)); 112 | } 113 | else 114 | { 115 | Intercept(globalInterceptors, entity, (i, e) => i.OnSave(e, this)); 116 | Intercept(entityInterceptors, entity, (i, e) => i.OnSave(e, this)); 117 | } 118 | 119 | interceptedEntities.AddIfNotExists(entity); 120 | } 121 | 122 | InterceptSave(interceptedEntities); 123 | } 124 | 125 | private IEnumerable GetModifiedEntities(DbContext context) 126 | { 127 | IEnumerable modifiedEntities = contextUtilities.GetChangedEntities(context, 128 | s => s == EntityState.Added || s == EntityState.Modified || s == EntityState.Deleted); 129 | return modifiedEntities; 130 | } 131 | 132 | private void Intercept(IEnumerable interceptors, object entity, Action intercept) 133 | { 134 | IEntityEntry entry = contextUtilities.GetEntry(entity, contextBuilder.Context); 135 | foreach (var interceptor in interceptors) 136 | { 137 | intercept(interceptor, entry); 138 | } 139 | } 140 | 141 | public void Add(T entity) where T : class 142 | { 143 | contextBuilder.Context.Set().Add(entity); 144 | } 145 | 146 | public void Delete(T entity) where T : class 147 | { 148 | contextBuilder.Context.Set().Remove(entity); 149 | } 150 | 151 | public void BeginTransactionScope(SimplifiedIsolationLevel isolationLevel) 152 | { 153 | if (transactionScope != null) 154 | throw new InvalidOperationException("Cannot begin another transaction scope"); 155 | 156 | transactionScope = contextBuilder.Context.Database.BeginTransaction((System.Data.IsolationLevel)isolationLevel); 157 | } 158 | 159 | public IEntityEntry GetEntityEntry(T entity) where T : class 160 | { 161 | return contextUtilities.GetEntry(entity, contextBuilder.Context); 162 | } 163 | 164 | public IEnumerable GetEntityEntries() 165 | { 166 | return contextUtilities.GetEntries(contextBuilder.Context); 167 | } 168 | 169 | public void Dispose() 170 | { 171 | Dispose(true); 172 | GC.SuppressFinalize(this); 173 | } 174 | 175 | protected virtual void Dispose(bool disposing) 176 | { 177 | if (disposing) 178 | { 179 | if (transactionScope != null) 180 | transactionScope.Dispose(); 181 | 182 | if (contextBuilder != null) 183 | contextBuilder.Dispose(); 184 | } 185 | } 186 | } 187 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/UnitOfWorkFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Practices.ServiceLocation; 2 | 3 | namespace iQuarc.DataAccess 4 | { 5 | public class UnitOfWorkFactory : IUnitOfWorkFactory 6 | { 7 | private readonly IDbContextFactory contextFactory; 8 | private readonly IInterceptorsResolver interceptorsResolver; 9 | 10 | public UnitOfWorkFactory(IServiceLocator serviceLocator) 11 | { 12 | contextFactory = serviceLocator.GetInstance(); 13 | interceptorsResolver = serviceLocator.GetInstance(); 14 | } 15 | 16 | public UnitOfWorkFactory(IDbContextFactory contextFactory, IInterceptorsResolver interceptorsResolver) 17 | { 18 | this.contextFactory = contextFactory; 19 | this.interceptorsResolver = interceptorsResolver; 20 | } 21 | 22 | public IUnitOfWork CreateUnitOfWork() 23 | { 24 | return new UnitOfWork(contextFactory, interceptorsResolver); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/iQuarc.DataAccess.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {53FBDBE7-3B53-4FC1-9676-1932C6CE6A2F} 8 | Library 9 | Properties 10 | iQuarc.DataAccess 11 | iQuarc.DataAccess 12 | v4.5 13 | 512 14 | ..\ 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll 36 | True 37 | 38 | 39 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll 40 | True 41 | 42 | 43 | ..\packages\iQuarc.SystemEx.1.0.0.0\lib\net40\iQuarc.SystemEx.dll 44 | 45 | 46 | ..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll 47 | 48 | 49 | 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 | 107 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/iQuarc.DataAccess.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | false 10 | $description$ 11 | $description$ 12 | Basic functionality for hiding EF and for creating an Unit of Work 13 | Copyright 2014 14 | DataAccess Repository UnitOfWork EntityFramework ORM 15 | https://raw.githubusercontent.com/iQuarc/DataAccess/master/LICENSE 16 | https://github.com/iQuarc/DataAccess 17 | http://iquarc.com/images/iquarc-icon-BW-32x32.png 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/make-package.bat: -------------------------------------------------------------------------------- 1 | c:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe iQuarc.DataAccess.csproj /p:Configuration=Release 2 | nuget pack iQuarc.DataAccess.csproj -Prop Configuration=Release -------------------------------------------------------------------------------- /src/iQuarc.DataAccess/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | --------------------------------------------------------------------------------