[
  {
    "path": ".gitattributes",
    "content": "###############################################################################\n# Set default behavior to automatically normalize line endings.\n###############################################################################\n* text=auto\n\n###############################################################################\n# Set default behavior for command prompt diff.\n#\n# This is need for earlier builds of msysgit that does not have it on by\n# default for csharp files.\n# Note: This is only used by command line\n###############################################################################\n#*.cs     diff=csharp\n\n###############################################################################\n# Set the merge driver for project and solution files\n#\n# Merging from the command prompt will add diff markers to the files if there\n# are conflicts (Merging from VS is not affected by the settings below, in VS\n# the diff markers are never inserted). Diff markers may cause the following \n# file extensions to fail to load in VS. An alternative would be to treat\n# these files as binary and thus will always conflict and require user\n# intervention with every merge. To do so, just uncomment the entries below\n###############################################################################\n#*.sln       merge=binary\n#*.csproj    merge=binary\n#*.vbproj    merge=binary\n#*.vcxproj   merge=binary\n#*.vcproj    merge=binary\n#*.dbproj    merge=binary\n#*.fsproj    merge=binary\n#*.lsproj    merge=binary\n#*.wixproj   merge=binary\n#*.modelproj merge=binary\n#*.sqlproj   merge=binary\n#*.wwaproj   merge=binary\n\n###############################################################################\n# behavior for image files\n#\n# image files are treated as binary by default.\n###############################################################################\n#*.jpg   binary\n#*.png   binary\n#*.gif   binary\n\n###############################################################################\n# diff behavior for common document formats\n# \n# Convert binary document formats to text before diffing them. This feature\n# is only available from the command line. Turn it on by uncommenting the \n# entries below.\n###############################################################################\n#*.doc   diff=astextplain\n#*.DOC   diff=astextplain\n#*.docx  diff=astextplain\n#*.DOCX  diff=astextplain\n#*.dot   diff=astextplain\n#*.DOT   diff=astextplain\n#*.pdf   diff=astextplain\n#*.PDF   diff=astextplain\n#*.rtf   diff=astextplain\n#*.RTF   diff=astextplain\n"
  },
  {
    "path": ".github/issue_template.md",
    "content": "# Summary of the issue\n\n\n\n## Environment\n\n```\n.NET Core SDK version: \nMicrosoft.EntityFrameworkCore version: \nEFSecondLevelCache.Core version: \n```\n\n## Example code/Steps to reproduce:\n\n```\npaste your core code\n```\n\n## Output:\n\n```\nException message:\nFull Stack trace:\n```\n\n"
  },
  {
    "path": ".github/lock.yml",
    "content": "daysUntilLock: 90\n\nskipCreatedBefore: false\n\nexemptLabels: []\n\nlockLabel: false\n\nlockComment: >\n  This thread has been automatically locked since there has not been\n  any recent activity after it was closed. Please open a new issue for\n  related problems.\n  \nsetLockReason: true\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: .NET Core Build\n\non: [push, pull_request]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v1\n    - name: Setup .NET Core\n      uses: actions/setup-dotnet@v1\n      with:\n        dotnet-version: 3.1.101\n    - name: Build DNTCaptcha.Core lib\n      run: dotnet build ./src/EFSecondLevelCache.Core/EFSecondLevelCache.Core.csproj --configuration Release"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\n[Xx]64/\n[Xx]86/\n[Bb]uild/\nbld/\n[Bb]in/\n[Oo]bj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# DNX\nproject.lock.json\nartifacts/\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n\n# TODO: Un-comment the next line if you do not want to checkin \n# your web deploy settings because they may include unencrypted\n# passwords\n#*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n# NuGet v3's project.json files produces more ignoreable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n[Ss]tyle[Cc]op.*\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\nnode_modules/\norleans.codegen.cs\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# LightSwitch generated files\nGeneratedArtifacts/\nModelManifest.xml\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n/.idea\n/src/Tests/EFSecondLevelCache.Core.PerformanceTests/BenchmarkDotNet.Artifacts"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n    // Use IntelliSense to find out which attributes exist for C# debugging\n    // Use hover for the description of the existing attributes\n    // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"name\": \".NET Core Launch (console)-Issue43\",\n            \"type\": \"coreclr\",\n            \"request\": \"launch\",\n            \"preLaunchTask\": \"build\",\n            \"program\": \"${workspaceFolder}/src/Tests/Issues/Issue43/bin/Debug/netcoreapp3.1/Issue43.dll\",\n            \"args\": [],\n            \"cwd\": \"${workspaceFolder}\",\n            \"stopAtEntry\": false,\n            \"console\": \"internalConsole\"\n        },\n        {\n            \"name\": \".NET Core Launch (web)\",\n            \"type\": \"coreclr\",\n            \"request\": \"launch\",\n            \"preLaunchTask\": \"build\",\n            // If you have changed target frameworks, make sure to update the program path.\n            \"program\": \"${workspaceRoot}/src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/bin/Debug/netcoreapp3.1/EFSecondLevelCache.Core.AspNetCoreSample.dll\",\n            \"args\": [],\n            \"cwd\": \"${workspaceRoot}/src/Tests/EFSecondLevelCache.Core.AspNetCoreSample\",\n            \"stopAtEntry\": false,\n            \"internalConsoleOptions\": \"openOnSessionStart\",\n            \"launchBrowser\": {\n                \"enabled\": true,\n                \"args\": \"${auto-detect-url}\",\n                \"windows\": {\n                    \"command\": \"cmd.exe\",\n                    \"args\": \"/C start ${auto-detect-url}\"\n                },\n                \"osx\": {\n                    \"command\": \"open\"\n                },\n                \"linux\": {\n                    \"command\": \"xdg-open\"\n                }\n            },\n            \"env\": {\n                \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n            },\n            \"sourceFileMap\": {\n                \"/Views\": \"${workspaceRoot}/Views\"\n            }\n        },\n        {\n            \"name\": \".NET Core Attach\",\n            \"type\": \"coreclr\",\n            \"request\": \"attach\",\n            \"processId\": \"${command:pickProcess}\"\n        }\n    ]\n}"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"workbench.colorCustomizations\": {\n        \"activityBar.background\": \"#7295b1\",\n        \"activityBar.activeBorder\": \"#e8d6e0\",\n        \"activityBar.foreground\": \"#15202b\",\n        \"activityBar.inactiveForeground\": \"#15202b99\",\n        \"activityBarBadge.background\": \"#e8d6e0\",\n        \"activityBarBadge.foreground\": \"#15202b\",\n        \"titleBar.activeBackground\": \"#557c9b\",\n        \"titleBar.inactiveBackground\": \"#557c9b99\",\n        \"titleBar.activeForeground\": \"#e7e7e7\",\n        \"titleBar.inactiveForeground\": \"#e7e7e799\",\n        \"statusBar.background\": \"#557c9b\",\n        \"statusBarItem.hoverBackground\": \"#7295b1\",\n        \"statusBar.foreground\": \"#e7e7e7\"\n    },\n    \"peacock.color\": \"#557c9b\"\n}"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\n    \"version\": \"0.1.0\",\n    \"command\": \"dotnet\",\n    \"isShellCommand\": true,\n    \"args\": [],\n    \"tasks\": [\n        {\n            \"taskName\": \"build\",\n            \"args\": [\n                \"${workspaceRoot}/src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/EFSecondLevelCache.Core.AspNetCoreSample.csproj\"\n            ],\n            \"isBuildCommand\": true,\n            \"problemMatcher\": \"$msCompile\"\n        }\n    ]\n}"
  },
  {
    "path": "EFSecondLevelCache.Core.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 15\nVisualStudioVersion = 15.0.26228.4\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"src\", \"src\", \"{0C0EA45E-C765-4D41-A1DA-EDDC8F239A5B}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Solution Items\", \"Solution Items\", \"{312DDEFA-6800-4297-9B38-0C458BB28FED}\"\n\tProjectSection(SolutionItems) = preProject\n\t\tLICENSE.md = LICENSE.md\n\t\tREADME.md = README.md\n\tEndProjectSection\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"EFSecondLevelCache.Core\", \"src\\EFSecondLevelCache.Core\\EFSecondLevelCache.Core.csproj\", \"{755D6A18-E67A-4E14-8289-BD33A901C52B}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"EFSecondLevelCache.Core.Tests\", \"src\\Tests\\EFSecondLevelCache.Core.Tests\\EFSecondLevelCache.Core.Tests.csproj\", \"{5475D985-85D6-4DD2-899E-8E8B333B0070}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"EFSecondLevelCache.Core.AspNetCoreSample\", \"src\\Tests\\EFSecondLevelCache.Core.AspNetCoreSample\\EFSecondLevelCache.Core.AspNetCoreSample.csproj\", \"{EB8B44EC-D677-47B3-8A72-3E340BFDF0DE}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"EFSecondLevelCache.Core.NET46Sample\", \"src\\Tests\\EFSecondLevelCache.Core.NET46Sample\\EFSecondLevelCache.Core.NET46Sample\\EFSecondLevelCache.Core.NET46Sample.csproj\", \"{CEE7F26B-7A7E-4021-A37F-F9625EAF85CE}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Tests\", \"Tests\", \"{4D31E9D7-B686-44FF-8BCD-C17BA26A0333}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Issues\", \"Issues\", \"{EDE35C41-BFB7-492F-A8FD-C13FB276486C}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Issue15\", \"src\\Tests\\Issues\\Issue15\\Issue15.csproj\", \"{767215E4-AF19-4AD0-968C-532BE14AF75A}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"EFSecondLevelCache.Core.PerformanceTests\", \"src\\Tests\\EFSecondLevelCache.Core.PerformanceTests\\EFSecondLevelCache.Core.PerformanceTests.csproj\", \"{01DE3CF0-AA97-43B3-B95C-45184DF223CB}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Issue43\", \"src\\Tests\\Issues\\Issue43\\Issue43.csproj\", \"{65D28CEA-604F-4283-BDEE-9D6B669A37E1}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\t\tDebug|x64 = Debug|x64\n\t\tDebug|x86 = Debug|x86\n\t\tRelease|x64 = Release|x64\n\t\tRelease|x86 = Release|x86\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{755D6A18-E67A-4E14-8289-BD33A901C52B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{755D6A18-E67A-4E14-8289-BD33A901C52B}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{755D6A18-E67A-4E14-8289-BD33A901C52B}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{755D6A18-E67A-4E14-8289-BD33A901C52B}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{5475D985-85D6-4DD2-899E-8E8B333B0070}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{5475D985-85D6-4DD2-899E-8E8B333B0070}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{5475D985-85D6-4DD2-899E-8E8B333B0070}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{5475D985-85D6-4DD2-899E-8E8B333B0070}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{EB8B44EC-D677-47B3-8A72-3E340BFDF0DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{EB8B44EC-D677-47B3-8A72-3E340BFDF0DE}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{EB8B44EC-D677-47B3-8A72-3E340BFDF0DE}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{EB8B44EC-D677-47B3-8A72-3E340BFDF0DE}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{CEE7F26B-7A7E-4021-A37F-F9625EAF85CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{CEE7F26B-7A7E-4021-A37F-F9625EAF85CE}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{CEE7F26B-7A7E-4021-A37F-F9625EAF85CE}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{CEE7F26B-7A7E-4021-A37F-F9625EAF85CE}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{767215E4-AF19-4AD0-968C-532BE14AF75A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{767215E4-AF19-4AD0-968C-532BE14AF75A}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{767215E4-AF19-4AD0-968C-532BE14AF75A}.Debug|x64.ActiveCfg = Debug|x64\n\t\t{767215E4-AF19-4AD0-968C-532BE14AF75A}.Debug|x64.Build.0 = Debug|x64\n\t\t{767215E4-AF19-4AD0-968C-532BE14AF75A}.Debug|x86.ActiveCfg = Debug|x86\n\t\t{767215E4-AF19-4AD0-968C-532BE14AF75A}.Debug|x86.Build.0 = Debug|x86\n\t\t{767215E4-AF19-4AD0-968C-532BE14AF75A}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{767215E4-AF19-4AD0-968C-532BE14AF75A}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{767215E4-AF19-4AD0-968C-532BE14AF75A}.Release|x64.ActiveCfg = Release|x64\n\t\t{767215E4-AF19-4AD0-968C-532BE14AF75A}.Release|x64.Build.0 = Release|x64\n\t\t{767215E4-AF19-4AD0-968C-532BE14AF75A}.Release|x86.ActiveCfg = Release|x86\n\t\t{767215E4-AF19-4AD0-968C-532BE14AF75A}.Release|x86.Build.0 = Release|x86\n\t\t{01DE3CF0-AA97-43B3-B95C-45184DF223CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{01DE3CF0-AA97-43B3-B95C-45184DF223CB}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{01DE3CF0-AA97-43B3-B95C-45184DF223CB}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{01DE3CF0-AA97-43B3-B95C-45184DF223CB}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{01DE3CF0-AA97-43B3-B95C-45184DF223CB}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{01DE3CF0-AA97-43B3-B95C-45184DF223CB}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{01DE3CF0-AA97-43B3-B95C-45184DF223CB}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{01DE3CF0-AA97-43B3-B95C-45184DF223CB}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{01DE3CF0-AA97-43B3-B95C-45184DF223CB}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{01DE3CF0-AA97-43B3-B95C-45184DF223CB}.Release|x64.Build.0 = Release|Any CPU\n\t\t{01DE3CF0-AA97-43B3-B95C-45184DF223CB}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{01DE3CF0-AA97-43B3-B95C-45184DF223CB}.Release|x86.Build.0 = Release|Any CPU\n\t\t{65D28CEA-604F-4283-BDEE-9D6B669A37E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{65D28CEA-604F-4283-BDEE-9D6B669A37E1}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{65D28CEA-604F-4283-BDEE-9D6B669A37E1}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{65D28CEA-604F-4283-BDEE-9D6B669A37E1}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{65D28CEA-604F-4283-BDEE-9D6B669A37E1}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{65D28CEA-604F-4283-BDEE-9D6B669A37E1}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{65D28CEA-604F-4283-BDEE-9D6B669A37E1}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{65D28CEA-604F-4283-BDEE-9D6B669A37E1}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{65D28CEA-604F-4283-BDEE-9D6B669A37E1}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{65D28CEA-604F-4283-BDEE-9D6B669A37E1}.Release|x64.Build.0 = Release|Any CPU\n\t\t{65D28CEA-604F-4283-BDEE-9D6B669A37E1}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{65D28CEA-604F-4283-BDEE-9D6B669A37E1}.Release|x86.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(NestedProjects) = preSolution\n\t\t{755D6A18-E67A-4E14-8289-BD33A901C52B} = {0C0EA45E-C765-4D41-A1DA-EDDC8F239A5B}\n\t\t{5475D985-85D6-4DD2-899E-8E8B333B0070} = {0C0EA45E-C765-4D41-A1DA-EDDC8F239A5B}\n\t\t{EB8B44EC-D677-47B3-8A72-3E340BFDF0DE} = {0C0EA45E-C765-4D41-A1DA-EDDC8F239A5B}\n\t\t{CEE7F26B-7A7E-4021-A37F-F9625EAF85CE} = {0C0EA45E-C765-4D41-A1DA-EDDC8F239A5B}\n\t\t{4D31E9D7-B686-44FF-8BCD-C17BA26A0333} = {0C0EA45E-C765-4D41-A1DA-EDDC8F239A5B}\n\t\t{EDE35C41-BFB7-492F-A8FD-C13FB276486C} = {4D31E9D7-B686-44FF-8BCD-C17BA26A0333}\n\t\t{767215E4-AF19-4AD0-968C-532BE14AF75A} = {EDE35C41-BFB7-492F-A8FD-C13FB276486C}\n\t\t{01DE3CF0-AA97-43B3-B95C-45184DF223CB} = {4D31E9D7-B686-44FF-8BCD-C17BA26A0333}\n\t\t{65D28CEA-604F-4283-BDEE-9D6B669A37E1} = {EDE35C41-BFB7-492F-A8FD-C13FB276486C}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n"
  },
  {
    "path": "README.md",
    "content": "# [Announcing a better Second Level Caching Library!](https://github.com/VahidN/EFSecondLevelCache.Core/issues/67)\r\n\r\n# EFSecondLevelCache.Core\r\n\r\n<p align=\"left\">\r\n  <a href=\"https://github.com/VahidN/EFSecondLevelCache.Core\">\r\n     <img alt=\"GitHub Actions status\" src=\"https://github.com/VahidN/EFSecondLevelCache.Core/workflows/.NET%20Core%20Build/badge.svg\">\r\n  </a>\r\n</p>\r\n\r\nEntity Framework Core Second Level Caching Library.\r\n\r\nSecond level caching is a query cache. The results of EF commands will be stored in the cache, so that the same EF commands will retrieve their data from the cache rather than executing them against the database again.\r\n\r\n## Install via NuGet\r\n\r\nTo install EFSecondLevelCache.Core, run the following command in the Package Manager Console:\r\n\r\n[![Nuget](https://img.shields.io/nuget/v/EFSecondLevelCache.Core)](https://github.com/VahidN/EFSecondLevelCache.Core)\r\n\r\n```\r\nPM> Install-Package EFSecondLevelCache.Core\r\n```\r\n\r\nYou can also view the [package page](http://www.nuget.org/packages/EFSecondLevelCache.Core/) on NuGet.\r\n\r\nThis library also uses the [CacheManager.Core](https://github.com/MichaCo/CacheManager), as a highly configurable cache manager.\r\nTo use its in-memory caching mechanism, add these entries to the `.csproj` file:\r\n\r\n```xml\r\n  <ItemGroup>\r\n    <PackageReference Include=\"EFSecondLevelCache.Core\" Version=\"2.9.0\" />\r\n    <PackageReference Include=\"CacheManager.Core\" Version=\"1.2.0\" />\r\n    <PackageReference Include=\"CacheManager.Microsoft.Extensions.Caching.Memory\" Version=\"1.2.0\" />\r\n    <PackageReference Include=\"CacheManager.Serialization.Json\" Version=\"1.2.0\" />\r\n  </ItemGroup>\r\n```\r\n\r\nAnd to get the latest versions of these libraries you can run the following command in the Package Manager Console:\r\n\r\n```\r\nPM> Update-Package\r\n```\r\n\r\n## Usage\r\n\r\n1- [Register the required services](/src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/Startup.cs) of `EFSecondLevelCache.Core` and also `CacheManager.Core`\r\n\r\n```csharp\r\nnamespace EFSecondLevelCache.Core.AspNetCoreSample\r\n{\r\n    public class Startup\r\n    {\r\n        public void ConfigureServices(IServiceCollection services)\r\n        {\r\n            services.AddEFSecondLevelCache();\r\n\r\n            // Add an in-memory cache service provider\r\n            services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));\r\n            services.AddSingleton(typeof(ICacheManagerConfiguration),\r\n                new CacheManager.Core.ConfigurationBuilder()\r\n                        .WithJsonSerializer()\r\n                        .WithMicrosoftMemoryCacheHandle(instanceName: \"MemoryCache1\")\r\n                        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))\r\n                        .Build());\r\n        }\r\n    }\r\n}\r\n```\r\n\r\nIf you want to use the Redis as the preferred cache provider, first install the `CacheManager.StackExchange.Redis` package and then register its required services:\r\n\r\n```csharp\r\n// Add Redis cache service provider\r\nvar jss = new JsonSerializerSettings\r\n{\r\n    NullValueHandling = NullValueHandling.Ignore,\r\n    ReferenceLoopHandling = ReferenceLoopHandling.Ignore\r\n};\r\n\r\nconst string redisConfigurationKey = \"redis\";\r\nservices.AddSingleton(typeof(ICacheManagerConfiguration),\r\n    new CacheManager.Core.ConfigurationBuilder()\r\n        .WithJsonSerializer(serializationSettings: jss, deserializationSettings: jss)\r\n        .WithUpdateMode(CacheUpdateMode.Up)\r\n        .WithRedisConfiguration(redisConfigurationKey, config =>\r\n        {\r\n            config.WithAllowAdmin()\r\n                .WithDatabase(0)\r\n                .WithEndpoint(\"localhost\", 6379)\r\n                // Enables keyspace notifications to react on eviction/expiration of items.\r\n                // Make sure that all servers are configured correctly and 'notify-keyspace-events' is at least set to 'Exe', otherwise CacheManager will not retrieve any events.\r\n                // See https://redis.io/topics/notifications#configuration for configuration details.\r\n                .EnableKeyspaceEvents();\r\n        })\r\n        .WithMaxRetries(100)\r\n        .WithRetryTimeout(50)\r\n        .WithRedisCacheHandle(redisConfigurationKey)\r\n        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))\r\n        .Build());\r\nservices.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));\r\n```\r\n\r\n2- [Setting up the cache invalidation](/src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/DataLayer/SampleContext.cs) by overriding the SaveChanges method to prevent stale reads:\r\n\r\n```csharp\r\nnamespace EFSecondLevelCache.Core.AspNetCoreSample.DataLayer\r\n{\r\n    public class SampleContext : DbContext\r\n    {\r\n        public SampleContext(DbContextOptions<SampleContext> options) : base(options)\r\n        { }\r\n\r\n        public virtual DbSet<Post> Posts { get; set; }\r\n\r\n        public override int SaveChanges()\r\n        {\r\n            var changedEntityNames = this.GetChangedEntityNames();\r\n\r\n            this.ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again.\r\n            var result = base.SaveChanges();\r\n            this.ChangeTracker.AutoDetectChangesEnabled = true;\r\n\r\n            this.GetService<IEFCacheServiceProvider>().InvalidateCacheDependencies(changedEntityNames);\r\n\r\n            return result;\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n3- Then to cache the results of the normal queries like:\r\n\r\n```csharp\r\nvar products = context.Products.Include(x => x.Tags).FirstOrDefault();\r\n```\r\n\r\nWe can use the new `Cacheable()` extension method:\r\n\r\n```csharp\r\n// If you don't specify the `EFCachePolicy`, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.\r\nvar products = context.Products.Include(x => x.Tags).Cacheable().FirstOrDefault(); // Async methods are supported too.\r\n\r\n// Or you can specify the `EFCachePolicy` explicitly to override the global settings.\r\nvar post1 = context.Posts\r\n                   .Where(x => x.Id > 0)\r\n                   .OrderBy(x => x.Id)\r\n                   .Cacheable(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(5))\r\n                   .FirstOrDefault();\r\n\r\n// NOTE: It's better to add the `Cacheable()` method before the materialization methods such as `ToList()` or `FirstOrDefault()` to cover the whole expression tree.\r\n```\r\n\r\nAlso AutoMapper's `ProjectTo()` method is supported:\r\n\r\n```csharp\r\nvar posts = context.Posts\r\n                   .Where(x => x.Id > 0)\r\n                   .OrderBy(x => x.Id)\r\n                   .Cacheable()\r\n                   .ProjectTo<PostDto>(configuration: _mapper.ConfigurationProvider)\r\n                   .ToList();\r\n```\r\n\r\n## Guidance\r\n\r\n### When to use\r\n\r\nGood candidates for query caching are global site settings and public data, such as infrequently changing articles or comments. It can also be beneficial to cache data specific to a user so long as the cache expires frequently enough relative to the size of the user base that memory consumption remains acceptable. Small, per-user data that frequently exceeds the cache's lifetime, such as a user's photo path, is better held in user claims, which are stored in cookies, than in this cache.\r\n\r\n### Scope\r\n\r\nThis cache is scoped to the application, not the current user. It does not use session variables. Accordingly, when retriveing cached per-user data, be sure queries in include code such as `.Where(x => .... && x.UserId == id)`.\r\n\r\n### Invalidation\r\n\r\nThis cache is updated when an entity is changed (insert, update, or delete) via a DbContext that uses this library. If the database is updated through some other means, such as a stored procedure or trigger, the cache becomes stale.\r\n"
  },
  {
    "path": "global.json",
    "content": "{\n  \"sdk\": {\n    \"version\": \"3.1.101\"\n  }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/Contracts/EFCacheDebugInfo.cs",
    "content": "﻿namespace EFSecondLevelCache.Core.Contracts\n{\n    /// <summary>\n    /// Stores the debug information of the caching process.\n    /// </summary>\n    public class EFCacheDebugInfo\n    {\n        /// <summary>\n        /// Stores information of the computed key of the input LINQ query.\n        /// </summary>\n        public EFCacheKey EFCacheKey { set; get; }\n\n        /// <summary>\n        /// Determines this query is using the 2nd level cache or not.\n        /// </summary>\n        public bool IsCacheHit { set; get; }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/Contracts/EFCacheKey.cs",
    "content": "using System.Collections.Generic;\n\nnamespace EFSecondLevelCache.Core.Contracts\n{\n    /// <summary>\n    /// Stores information of the computed key of the input LINQ query.\n    /// </summary>\n    public class EFCacheKey\n    {\n        /// <summary>\n        /// The computed key of the input LINQ query.\n        /// </summary>\n        public string Key { set; get; }\n\n        /// <summary>\n        /// Hash of the input LINQ query's computed key.\n        /// </summary>\n        public string KeyHash { set; get; }\n\n        /// <summary>\n        /// Determines which entities are used in this LINQ query.\n        /// This array will be used to invalidate the related cache of all related queries automatically.\n        /// </summary>\n        public ISet<string> CacheDependencies { set; get; }\n\n        /// <summary>\n        /// Stores information of the computed key of the input LINQ query.\n        /// </summary>\n        public EFCacheKey()\n        {\n            CacheDependencies = new HashSet<string>();\n        }\n\n        /// <summary>\n        /// Equals\n        /// </summary>\n        /// <param name=\"obj\"></param>\n        /// <returns></returns>\n        public override bool Equals(object obj)\n        {\n            var efCacheKey = obj as EFCacheKey;\n            if (efCacheKey == null)\n                return false;\n\n            return this.KeyHash == efCacheKey.KeyHash;\n        }\n\n        /// <summary>\n        /// GetHashCode\n        /// </summary>\n        /// <returns></returns>\n        public override int GetHashCode()\n        {\n            unchecked\n            {\n                var hash = 17;\n                hash = hash * 23 + KeyHash.GetHashCode();\n                return hash;\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/Contracts/EFQueryDebugView.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace EFSecondLevelCache.Core.Contracts\n{\n    /// <summary>\n    /// Expression and its Dependencies\n    /// </summary>\n    public class EFQueryDebugView\n    {\n        /// <summary>\n        ///  Dependency items.\n        /// </summary>\n        public ISet<string> Types { set; get; }\n\n        /// <summary>\n        /// Expression to a readable string.\n        /// </summary>\n        public string DebugView { set; get; }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/Contracts/IEFCacheKeyHashProvider.cs",
    "content": "namespace EFSecondLevelCache.Core.Contracts\n{\n    /// <summary>\n    /// The CacheKey Hash Provider Contract.\n    /// </summary>\n    public interface IEFCacheKeyHashProvider\n    {\n        /// <summary>\n        /// Computes the unique hash of the input.\n        /// </summary>\n        /// <param name=\"data\">the input data to hash</param>\n        /// <returns>Hashed data</returns>\n        string ComputeHash(string data);\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/Contracts/IEFCacheKeyProvider.cs",
    "content": "using System.Linq;\nusing System.Linq.Expressions;\n\nnamespace EFSecondLevelCache.Core.Contracts\n{\n    /// <summary>\n    /// CacheKeyProvider Contract.\n    /// </summary>\n    public interface IEFCacheKeyProvider\n    {\n        /// <summary>\n        /// Gets an EF query and returns its hash to store in the cache.\n        /// </summary>\n        /// <param name=\"query\">The EF query.</param>\n        /// <param name=\"expression\">An expression tree that represents a LINQ query.</param>\n        /// <param name=\"saltKey\">If you think the computed hash of the query is not enough, set this value.</param>\n        /// <returns>Information of the computed key of the input LINQ query.</returns>\n        EFCacheKey GetEFCacheKey(IQueryable query, Expression expression, string saltKey = \"\");\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/Contracts/IEFCacheServiceProvider.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace EFSecondLevelCache.Core.Contracts\n{\n    /// <summary>\n    /// Cache Service Provider Contract.\n    /// </summary>\n    public interface IEFCacheServiceProvider\n    {\n        /// <summary>\n        /// Removes the cached entries added by this library.\n        /// </summary>\n        void ClearAllCachedEntries();\n\n        /// <summary>\n        /// Gets a cached entry by key.\n        /// </summary>\n        /// <param name=\"cacheKey\">key to find</param>\n        /// <returns>cached value</returns>\n        object GetValue(string cacheKey);\n\n        /// <summary>\n        /// Adds a new item to the cache.\n        /// </summary>\n        /// <param name=\"cacheKey\">key</param>\n        /// <param name=\"value\">value</param>\n        /// <param name=\"rootCacheKeys\">cache dependencies</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item. If you set it to null, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.</param>\n        void InsertValue(string cacheKey, object value, ISet<string> rootCacheKeys, EFCachePolicy cachePolicy);\n\n        /// <summary>\n        /// Invalidates all of the cache entries which are dependent on any of the specified root keys.\n        /// </summary>\n        /// <param name=\"rootCacheKeys\">cache dependencies</param>\n        void InvalidateCacheDependencies(string[] rootCacheKeys);\n\n        /// <summary>\n        /// Some cache providers won't accept null values.\n        /// So we need a custom Null object here. It should be defined `static readonly` in your code.\n        /// </summary>\n        object NullObject { get; }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFAsyncEnumerable.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Asynchronous version of the IEnumerable interface, allowing elements of the enumerable sequence to be retrieved asynchronously.\n    /// </summary>\n    public class EFAsyncEnumerable<T> : IAsyncEnumerable<T>\n    {\n        private readonly IEnumerator<T> _inner;\n\n        /// <summary>\n        /// Asynchronous version of the IEnumerable interface\n        /// </summary>\n        public EFAsyncEnumerable(IEnumerator<T> inner)\n        {\n            _inner = inner;\n        }\n\n        /// <summary>\n        /// Gets an asynchronous enumerator over the sequence.\n        /// </summary>\n        public IAsyncEnumerator<T> GetEnumerator()\n        {\n            return new EFAsyncEnumerator<T>(_inner);\n        }\n\n        /// <summary>\n        /// Gets an asynchronous enumerator over the sequence.\n        /// </summary>\n        public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken())\n        {\n            return new EFAsyncEnumerator<T>(_inner);\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFAsyncEnumerator.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Asynchronous version of the IEnumerator of T interface that allows elements to be retrieved asynchronously.\n    /// </summary>\n    /// <typeparam name=\"T\"></typeparam>\n    public class EFAsyncEnumerator<T> : IAsyncEnumerator<T>\n    {\n        private readonly IEnumerator<T> _inner;\n\n        /// <summary>\n        /// Asynchronous version of the IEnumerator of T interface that allows elements to be retrieved asynchronously.\n        /// </summary>\n        /// <param name=\"inner\">The inner IEnumerator</param>\n        public EFAsyncEnumerator(IEnumerator<T> inner)\n        {\n            _inner = inner;\n        }\n\n        /// <summary>\n        /// Gets the current element in the iteration.\n        /// </summary>\n        public T Current => _inner.Current;\n\n        /// <summary>\n        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.\n        /// </summary>\n        public void Dispose()\n        {\n            _inner.Dispose();\n        }\n\n        /// <summary>\n        /// Advances the enumerator to the next element in the sequence, returning the result asynchronously.\n        /// </summary>\n        /// <param name=\"cancellationToken\">A System.Threading.CancellationToken to observe while waiting for the task to complete.</param>\n        /// <returns>A task that represents the asynchronous operation.  The task result contains true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the sequence.</returns>\n        public Task<bool> MoveNext(CancellationToken cancellationToken)\n        {\n            cancellationToken.ThrowIfCancellationRequested();\n            return Task.FromResult(_inner.MoveNext());\n        }\n\n        /// <summary>\n        /// Advances the enumerator to the next element in the sequence, returning the result asynchronously.\n        /// </summary>\n        /// <returns>A task that represents the asynchronous operation.  The task result contains true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the sequence.</returns>\n        public ValueTask<bool> MoveNextAsync()\n        {\n            return new ValueTask<bool>(_inner.MoveNext());\n        }\n\n        /// <summary>\n        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.\n        /// </summary>\n        public ValueTask DisposeAsync()\n        {\n            _inner.Dispose();\n            return default;\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFAsyncTaskEnumerable.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Asynchronous version of the IEnumerable interface, allowing elements of the enumerable sequence to be retrieved asynchronously.\n    /// </summary>\n    public class EFAsyncTaskEnumerable<T> : IAsyncEnumerable<T>\n    {\n        private readonly Task<T> _task;\n\n        /// <summary>\n        /// Asynchronous version of the IEnumerable interface.\n        /// </summary>\n        public EFAsyncTaskEnumerable(Task<T> task)\n        {\n            _task = task;\n        }\n\n        /// <summary>\n        /// Gets an asynchronous enumerator over the sequence.\n        /// </summary>\n        public IAsyncEnumerator<T> GetEnumerator() => new EFAsyncTaskEnumerator<T>(_task);\n\n        /// <summary>\n        /// Gets an asynchronous enumerator over the sequence.\n        /// </summary>\n        public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken())\n        {\n            return new EFAsyncTaskEnumerator<T>(_task);\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFAsyncTaskEnumerator.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Asynchronous version of the IEnumerator interface, allowing elements to be retrieved asynchronously.\n    /// </summary>\n    public sealed class EFAsyncTaskEnumerator<T> : IAsyncEnumerator<T>\n    {\n        private readonly Task<T> _task;\n        private bool _moved;\n\n        /// <summary>\n        /// Asynchronous version of the IEnumerator interface\n        /// </summary>\n        public EFAsyncTaskEnumerator(Task<T> task)\n        {\n            _task = task;\n        }\n\n        /// <summary>\n        /// Gets the current element in the iteration.\n        /// </summary>\n        public T Current => !_moved ? default(T) : _task.Result;\n\n        /// <summary>\n        /// Advances the enumerator to the next element in the sequence, returning the result asynchronously.\n        /// </summary>\n        public async Task<bool> MoveNext(CancellationToken cancellationToken)\n        {\n            cancellationToken.ThrowIfCancellationRequested();\n\n            if (!_moved)\n            {\n                await _task.ConfigureAwait(false);\n\n                _moved = true;\n\n                return _moved;\n            }\n\n            return false;\n        }\n\n        /// <summary>\n        /// Advances the enumerator to the next element in the sequence, returning the result asynchronously.\n        /// </summary>\n        public ValueTask<bool> MoveNextAsync()\n        {\n            return new ValueTask<bool>(MoveNext(new CancellationToken()));\n        }\n\n        /// <summary>\n        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.\n        /// </summary>\n        public ValueTask DisposeAsync()\n        {\n            return default;\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFCacheKeyHashProvider.cs",
    "content": "﻿using System;\nusing EFSecondLevelCache.Core.Contracts;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Computes the unique hash of the input, using the xxHash algorithm.\n    /// </summary>\n    public class EFCacheKeyHashProvider : IEFCacheKeyHashProvider\n    {\n        /// <summary>\n        /// Computes the unique hash of the input.\n        /// </summary>\n        /// <param name=\"data\">the input data to hash</param>\n        /// <returns>Hashed data using the xxHash algorithm</returns>\n        public string ComputeHash(string data)\n        {\n            if(string.IsNullOrWhiteSpace(data))\n                throw new ArgumentNullException(nameof(data));\n\n            return $\"{XxHashUnsafe.ComputeHash(data):X}\";\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFCacheKeyProvider3x.cs",
    "content": "using System.Linq;\nusing System.Linq.Expressions;\nusing EFSecondLevelCache.Core.Contracts;\nusing System;\nusing CacheManager.Core;\nusing Microsoft.Extensions.DependencyInjection;\nusing System.Reflection;\nusing Microsoft.EntityFrameworkCore.Query;\nusing Microsoft.EntityFrameworkCore.Query.Internal;\nusing Microsoft.EntityFrameworkCore.Diagnostics;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// A custom cache key provider for EF queries.\n    /// </summary>\n    public class EFCacheKeyProvider : IEFCacheKeyProvider\n    {\n        private static readonly TypeInfo _queryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo();\n\n        private static readonly FieldInfo _queryCompilerField =\n            typeof(EntityQueryProvider).GetTypeInfo().DeclaredFields.First(x => x.Name == \"_queryCompiler\");\n\n        private static readonly FieldInfo _queryContextFactoryField =\n            _queryCompilerTypeInfo.DeclaredFields.First(x => x.Name == \"_queryContextFactory\");\n\n        private static readonly FieldInfo _loggerField =\n            _queryCompilerTypeInfo.DeclaredFields.First(x => x.Name == \"_logger\");\n\n        private static readonly TimeSpan _slidingExpirationTimeSpan = TimeSpan.FromMinutes(7);\n\n        private static readonly ICacheManager<EFCacheKey> _keysCacheManager =\n            EFStaticServiceProvider.Instance.GetRequiredService<ICacheManager<EFCacheKey>>();\n\n        private readonly IEFCacheKeyHashProvider _cacheKeyHashProvider;\n\n        /// <summary>\n        /// A custom cache key provider for EF queries.\n        /// </summary>\n        /// <param name=\"cacheKeyHashProvider\">Provides the custom hashing algorithm.</param>\n        public EFCacheKeyProvider(IEFCacheKeyHashProvider cacheKeyHashProvider)\n        {\n            _cacheKeyHashProvider = cacheKeyHashProvider;\n        }\n\n        /// <summary>\n        /// Gets an EF query and returns its hashed key to store in the cache.\n        /// </summary>\n        /// <param name=\"query\">The EF query.</param>\n        /// <param name=\"expression\">An expression tree that represents a LINQ query.</param>\n        /// <param name=\"saltKey\">If you think the computed hash of the query is not enough, set this value.</param>\n        /// <returns>Information of the computed key of the input LINQ query.</returns>\n        public EFCacheKey GetEFCacheKey(IQueryable query, Expression expression, string saltKey = \"\")\n        {\n            var queryCompiler = (QueryCompiler)_queryCompilerField.GetValue(query.Provider);\n            var (expressionKeyHash, modifiedExpression) = getExpressionKeyHash(queryCompiler, _cacheKeyHashProvider, expression);\n            var cachedKey = _keysCacheManager.Get<EFCacheKey>(expressionKeyHash);\n            if (cachedKey != null)\n            {\n                return cachedKey;\n            }\n\n            var expressionPrinter = new ExpressionPrinter();\n            var sql = expressionPrinter.PrintDebug(modifiedExpression);\n\n            var expressionVisitorResult = EFQueryExpressionVisitor.GetDebugView(expression);\n            var key = $\"{sql};{expressionVisitorResult.DebugView};{saltKey}\";\n            var keyHash = _cacheKeyHashProvider.ComputeHash(key);\n\n            var cacheKey = new EFCacheKey\n            {\n                Key = key,\n                KeyHash = keyHash,\n                CacheDependencies = expressionVisitorResult.Types\n            };\n            setCache(expressionKeyHash, cacheKey);\n            return cacheKey;\n        }\n\n        private static void setCache(string expressionKeyHash, EFCacheKey value)\n        {\n            _keysCacheManager.Add(\n                new CacheItem<EFCacheKey>(expressionKeyHash, value, ExpirationMode.Sliding, _slidingExpirationTimeSpan));\n        }\n\n        private static (string ExpressionKeyHash, Expression ModifiedExpression) getExpressionKeyHash(\n            QueryCompiler queryCompiler,\n            IEFCacheKeyHashProvider cacheKeyHashProvider,\n            Expression expression)\n        {\n            var queryContextFactory = (IQueryContextFactory)_queryContextFactoryField.GetValue(queryCompiler);\n            var queryContext = queryContextFactory.Create();\n            var logger = (IDiagnosticsLogger<DbLoggerCategory.Query>)_loggerField.GetValue(queryCompiler);\n            expression = queryCompiler.ExtractParameters(expression, queryContext, logger, parameterize: false);\n\n            var expressionKey = $\"{ExpressionEqualityComparer.Instance.GetHashCode(expression)};\";\n            var parameterValues = queryContext.ParameterValues;\n            if (parameterValues.Any())\n            {\n                expressionKey = parameterValues.Aggregate(expressionKey,\n                    (current, item) => current + $\"{item.Key}={item.Value?.GetHashCode()};\");\n            }\n\n            return (cacheKeyHashProvider.ComputeHash(expressionKey), expression);\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFCachePolicy.cs",
    "content": "using System;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Defines the supported expiration modes for cache items.\n    /// </summary>\n    public enum CacheExpirationMode\n    {\n        /// <summary>\n        /// Defines absolute expiration. The item will expire after the expiration timeout.\n        /// </summary>\n        Absolute,\n\n        /// <summary>\n        /// Defines sliding expiration. The expiration timeout will be refreshed on every access.\n        /// </summary>\n        Sliding\n    }\n\n    /// <summary>\n    /// EFCachePolicy determines the Expiration time of the cache.\n    /// If you don't define it, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.\n    /// </summary>\n    public class EFCachePolicy\n    {\n        /// <summary>\n        /// Defines the expiration mode of the cache item.\n        /// Its default value is Absolute.\n        /// </summary>\n        public CacheExpirationMode ExpirationMode { set; get; }\n\n        /// <summary>\n        /// The expiration timeout.\n        /// Its default value is 20 minutes later.\n        /// </summary>\n        /// <value></value>\n        public TimeSpan Timeout { set; get; } = TimeSpan.FromMinutes(20);\n\n        /// <summary>\n        /// If you think the computed hash of the query to calculate the cache-key is not enough, set this value.\n        /// Its default value is string.Empty.\n        /// </summary>\n        public string SaltKey { set; get; } = string.Empty;\n\n        /// <summary>\n        /// EFCachePolicy determines the Expiration time of the cache.\n        /// </summary>\n        public EFCachePolicy() { }\n\n        /// <summary>\n        /// EFCachePolicy determines the Expiration time of the cache.\n        /// </summary>\n        /// <param name=\"expirationMode\">Defines the expiration mode of the cache item.</param>\n        /// <param name=\"timeout\">The expiration timeout.</param>\n        public EFCachePolicy(CacheExpirationMode expirationMode, TimeSpan timeout)\n        {\n            ExpirationMode = expirationMode;\n            Timeout = timeout;\n        }\n\n        /// <summary>\n        /// EFCachePolicy determines the Expiration time of the cache.\n        /// </summary>\n        /// <param name=\"expirationMode\">Defines the expiration mode of the cache item.</param>\n        /// <param name=\"timeout\">The expiration timeout.</param>\n        /// <param name=\"saltKey\">If you think the computed hash of the query to calculate the cache-key is not enough, set this value.</param>\n        public EFCachePolicy(CacheExpirationMode expirationMode, TimeSpan timeout, string saltKey)\n        {\n            ExpirationMode = expirationMode;\n            Timeout = timeout;\n            SaltKey = saltKey;\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFCacheServiceProvider.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading;\nusing Microsoft.Extensions.DependencyInjection;\nusing CacheManager.Core;\nusing EFSecondLevelCache.Core.Contracts;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Using ICacheManager as a cache service.\n    /// </summary>\n    public class EFCacheServiceProvider : IEFCacheServiceProvider\n    {\n        private static readonly EFCacheKey _nullObject = new EFCacheKey();\n\n        private static readonly ICacheManager<ISet<string>> _dependenciesCacheManager =\n            EFStaticServiceProvider.Instance.GetRequiredService<ICacheManager<ISet<string>>>();\n\n        private static readonly ICacheManager<object> _valuesCacheManager =\n            EFStaticServiceProvider.Instance.GetRequiredService<ICacheManager<object>>();\n\n        private static readonly ReaderWriterLockSlim _vcmReaderWriterLock = new ReaderWriterLockSlim();\n        private static readonly ReaderWriterLockSlim _dcReaderWriterLock = new ReaderWriterLockSlim();\n\n        /// <summary>\n        /// Some cache providers won't accept null values.\n        /// So we need a custom Null object here. It should be defined `static readonly` in your code.\n        /// </summary>\n        public object NullObject => _nullObject;\n\n        /// <summary>\n        /// Using ICacheManager as a cache service.\n        /// </summary>\n        public EFCacheServiceProvider()\n        {\n            // Occurs when an item was removed by the cache handle due to expiration or e.g. memory pressure eviction.\n            // Without _dependenciesCacheManager items, we can't invalidate cached items on Insert/Update/Delete.\n            // So to prevent stale reads, we have to clear all cached data in this case.\n            _dependenciesCacheManager.OnRemoveByHandle += (sender, args) => ClearAllCachedEntries();\n        }\n\n        /// <summary>\n        /// Removes the cached entries added by this library.\n        /// </summary>\n        public void ClearAllCachedEntries()\n        {\n            _vcmReaderWriterLock.TryWriteLocked(() => _valuesCacheManager.Clear());\n            _dcReaderWriterLock.TryWriteLocked(() => _dependenciesCacheManager.Clear());\n        }\n\n        /// <summary>\n        /// Gets a cached entry by key.\n        /// </summary>\n        /// <param name=\"cacheKey\">key to find</param>\n        /// <returns>cached value</returns>\n        public object GetValue(string cacheKey)\n        {\n            return _valuesCacheManager.Get(cacheKey);\n        }\n\n        /// <summary>\n        /// Adds a new item to the cache.\n        /// </summary>\n        /// <param name=\"cacheKey\">key</param>\n        /// <param name=\"value\">value</param>\n        /// <param name=\"rootCacheKeys\">cache dependencies</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item.</param>\n        public void InsertValue(string cacheKey, object value, ISet<string> rootCacheKeys, EFCachePolicy cachePolicy)\n        {\n            if (value == null)\n            {\n                value = NullObject; // `HttpRuntime.Cache.Insert` won't accept null values.\n            }\n\n            foreach (var rootCacheKey in rootCacheKeys)\n            {\n                _dcReaderWriterLock.TryWriteLocked(() =>\n                {\n                    _dependenciesCacheManager.AddOrUpdate(rootCacheKey, new HashSet<string> { cacheKey },\n                        updateValue: set =>\n                        {\n                            set.Add(cacheKey);\n                            return set;\n                        });\n                });\n            }\n\n            _vcmReaderWriterLock.TryWriteLocked(() =>\n            {\n                if (cachePolicy == null)\n                {\n                    _valuesCacheManager.Add(cacheKey, value);\n                }\n                else\n                {\n                    _valuesCacheManager.Add(new CacheItem<object>(\n                      cacheKey,\n                      value,\n                      cachePolicy.ExpirationMode == CacheExpirationMode.Absolute ? ExpirationMode.Absolute : ExpirationMode.Sliding,\n                      cachePolicy.Timeout));\n                }\n            });\n        }\n\n        /// <summary>\n        /// Invalidates all of the cache entries which are dependent on any of the specified root keys.\n        /// </summary>\n        /// <param name=\"rootCacheKeys\">cache dependencies</param>\n        public void InvalidateCacheDependencies(string[] rootCacheKeys)\n        {\n            foreach (var rootCacheKey in rootCacheKeys)\n            {\n                if (string.IsNullOrWhiteSpace(rootCacheKey))\n                {\n                    continue;\n                }\n\n                clearDependencyValues(rootCacheKey);\n                _dcReaderWriterLock.TryWriteLocked(() => _dependenciesCacheManager.Remove(rootCacheKey));\n            }\n        }\n\n        private void clearDependencyValues(string rootCacheKey)\n        {\n            _dcReaderWriterLock.TryReadLocked(() =>\n            {\n                var dependencyKeys = _dependenciesCacheManager.Get(rootCacheKey);\n                if (dependencyKeys == null)\n                {\n                    return;\n                }\n\n                foreach (var dependencyKey in dependencyKeys)\n                {\n                    _vcmReaderWriterLock.TryWriteLocked(() => _valuesCacheManager.Remove(dependencyKey));\n                }\n            });\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFCachedDbSet.cs",
    "content": "using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing System.Threading;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Query.Internal;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Provides functionality to evaluate queries against a specific data source.\n    /// </summary>\n    /// <typeparam name=\"TType\"></typeparam>\n    public class EFCachedDbSet<TType> : IOrderedQueryable<TType>, IAsyncEnumerable<TType>\n        where TType : class\n    {\n        private readonly EFCachedQueryProvider _provider;\n\n        /// <summary>\n        /// Provides functionality to evaluate queries against a specific data source.\n        /// </summary>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <param name=\"cacheKeyProvider\">Gets an EF query and returns its hash to store in the cache.</param>\n        /// <param name=\"cacheServiceProvider\">Cache Service Provider.</param>\n        public EFCachedDbSet(\n            DbSet<TType> query,\n            EFCachePolicy cachePolicy,\n            EFCacheDebugInfo debugInfo,\n            IEFCacheKeyProvider cacheKeyProvider,\n            IEFCacheServiceProvider cacheServiceProvider)\n        {\n            CachePolicy = cachePolicy;\n            DebugInfo = debugInfo;\n            CacheKeyProvider = cacheKeyProvider;\n            CacheServiceProvider = cacheServiceProvider;\n            Query = query;\n            var queryable = Query.AsNoTracking().AsQueryable();\n            ElementType = queryable.ElementType;\n            Expression = queryable.Expression;\n            _provider = new EFCachedQueryProvider(queryable, cachePolicy, debugInfo, cacheKeyProvider, cacheServiceProvider);\n        }\n\n        /// <summary>\n        /// Asynchronous version of the IEnumerable interface\n        /// </summary>\n        public IAsyncEnumerable<TType> AsyncEnumerable => new EFAsyncEnumerable<TType>(this.AsEnumerable().GetEnumerator());\n\n        /// <summary>\n        /// Gets an EF query and returns its hash to store in the cache.\n        /// </summary>\n        public IEFCacheKeyProvider CacheKeyProvider { get; }\n\n        /// <summary>\n        /// Cache Service Provider.\n        /// </summary>\n        public IEFCacheServiceProvider CacheServiceProvider { get; }\n\n        /// <summary>\n        /// Stores the debug information of the caching process.\n        /// </summary>\n        public EFCacheDebugInfo DebugInfo { get; }\n\n        /// <summary>\n        /// Gets the type of the element(s) that are returned when the expression tree associated with this instance of System.Linq.IQueryable is executed.\n        /// </summary>\n        public Type ElementType { get; }\n\n        /// <summary>\n        /// Gets the expression tree that is associated with the instance of System.Linq.IQueryable.\n        /// </summary>\n        public Expression Expression { get; }\n\n        /// <summary>\n        /// Gets the query provider that is associated with this data source.\n        /// </summary>\n        public IQueryProvider Provider => _provider;\n\n        /// <summary>\n        /// The input EF query.\n        /// </summary>\n        public DbSet<TType> Query { get; }\n\n        /// <summary>\n        /// Defines the expiration mode of the cache item.\n        /// </summary>\n        public EFCachePolicy CachePolicy { get; }\n\n        /// <summary>\n        /// Returns an enumerator that iterates through a collection.\n        /// </summary>\n        /// <returns>A collections that can be used to iterate through the collection.</returns>\n        public IEnumerator GetEnumerator()\n        {\n            return _provider.Materializer.Materialize(Expression, () => Query.ToArray()).GetEnumerator();\n        }\n\n        /// <summary>\n        /// Returns an enumerator that iterates through a collection.\n        /// </summary>\n        /// <returns>A collections that can be used to iterate through the collection.</returns>\n        IEnumerator<TType> IEnumerable<TType>.GetEnumerator()\n        {\n            return ((IEnumerable<TType>)_provider.Materializer.Materialize(Expression, () => Query.ToArray())).GetEnumerator();\n        }\n\n        /// <summary>\n        /// Returns an enumerator that iterates through a collection.\n        /// </summary>\n        /// <returns>A collections that can be used to iterate through the collection.</returns>\n        public IAsyncEnumerator<TType> GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken())\n        {\n            return new EFAsyncEnumerator<TType>(\n                    ((IEnumerable<TType>)_provider.Materializer.Materialize(Expression, () => Query.ToArray())).GetEnumerator());\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFCachedDbSetExtensions.cs",
    "content": "using System;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Internal;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.EntityFrameworkCore.Diagnostics;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    ///  DbSet Extensions\n    /// </summary>\n    public static class EFCachedDbSetExtensions\n    {\n        /// <summary>\n        /// Finds an entity with the given primary key values.\n        /// </summary>\n        public static TEntity Find<TEntity>(\n            this EFCachedDbSet<TEntity> cachedQueryable, params object[] keyValues)\n            where TEntity : class\n        {\n            var query = buildFindQueryable(cachedQueryable, keyValues);\n            return new EFCachedQueryable<TEntity>(\n                query, cachedQueryable.CachePolicy, cachedQueryable.DebugInfo,\n                cachedQueryable.CacheKeyProvider, cachedQueryable.CacheServiceProvider).FirstOrDefault();\n        }\n\n        /// <summary>\n        /// Finds an entity with the given primary key values.\n        /// </summary>\n        public static Task<TEntity> FindAsync<TEntity>(\n            this EFCachedDbSet<TEntity> cachedQueryable,\n            object[] keyValues,\n            CancellationToken cancellationToken) where TEntity : class\n        {\n            var query = buildFindQueryable(cachedQueryable, keyValues);\n            return new EFCachedQueryable<TEntity>(\n                query, cachedQueryable.CachePolicy, cachedQueryable.DebugInfo,\n                cachedQueryable.CacheKeyProvider, cachedQueryable.CacheServiceProvider).FirstOrDefaultAsync(cancellationToken);\n        }\n\n        /// <summary>\n        /// Finds an entity with the given primary key values.\n        /// </summary>\n        public static Task<TEntity> FindAsync<TEntity>(\n            this EFCachedDbSet<TEntity> cachedQueryable,\n            params object[] keyValues) where TEntity : class\n        {\n            return cachedQueryable.FindAsync(keyValues, default(CancellationToken));\n        }\n\n        private static IQueryable<TEntity> buildFindQueryable<TEntity>(\n            EFCachedDbSet<TEntity> cachedQueryable, object[] keyValues)\n            where TEntity : class\n        {\n            var set = cachedQueryable.Query;\n            var context = set.GetInfrastructure().GetRequiredService<IDbContextServices>().CurrentContext.Context;\n            var keyProperties = context.Model.FindEntityType(typeof(TEntity)).FindPrimaryKey().Properties;\n\n            if (keyProperties.Count != keyValues.Length)\n            {\n                if (keyProperties.Count == 1)\n                {\n                    throw new ArgumentException(\n                        CoreStrings.FindNotCompositeKey(typeof(TEntity).ShortDisplayName(), keyValues.Length));\n                }\n                throw new ArgumentException(\n                    CoreStrings.FindValueCountMismatch(typeof(TEntity).ShortDisplayName(), keyProperties.Count,\n                        keyValues.Length));\n            }\n\n            for (var i = 0; i < keyValues.Length; i++)\n            {\n                if (keyValues[i] == null)\n                {\n                    throw new ArgumentNullException(nameof(keyValues));\n                }\n\n                var valueType = keyValues[i].GetType();\n                var propertyType = keyProperties[i].ClrType;\n                if (valueType != propertyType)\n                {\n                    throw new ArgumentException(\n                        CoreStrings.FindValueTypeMismatch(\n                            i, typeof(TEntity).ShortDisplayName(), valueType.ShortDisplayName(),\n                            propertyType.ShortDisplayName()));\n                }\n            }\n\n            IQueryable<TEntity> query = context.Set<TEntity>().AsNoTracking();\n            var parameter = Expression.Parameter(typeof(TEntity), \"x\");\n            for (var i = 0; i < keyProperties.Count; i++)\n            {\n                var property = keyProperties[i];\n                var keyValue = keyValues[i];\n                var expression = Expression.Lambda(\n                    Expression.Equal(\n                        Expression.Property(parameter, property.Name),\n                        Expression.Constant(keyValue)),\n                    parameter) as Expression<Func<TEntity, bool>>;\n\n                query = query.Where(expression);\n            }\n            return query;\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFCachedQueryExtensions.cs",
    "content": "using System;\nusing System.Linq;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Returns a new cached query.\n    /// </summary>\n    public static class EFCachedQueryExtensions\n    {\n        private static readonly IEFCacheKeyProvider _defaultCacheKeyProvider;\n        private static readonly IEFCacheServiceProvider _defaultCacheServiceProvider;\n\n        static EFCachedQueryExtensions()\n        {\n            var serviceProvider = EFStaticServiceProvider.Instance;\n            _defaultCacheServiceProvider = serviceProvider.GetRequiredService<IEFCacheServiceProvider>();\n            _defaultCacheKeyProvider = serviceProvider.GetRequiredService<IEFCacheKeyProvider>();\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item. If you set it to null or don't specify it, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <param name=\"cacheKeyProvider\">Gets an EF query and returns its hash to store in the cache.</param>\n        /// <param name=\"cacheServiceProvider\">Cache Service Provider.</param>\n        /// <returns></returns>\n        public static EFCachedQueryable<TType> Cacheable<TType>(\n            this IQueryable<TType> query, EFCachePolicy cachePolicy, EFCacheDebugInfo debugInfo,\n            IEFCacheKeyProvider cacheKeyProvider, IEFCacheServiceProvider cacheServiceProvider)\n        {\n            return new EFCachedQueryable<TType>(query, cachePolicy, debugInfo, cacheKeyProvider, cacheServiceProvider);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item. If you set it to null or don't specify it, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <param name=\"cacheKeyProvider\">Gets an EF query and returns its hash to store in the cache.</param>\n        /// <param name=\"cacheServiceProvider\">Cache Service Provider.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static IQueryable Cacheable(\n           this IQueryable query, EFCachePolicy cachePolicy, EFCacheDebugInfo debugInfo,\n           IEFCacheKeyProvider cacheKeyProvider, IEFCacheServiceProvider cacheServiceProvider)\n        {\n            var type = typeof(EFCachedQueryable<>).MakeGenericType(query.ElementType);\n            var cachedQueryable = Activator.CreateInstance(type, query, cachePolicy, debugInfo, cacheKeyProvider, cacheServiceProvider);\n            return cachedQueryable as IQueryable;\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item. If you set it to null or don't specify it, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <param name=\"cacheKeyProvider\">Gets an EF query and returns its hash to store in the cache.</param>\n        /// <param name=\"cacheServiceProvider\">Cache Service Provider.</param>\n        /// <returns></returns>\n        public static EFCachedDbSet<TType> Cacheable<TType>(\n            this DbSet<TType> query, EFCachePolicy cachePolicy, EFCacheDebugInfo debugInfo,\n            IEFCacheKeyProvider cacheKeyProvider, IEFCacheServiceProvider cacheServiceProvider) where TType : class\n        {\n            return new EFCachedDbSet<TType>(query, cachePolicy, debugInfo, cacheKeyProvider, cacheServiceProvider);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item. If you set it to null or don't specify it, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <param name=\"serviceProvider\">Defines a mechanism for retrieving a service object.</param>\n        /// <returns></returns>\n        public static EFCachedQueryable<TType> Cacheable<TType>(\n            this IQueryable<TType> query, EFCachePolicy cachePolicy, EFCacheDebugInfo debugInfo, IServiceProvider serviceProvider)\n        {\n            var cacheServiceProvider = serviceProvider.GetRequiredService<IEFCacheServiceProvider>();\n            var cacheKeyProvider = serviceProvider.GetRequiredService<IEFCacheKeyProvider>();\n            return new EFCachedQueryable<TType>(query, cachePolicy, debugInfo, cacheKeyProvider, cacheServiceProvider);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item. If you set it to null or don't specify it, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <param name=\"serviceProvider\">Defines a mechanism for retrieving a service object.</param>\n        /// <returns></returns>\n        public static IQueryable Cacheable(\n            this IQueryable query, EFCachePolicy cachePolicy, EFCacheDebugInfo debugInfo, IServiceProvider serviceProvider)\n        {\n            var cacheServiceProvider = serviceProvider.GetRequiredService<IEFCacheServiceProvider>();\n            var cacheKeyProvider = serviceProvider.GetRequiredService<IEFCacheKeyProvider>();\n            return Cacheable(query, cachePolicy, debugInfo, cacheKeyProvider, cacheServiceProvider);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item. If you set it to null or don't specify it, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <param name=\"serviceProvider\">Defines a mechanism for retrieving a service object.</param>\n        /// <returns></returns>\n        public static EFCachedDbSet<TType> Cacheable<TType>(\n            this DbSet<TType> query, EFCachePolicy cachePolicy, EFCacheDebugInfo debugInfo, IServiceProvider serviceProvider) where TType : class\n        {\n            var cacheServiceProvider = serviceProvider.GetRequiredService<IEFCacheServiceProvider>();\n            var cacheKeyProvider = serviceProvider.GetRequiredService<IEFCacheKeyProvider>();\n            return new EFCachedDbSet<TType>(query, cachePolicy, debugInfo, cacheKeyProvider, cacheServiceProvider);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <param name=\"serviceProvider\">Defines a mechanism for retrieving a service object.</param>\n        /// <returns></returns>\n        public static EFCachedQueryable<TType> Cacheable<TType>(\n            this IQueryable<TType> query, EFCacheDebugInfo debugInfo, IServiceProvider serviceProvider)\n        {\n            return Cacheable(query, null, debugInfo, serviceProvider);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <param name=\"serviceProvider\">Defines a mechanism for retrieving a service object.</param>\n        /// <returns></returns>\n        public static IQueryable Cacheable(\n            this IQueryable query, EFCacheDebugInfo debugInfo, IServiceProvider serviceProvider)\n        {\n            return Cacheable(query, null, debugInfo, serviceProvider);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <param name=\"serviceProvider\">Defines a mechanism for retrieving a service object.</param>\n        /// <returns></returns>\n        public static EFCachedDbSet<TType> Cacheable<TType>(\n            this DbSet<TType> query, EFCacheDebugInfo debugInfo, IServiceProvider serviceProvider) where TType : class\n        {\n            return Cacheable(query, null, debugInfo, serviceProvider);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"serviceProvider\">Defines a mechanism for retrieving a service object.</param>\n        /// <returns></returns>\n        public static EFCachedQueryable<TType> Cacheable<TType>(\n            this IQueryable<TType> query, IServiceProvider serviceProvider)\n        {\n            return Cacheable(query, null, new EFCacheDebugInfo(), serviceProvider);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"serviceProvider\">Defines a mechanism for retrieving a service object.</param>\n        /// <returns></returns>\n        public static EFCachedDbSet<TType> Cacheable<TType>(\n            this DbSet<TType> query, IServiceProvider serviceProvider) where TType : class\n        {\n            return Cacheable(query, null, new EFCacheDebugInfo(), serviceProvider);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item. If you set it to null or don't specify it, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static EFCachedQueryable<TType> Cacheable<TType>(\n            this IQueryable<TType> query, EFCachePolicy cachePolicy, EFCacheDebugInfo debugInfo)\n        {\n            return Cacheable(query, cachePolicy, debugInfo, _defaultCacheKeyProvider, _defaultCacheServiceProvider);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item. If you set it to null or don't specify it, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static EFCachedDbSet<TType> Cacheable<TType>(\n            this DbSet<TType> query, EFCachePolicy cachePolicy, EFCacheDebugInfo debugInfo) where TType : class\n        {\n            return Cacheable(query, cachePolicy, debugInfo, _defaultCacheKeyProvider, _defaultCacheServiceProvider);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item. If you set it to null or don't specify it, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static IQueryable Cacheable(this IQueryable query, EFCachePolicy cachePolicy, EFCacheDebugInfo debugInfo)\n        {\n            return Cacheable(query, cachePolicy, debugInfo, _defaultCacheKeyProvider, _defaultCacheServiceProvider);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static EFCachedQueryable<TType> Cacheable<TType>(this IQueryable<TType> query)\n        {\n            return Cacheable(query, null, new EFCacheDebugInfo());\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static IQueryable Cacheable(this IQueryable query)\n        {\n            return Cacheable(query, null, new EFCacheDebugInfo());\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static EFCachedDbSet<TType> Cacheable<TType>(this DbSet<TType> query) where TType : class\n        {\n            return Cacheable(query, null, new EFCacheDebugInfo());\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static EFCachedQueryable<TType> Cacheable<TType>(this IQueryable<TType> query, EFCacheDebugInfo debugInfo)\n        {\n            return Cacheable(query, null, debugInfo);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static EFCachedDbSet<TType> Cacheable<TType>(this DbSet<TType> query, EFCacheDebugInfo debugInfo) where TType : class\n        {\n            return Cacheable(query, null, debugInfo);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item. If you set it to null or don't specify it, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static EFCachedQueryable<TType> Cacheable<TType>(this IQueryable<TType> query, EFCachePolicy cachePolicy)\n        {\n            return Cacheable(query, cachePolicy, new EFCacheDebugInfo());\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"expirationMode\">Defines the expiration mode of the cache item.</param>\n        /// <param name=\"timeout\">The expiration timeout.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static EFCachedQueryable<TType> Cacheable<TType>(\n            this IQueryable<TType> query, CacheExpirationMode expirationMode, TimeSpan timeout)\n        {\n            return Cacheable(query, new EFCachePolicy(expirationMode, timeout), new EFCacheDebugInfo());\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"expirationMode\">Defines the expiration mode of the cache item.</param>\n        /// <param name=\"timeout\">The expiration timeout.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static EFCachedQueryable<TType> Cacheable<TType>(\n            this IQueryable<TType> query, CacheExpirationMode expirationMode, TimeSpan timeout, EFCacheDebugInfo debugInfo)\n        {\n            return Cacheable(query, new EFCachePolicy(expirationMode, timeout), debugInfo);\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item. If you set it to null or don't specify it, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static IQueryable Cacheable(this IQueryable query, EFCachePolicy cachePolicy)\n        {\n            return Cacheable(query, cachePolicy, new EFCacheDebugInfo());\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item. If you set it to null or don't specify it, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static EFCachedDbSet<TType> Cacheable<TType>(\n            this DbSet<TType> query, EFCachePolicy cachePolicy) where TType : class\n        {\n            return Cacheable(query, cachePolicy, new EFCacheDebugInfo());\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"expirationMode\">Defines the expiration mode of the cache item.</param>\n        /// <param name=\"timeout\">The expiration timeout.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static EFCachedDbSet<TType> Cacheable<TType>(\n            this DbSet<TType> query, CacheExpirationMode expirationMode, TimeSpan timeout) where TType : class\n        {\n            return Cacheable(query, new EFCachePolicy(expirationMode, timeout), new EFCacheDebugInfo());\n        }\n\n        /// <summary>\n        /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider.\n        /// </summary>\n        /// <typeparam name=\"TType\">Entity type.</typeparam>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"expirationMode\">Defines the expiration mode of the cache item.</param>\n        /// <param name=\"timeout\">The expiration timeout.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <returns>Provides functionality to evaluate queries against a specific data source.</returns>\n        public static EFCachedDbSet<TType> Cacheable<TType>(\n            this DbSet<TType> query, CacheExpirationMode expirationMode, TimeSpan timeout, EFCacheDebugInfo debugInfo) where TType : class\n        {\n            return Cacheable(query, new EFCachePolicy(expirationMode, timeout), debugInfo);\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFCachedQueryProvider.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing System.Reflection;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.EntityFrameworkCore.Query.Internal;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Defines methods to create and execute queries that are described by an System.Linq.IQueryable object.\n    /// </summary>\n    public class EFCachedQueryProvider : IAsyncQueryProvider\n    {\n        private readonly IEFCacheKeyProvider _cacheKeyProvider;\n        private readonly IEFCacheServiceProvider _cacheServiceProvider;\n        private readonly EFCacheDebugInfo _debugInfo;\n        private readonly EFCachePolicy _cachePolicy;\n        private readonly IQueryable _query;\n\n        private static readonly MethodInfo _fromResultMethodInfo = typeof(Task).GetMethod(\"FromResult\");\n        private static readonly MethodInfo _materializeAsyncMethodInfo = typeof(EFMaterializer).GetMethod(nameof(EFMaterializer.MaterializeAsync));\n\n        /// <summary>\n        /// Defines methods to create and execute queries that are described by an System.Linq.IQueryable object.\n        /// </summary>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <param name=\"cacheKeyProvider\">Gets an EF query and returns its hash to store in the cache.</param>\n        /// <param name=\"cacheServiceProvider\">The Cache Service Provider.</param>\n        public EFCachedQueryProvider(\n            IQueryable query,\n            EFCachePolicy cachePolicy,\n            EFCacheDebugInfo debugInfo,\n            IEFCacheKeyProvider cacheKeyProvider,\n            IEFCacheServiceProvider cacheServiceProvider)\n        {\n            _query = query;\n            _cachePolicy = cachePolicy;\n            _debugInfo = debugInfo;\n            _cacheKeyProvider = cacheKeyProvider;\n            _cacheServiceProvider = cacheServiceProvider;\n            Materializer =\n                new EFMaterializer(_query, _cachePolicy, _debugInfo, _cacheKeyProvider, _cacheServiceProvider);\n        }\n\n        /// <summary>\n        /// Defines methods to create and execute queries that are described by an System.Linq.IQueryable object.\n        /// </summary>\n        public EFMaterializer Materializer { get; }\n\n        /// <summary>\n        /// Constructs an System.Linq.IQueryable of T object that can evaluate the query represented by a specified expression tree.\n        /// </summary>\n        /// <typeparam name=\"TElement\">The type of the elements that is returned.</typeparam>\n        /// <param name=\"expression\">An expression tree that represents a LINQ query.</param>\n        /// <returns>An System.Linq.IQueryable of T that can evaluate the query represented by the specified expression tree.</returns>\n        public IQueryable<TElement> CreateQuery<TElement>(Expression expression)\n        {\n            return (IQueryable<TElement>)CreateQuery(expression);\n        }\n\n        /// <summary>\n        /// Constructs an System.Linq.IQueryable object that can evaluate the query represented by a specified expression tree.\n        /// </summary>\n        /// <param name=\"expression\">An expression tree that represents a LINQ query.</param>\n        /// <returns>An System.Linq.IQueryable that can evaluate the query represented by the specified expression tree.</returns>\n        public IQueryable CreateQuery(Expression expression)\n        {\n            var argumentType = expression.Type.GenericTypeArguments[0];\n            var cachedQueryable = typeof(EFCachedQueryable<>).MakeGenericType(argumentType);\n            var constructorArgs = new object[]\n            {\n                _query.Provider.CreateQuery(expression),\n                _cachePolicy,\n                _debugInfo,\n                _cacheKeyProvider,\n                _cacheServiceProvider\n            };\n            return (IQueryable)Activator.CreateInstance(cachedQueryable, constructorArgs);\n        }\n\n        /// <summary>\n        /// Executes the query represented by a specified expression tree.\n        /// </summary>\n        /// <param name=\"expression\">An expression tree that represents a LINQ query.</param>\n        /// <returns>The value that results from executing the specified query.</returns>\n        public object Execute(Expression expression)\n        {\n            return Materializer.Materialize(expression, () => _query.Provider.Execute(expression));\n        }\n\n        /// <summary>\n        /// Executes the strongly-typed query represented by a specified expression tree.\n        /// </summary>\n        /// <typeparam name=\"TResult\">The type of the value that results from executing the query.</typeparam>\n        /// <param name=\"expression\">An expression tree that represents a LINQ query.</param>\n        /// <returns>The value that results from executing the specified query.</returns>\n        public TResult Execute<TResult>(Expression expression)\n        {\n            return Materializer.Materialize(expression, () => _query.Provider.Execute<TResult>(expression));\n        }\n\n        /// <summary>\n        /// Asynchronously executes the strongly-typed query represented by a specified expression tree.\n        /// </summary>\n        /// <typeparam name=\"TResult\">The type of the value that results from executing the query.</typeparam>\n        /// <param name=\"expression\">An expression tree that represents a LINQ query.</param>\n        /// <param name=\"cancellationToken\">A CancellationToken to observe while waiting for the task to complete.</param>\n        /// <returns>A task that represents the asynchronous operation.  The task result contains the value that results from executing the specified query.</returns>\n        public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)\n        {\n            var type = typeof(TResult);\n\n            if (_query.Provider is EntityQueryProvider eqProvider)\n            {\n                if (isTaskOfT(type))\n                {\n                    var materializeAsyncMethod = _materializeAsyncMethodInfo.MakeGenericMethod(expression.Type);\n                    return (TResult)materializeAsyncMethod.Invoke(Materializer, new object[]\n                            {\n                                expression,\n                                new Func<TResult>(() => eqProvider.ExecuteAsync<TResult>(expression, cancellationToken))\n                            });\n                }\n\n                return Materializer.Materialize(\n                             expression,\n                             () => eqProvider.ExecuteAsync<TResult>(expression, cancellationToken));\n            }\n\n            if (isTaskOfT(type))\n            {\n                var result = Execute(expression);\n                var taskFromResultMethod = _fromResultMethodInfo.MakeGenericMethod(expression.Type);\n                return (TResult)taskFromResultMethod.Invoke(null, new[] { result });\n            }\n\n            return Execute<TResult>(expression);\n        }\n\n        private static bool isTaskOfT(Type type)\n        {\n            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>);\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFCachedQueryable.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing System.Threading;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.EntityFrameworkCore.Query.Internal;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Provides functionality to evaluate queries against a specific data source.\n    /// </summary>\n    /// <typeparam name=\"TType\">Type of the entity.</typeparam>\n    public class EFCachedQueryable<TType> : IOrderedQueryable<TType>, IAsyncEnumerable<TType>\n    {\n        private readonly EFCachedQueryProvider _provider;\n\n        /// <summary>\n        /// Provides functionality to evaluate queries against a specific data source.\n        /// </summary>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <param name=\"cacheKeyProvider\">Gets an EF query and returns its hash to store in the cache.</param>\n        /// <param name=\"cacheServiceProvider\">Cache Service Provider.</param>\n        public EFCachedQueryable(\n            IQueryable<TType> query,\n            EFCachePolicy cachePolicy,\n            EFCacheDebugInfo debugInfo,\n            IEFCacheKeyProvider cacheKeyProvider,\n            IEFCacheServiceProvider cacheServiceProvider)\n        {\n            CachePolicy = cachePolicy;\n            DebugInfo = debugInfo;\n            CacheKeyProvider = cacheKeyProvider;\n            CacheServiceProvider = cacheServiceProvider;\n            Query = query.MarkAsNoTracking();\n            _provider = new EFCachedQueryProvider(Query, cachePolicy, debugInfo, cacheKeyProvider, cacheServiceProvider);\n        }\n\n        /// <summary>\n        /// Asynchronous version of the IEnumerable interface\n        /// </summary>\n        public IAsyncEnumerable<TType> AsyncEnumerable => new EFAsyncEnumerable<TType>(this.AsEnumerable().GetEnumerator());\n\n        /// <summary>\n        /// Gets an EF query and returns its hash to store in the cache.\n        /// </summary>\n        public IEFCacheKeyProvider CacheKeyProvider { get; }\n\n        /// <summary>\n        /// Cache Service Provider.\n        /// </summary>\n        public IEFCacheServiceProvider CacheServiceProvider { get; }\n\n        /// <summary>\n        /// Stores the debug information of the caching process.\n        /// </summary>\n        public EFCacheDebugInfo DebugInfo { get; }\n\n        /// <summary>\n        /// Gets the type of the element(s) that are returned when the expression tree associated with this instance of System.Linq.IQueryable is executed.\n        /// </summary>\n        public Type ElementType => Query.ElementType;\n\n        /// <summary>\n        /// Gets the expression tree that is associated with the instance of System.Linq.IQueryable.\n        /// </summary>\n        public Expression Expression => Query.Expression;\n\n        /// <summary>\n        /// Gets the query provider that is associated with this data source.\n        /// </summary>\n        public IQueryProvider Provider => _provider;\n\n        /// <summary>\n        /// The input EF query.\n        /// </summary>\n        public IQueryable<TType> Query { get; }\n\n        /// <summary>\n        /// Defines the expiration mode of the cache item.\n        /// </summary>\n        public EFCachePolicy CachePolicy { get; }\n\n        /// <summary>\n        /// Returns an enumerator that iterates through a collection.\n        /// </summary>\n        /// <returns>A collections that can be used to iterate through the collection.</returns>\n        public IEnumerator GetEnumerator()\n        {\n            return ((IEnumerable)_provider.Materializer.Materialize(\n                               Query.Expression,\n                               () => Query.ToArray())).GetEnumerator();\n        }\n\n        /// <summary>\n        /// Returns an enumerator that iterates through a collection.\n        /// </summary>\n        /// <returns>A collections that can be used to iterate through the collection.</returns>\n        IEnumerator<TType> IEnumerable<TType>.GetEnumerator()\n        {\n            return ((IEnumerable<TType>)_provider.Materializer.Materialize(\n                                   Query.Expression,\n                                   () => Query.ToArray())).GetEnumerator();\n        }\n\n        /// <summary>\n        /// Returns an enumerator that iterates through a collection.\n        /// </summary>\n        /// <returns>A collections that can be used to iterate through the collection.</returns>\n        public IAsyncEnumerator<TType> GetAsyncEnumerator(CancellationToken cancellationToken)\n        {\n            return new EFAsyncEnumerator<TType>(\n                     ((IEnumerable<TType>)_provider.Materializer.Materialize(\n                                        Query.Expression,\n                                        () => Query.ToArray())).GetEnumerator());\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFChangeTrackerExtensions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing System.Reflection;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.ChangeTracking;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Change Tracker Extensions\n    /// </summary>\n    public static class EFChangeTrackerExtensions\n    {\n        private static readonly MethodInfo _asNoTrackingMethodInfo =\n            typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethod(nameof(EntityFrameworkQueryableExtensions.AsNoTracking));\n\n        /// <summary>\n        /// Find the base types of the given type, recursively.\n        /// </summary>\n        public static IEnumerable<Type> GetBaseTypes(this Type type)\n        {\n            if (type.GetTypeInfo().BaseType == null)\n            {\n                return type.GetInterfaces();\n            }\n\n            return Enumerable.Repeat(type.GetTypeInfo().BaseType, 1)\n                             .Concat(type.GetInterfaces())\n                             .Concat(type.GetInterfaces().SelectMany(GetBaseTypes))\n                             .Concat(type.GetTypeInfo().BaseType.GetBaseTypes());\n        }\n\n        /// <summary>\n        /// Using the ChangeTracker to find names of the changed entities.\n        /// It calls ChangeTracker.DetectChanges() explicitly.\n        /// </summary>\n        public static string[] GetChangedEntityNames(this DbContext dbContext)\n        {\n            var typesList = new List<Type>();\n            foreach (var type in dbContext.GetChangedEntityTypes())\n            {\n                typesList.Add(type);\n                typesList.AddRange(type.GetBaseTypes().Where(t => t != typeof(object)).ToList());\n            }\n\n            var changedEntityNames = typesList\n                .Select(type => type.FullName)\n                .Distinct()\n                .ToArray();\n\n            return changedEntityNames;\n        }\n\n        /// <summary>\n        /// Checks for changes to the entity and all owns entities.\n        /// </summary>\n        private static bool IsEntityChanged(EntityEntry entry)\n        {\n            return entry.State == EntityState.Added\n                   || entry.State == EntityState.Modified\n                   || entry.State == EntityState.Deleted\n                   || entry.References.Any(r => r.TargetEntry != null\n                                                && r.TargetEntry.Metadata.IsOwned()\n                                                && IsEntityChanged(r.TargetEntry));\n        }\n\n        /// <summary>\n        /// Using the ChangeTracker to find types of the changed entities.\n        /// It calls ChangeTracker.DetectChanges() explicitly.\n        /// </summary>\n        public static IEnumerable<Type> GetChangedEntityTypes(this DbContext dbContext)\n        {\n            if (!dbContext.ChangeTracker.AutoDetectChangesEnabled)\n            {\n                // ChangeTracker.Entries() only calls `Try`DetectChanges() behind the scene.\n                dbContext.ChangeTracker.DetectChanges();\n            }\n\n            return dbContext.ChangeTracker.Entries()\n                .Where(IsEntityChanged)\n                .Select(dbEntityEntry => dbEntityEntry.Entity.GetType());\n        }\n\n        /// <summary>\n        /// Applies the AsNoTracking method dynamically\n        /// </summary>\n        public static IQueryable<TType> MarkAsNoTracking<TType>(this IQueryable<TType> query)\n        {\n            if (typeof(TType).GetTypeInfo().IsClass)\n            {\n                return query.Provider.CreateQuery<TType>(\n                    Expression.Call(null, _asNoTrackingMethodInfo.MakeGenericMethod(typeof(TType)), query.Expression));\n            }\n            return query;\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFMaterializer.cs",
    "content": "using System;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing EFSecondLevelCache.Core.Contracts;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Cache Result Container\n    /// </summary>\n    /// <typeparam name=\"T\"></typeparam>\n    public class CacheResult<T>\n    {\n        /// <summary>\n        /// Could read from the cache?\n        /// </summary>\n        public bool CanRead { set; get; }\n\n        /// <summary>\n        /// EFCacheKey value\n        /// </summary>\n        public EFCacheKey CacheKey { set; get; }\n\n        /// <summary>\n        ///  Retrieved result from the cache\n        /// </summary>\n        public T Result { set; get; }\n\n        /// <summary>\n        /// Cache Result Container\n        /// </summary>\n        public CacheResult()\n        {\n        }\n\n        /// <summary>\n        /// Cache Result Container\n        /// </summary>\n        /// <param name=\"canRead\">Could read from the cache?</param>\n        /// <param name=\"cacheKey\">EFCacheKey value</param>\n        /// <param name=\"result\">Retrieved result from the cache</param>\n        public CacheResult(bool canRead, EFCacheKey cacheKey, T result)\n        {\n            CanRead = canRead;\n            CacheKey = cacheKey;\n            Result = result;\n        }\n    }\n\n    /// <summary>\n    /// Defines methods to create and execute queries that are described by an System.Linq.IQueryable object.\n    /// </summary>\n    public class EFMaterializer\n    {\n        private readonly IEFCacheKeyProvider _cacheKeyProvider;\n        private readonly IEFCacheServiceProvider _cacheServiceProvider;\n        private readonly EFCacheDebugInfo _debugInfo;\n        private readonly EFCachePolicy _cachePolicy;\n        private readonly IQueryable _query;\n        private static readonly object _syncLock = new object();\n        private static readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);\n\n\n        /// <summary>\n        /// Defines methods to create and execute queries that are described by an System.Linq.IQueryable object.\n        /// </summary>\n        /// <param name=\"query\">The input EF query.</param>\n        /// <param name=\"cachePolicy\">Defines the expiration mode of the cache item.</param>\n        /// <param name=\"debugInfo\">Stores the debug information of the caching process.</param>\n        /// <param name=\"cacheKeyProvider\">Gets an EF query and returns its hash to store in the cache.</param>\n        /// <param name=\"cacheServiceProvider\">The Cache Service Provider.</param>\n        public EFMaterializer(\n            IQueryable query,\n            EFCachePolicy cachePolicy,\n            EFCacheDebugInfo debugInfo,\n            IEFCacheKeyProvider cacheKeyProvider,\n            IEFCacheServiceProvider cacheServiceProvider)\n        {\n            _query = query;\n            _cachePolicy = cachePolicy;\n            _debugInfo = debugInfo;\n            _cacheKeyProvider = cacheKeyProvider;\n            _cacheServiceProvider = cacheServiceProvider;\n        }\n\n        /// <summary>\n        /// Executes the query represented by a specified expression tree to cache its results.\n        /// </summary>\n        /// <param name=\"expression\">An expression tree that represents a LINQ query.</param>\n        /// <param name=\"materializer\">How to run the query.</param>\n        /// <returns>The value that results from executing the specified query.</returns>\n        public async Task<T> MaterializeAsync<T>(Expression expression, Func<Task<T>> materializer)\n        {\n            await _semaphoreSlim.WaitAsync();\n            try\n            {\n                var cacheResult = readFromCache<T>(expression);\n                if (cacheResult.CanRead)\n                {\n                    return cacheResult.Result;\n                }\n\n                var result = await materializer();\n                _cacheServiceProvider.InsertValue(cacheResult.CacheKey.KeyHash, result,\n                    cacheResult.CacheKey.CacheDependencies, _cachePolicy);\n                return result;\n            }\n            finally\n            {\n                _semaphoreSlim.Release();\n            }\n        }\n\n        /// <summary>\n        /// Executes the query represented by a specified expression tree to cache its results.\n        /// </summary>\n        /// <param name=\"expression\">An expression tree that represents a LINQ query.</param>\n        /// <param name=\"materializer\">How to run the query.</param>\n        /// <returns>The value that results from executing the specified query.</returns>\n        public T Materialize<T>(Expression expression, Func<T> materializer)\n        {\n            lock (_syncLock)\n            {\n                var cacheResult = readFromCache<T>(expression);\n                if (cacheResult.CanRead)\n                {\n                    return cacheResult.Result;\n                }\n\n                var result = materializer();\n                _cacheServiceProvider.InsertValue(cacheResult.CacheKey.KeyHash, result,\n                    cacheResult.CacheKey.CacheDependencies, _cachePolicy);\n                return result;\n            }\n        }\n\n        private CacheResult<T> readFromCache<T>(Expression expression)\n        {\n            var cacheKey = _cacheKeyProvider.GetEFCacheKey(_query, expression, _cachePolicy?.SaltKey);\n            _debugInfo.EFCacheKey = cacheKey;\n            var queryCacheKey = cacheKey.KeyHash;\n            var result = _cacheServiceProvider.GetValue(queryCacheKey);\n            if (Equals(result, _cacheServiceProvider.NullObject))\n            {\n                _debugInfo.IsCacheHit = true;\n                return new CacheResult<T>(true, cacheKey, default);\n            }\n\n            if (result != null)\n            {\n                _debugInfo.IsCacheHit = true;\n                return new CacheResult<T>(true, cacheKey, (T)result);\n            }\n\n            return new CacheResult<T>(false, cacheKey, default);\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFQueryExpressionVisitor.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Globalization;\nusing System.IO;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing System.Reflection;\nusing EFSecondLevelCache.Core.Contracts;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Ref. https://github.com/dotnet/corefx/ -> src/System.Linq.Expressions/src/System/Linq/Expressions/DebugViewWriter.cs\n    /// </summary>\n    public class EFQueryExpressionVisitor : ExpressionVisitor\n    {\n        private const int MaxColumn = 120;\n\n        private const int Tab = 4;\n\n        private readonly TextWriter _out;\n        private readonly Stack<int> _stack = new Stack<int>();\n        private readonly HashSet<string> _types = new HashSet<string>();\n        private int _column;\n\n        private Flow _flow;\n\n        // Associate every unique anonymous LabelTarget in the tree with an integer.\n        // The id is used to create a name for the anonymous LabelTarget.\n        //\n        private Dictionary<LabelTarget, int> _labelIds;\n\n        // Associate every unique anonymous LambdaExpression in the tree with an integer.\n        // The id is used to create a name for the anonymous lambda.\n        //\n        private Dictionary<LambdaExpression, int> _lambdaIds;\n\n        // All the unique lambda expressions in the ET, will be used for displaying all\n        // the lambda definitions.\n        private Queue<LambdaExpression> _lambdas;\n        // Associate every unique anonymous parameter or variable in the tree with an integer.\n        // The id is used to create a name for the anonymous parameter or variable.\n        //\n        private Dictionary<ParameterExpression, int> _paramIds;\n        private EFQueryExpressionVisitor(TextWriter file)\n        {\n            _out = file;\n        }\n\n        [Flags]\n        private enum Flow\n        {\n            None,\n            Space,\n            NewLine,\n\n            Break = 0x8000      // newline if column > MaxColumn\n        }\n\n        private int Base => _stack.Count > 0 ? _stack.Peek() : 0;\n\n        private int Delta { get; set; }\n\n        private int Depth => Base + Delta;\n\n        /// <summary>\n        /// Write out the given AST\n        /// </summary>\n        public static EFQueryDebugView GetDebugView(Expression node)\n        {\n            using (var writer = new StringWriter(CultureInfo.CurrentCulture))\n            {\n                var efQueryExpressionVisitor = new EFQueryExpressionVisitor(writer);\n                efQueryExpressionVisitor.writeTo(node);\n                var types = efQueryExpressionVisitor._types;\n                return new EFQueryDebugView { DebugView = writer.ToString(), Types = types };\n            }\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitBinary(BinaryExpression node)\n        {\n            if (node.NodeType == ExpressionType.ArrayIndex)\n            {\n                parenthesizedVisit(node, node.Left);\n                Out(\"[\");\n                Visit(node.Right);\n                Out(\"]\");\n            }\n            else\n            {\n                bool parenthesizeLeft = needsParentheses(node, node.Left);\n                bool parenthesizeRight = needsParentheses(node, node.Right);\n\n                string op;\n                bool isChecked = false;\n                Flow beforeOp = Flow.Space;\n                switch (node.NodeType)\n                {\n                    case ExpressionType.Assign: op = \"=\"; break;\n                    case ExpressionType.Equal: op = \"==\"; break;\n                    case ExpressionType.NotEqual: op = \"!=\"; break;\n                    case ExpressionType.AndAlso: op = \"&&\"; beforeOp = Flow.Break | Flow.Space; break;\n                    case ExpressionType.OrElse: op = \"||\"; beforeOp = Flow.Break | Flow.Space; break;\n                    case ExpressionType.GreaterThan: op = \">\"; break;\n                    case ExpressionType.LessThan: op = \"<\"; break;\n                    case ExpressionType.GreaterThanOrEqual: op = \">=\"; break;\n                    case ExpressionType.LessThanOrEqual: op = \"<=\"; break;\n                    case ExpressionType.Add: op = \"+\"; break;\n                    case ExpressionType.AddAssign: op = \"+=\"; break;\n                    case ExpressionType.AddAssignChecked: op = \"+=\"; isChecked = true; break;\n                    case ExpressionType.AddChecked: op = \"+\"; isChecked = true; break;\n                    case ExpressionType.Subtract: op = \"-\"; break;\n                    case ExpressionType.SubtractAssign: op = \"-=\"; break;\n                    case ExpressionType.SubtractAssignChecked: op = \"-=\"; isChecked = true; break;\n                    case ExpressionType.SubtractChecked: op = \"-\"; isChecked = true; break;\n                    case ExpressionType.Divide: op = \"/\"; break;\n                    case ExpressionType.DivideAssign: op = \"/=\"; break;\n                    case ExpressionType.Modulo: op = \"%\"; break;\n                    case ExpressionType.ModuloAssign: op = \"%=\"; break;\n                    case ExpressionType.Multiply: op = \"*\"; break;\n                    case ExpressionType.MultiplyAssign: op = \"*=\"; break;\n                    case ExpressionType.MultiplyAssignChecked: op = \"*=\"; isChecked = true; break;\n                    case ExpressionType.MultiplyChecked: op = \"*\"; isChecked = true; break;\n                    case ExpressionType.LeftShift: op = \"<<\"; break;\n                    case ExpressionType.LeftShiftAssign: op = \"<<=\"; break;\n                    case ExpressionType.RightShift: op = \">>\"; break;\n                    case ExpressionType.RightShiftAssign: op = \">>=\"; break;\n                    case ExpressionType.And: op = \"&\"; break;\n                    case ExpressionType.AndAssign: op = \"&=\"; break;\n                    case ExpressionType.Or: op = \"|\"; break;\n                    case ExpressionType.OrAssign: op = \"|=\"; break;\n                    case ExpressionType.ExclusiveOr: op = \"^\"; break;\n                    case ExpressionType.ExclusiveOrAssign: op = \"^=\"; break;\n                    case ExpressionType.Power: op = \"**\"; break;\n                    case ExpressionType.PowerAssign: op = \"**=\"; break;\n                    case ExpressionType.Coalesce: op = \"??\"; break;\n\n                    default:\n                        throw new InvalidOperationException();\n                }\n\n                if (parenthesizeLeft)\n                {\n                    Out(\"(\", Flow.None);\n                }\n\n                Visit(node.Left);\n                if (parenthesizeLeft)\n                {\n                    Out(Flow.None, \")\", Flow.Break);\n                }\n\n                // prepend # to the operator to represent checked op\n                if (isChecked)\n                {\n                    op = string.Format(\n                            CultureInfo.CurrentCulture,\n                            \"#{0}\",\n                            op\n                    );\n                }\n                Out(beforeOp, op, Flow.Space | Flow.Break);\n\n                if (parenthesizeRight)\n                {\n                    Out(\"(\", Flow.None);\n                }\n                Visit(node.Right);\n                if (parenthesizeRight)\n                {\n                    Out(Flow.None, \")\", Flow.Break);\n                }\n            }\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitBlock(BlockExpression node)\n        {\n            Out(\".Block\");\n\n            Out(string.Format(CultureInfo.CurrentCulture, \"«{0}»\", node.Type));\n            addType(node.Type);\n\n            visitDeclarations(node.Variables);\n            Out(\" \");\n            // Use ; to separate expressions in the block\n            visitExpressions('{', ';', node.Expressions);\n\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override CatchBlock VisitCatchBlock(CatchBlock node)\n        {\n            Out(Flow.NewLine, $\"}} .Catch ({node.Test}\");\n            if (node.Variable != null)\n            {\n                Out(Flow.Space, \"\");\n                VisitParameter(node.Variable);\n            }\n            if (node.Filter != null)\n            {\n                Out(\") .If (\", Flow.Break);\n                Visit(node.Filter);\n            }\n            Out(\") {\", Flow.NewLine);\n            indent();\n            Visit(node.Body);\n            dedent();\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitConditional(ConditionalExpression node)\n        {\n            if (isSimpleExpression(node.Test))\n            {\n                Out(\".If (\");\n                Visit(node.Test);\n                Out(\") {\", Flow.NewLine);\n            }\n            else\n            {\n                Out(\".If (\", Flow.NewLine);\n                indent();\n                Visit(node.Test);\n                dedent();\n                Out(Flow.NewLine, \") {\", Flow.NewLine);\n            }\n            indent();\n            Visit(node.IfTrue);\n            dedent();\n            Out(Flow.NewLine, \"} .Else {\", Flow.NewLine);\n            indent();\n            Visit(node.IfFalse);\n            dedent();\n            Out(Flow.NewLine, \"}\");\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitConstant(ConstantExpression node)\n        {\n            printConstant(node, node.Value);\n            return node;\n        }\n\n        private void printConstant(ConstantExpression node, object value)\n        {\n            if (value == null)\n            {\n                Out(\"null\");\n            }\n            else if ((value is string) && node.Type == typeof(string))\n            {\n                Out(string.Format(\n                    CultureInfo.CurrentCulture,\n                    \"\\\"{0}\\\"\",\n                    value));\n            }\n            else if ((value is char) && node.Type == typeof(char))\n            {\n                Out(string.Format(\n                    CultureInfo.CurrentCulture,\n                    \"'{0}'\",\n                    value));\n            }\n            else if (((value is int) && node.Type == typeof(int))\n              || ((value is bool) && node.Type == typeof(bool)))\n            {\n                Out(value.ToString());\n            }\n            else if (typeof(IEnumerable).IsAssignableFrom(value.GetType())\n                        && !node.Type.ToString().Contains(\"Microsoft\"))\n            {\n                Out($\".Constant<{node.Type}>(new[]{{{string.Join(\", \", ((IEnumerable)value).Cast<object>().ToList())}}})\");\n            }\n            else\n            {\n                string suffix = getConstantValueSuffix(node.Type);\n                if (suffix != null)\n                {\n                    Out(value.ToString());\n                    Out(suffix);\n                }\n                else\n                {\n                    Out(string.Format(\n                        CultureInfo.CurrentCulture,\n                        \".Constant<{0}>({1})\",\n                        node.Type,\n                        value));\n                    addType(node.Type);\n                }\n            }\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitDebugInfo(DebugInfoExpression node)\n        {\n            Out(string.Format(\n                CultureInfo.CurrentCulture,\n                \".DebugInfo({0}: {1}, {2} - {3}, {4})\",\n                node.Document.FileName,\n                node.StartLine,\n                node.StartColumn,\n                node.EndLine,\n                node.EndColumn)\n            );\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitDefault(DefaultExpression node)\n        {\n            Out($\".Default({node.Type})\");\n            addType(node.Type);\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override ElementInit VisitElementInit(ElementInit node)\n        {\n            if (node.Arguments.Count == 1)\n            {\n                Visit(node.Arguments[0]);\n            }\n            else\n            {\n                visitExpressions('{', node.Arguments);\n            }\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitExtension(Expression node)\n        {\n            Out(string.Format(CultureInfo.CurrentCulture, \".Extension<{0}>\", node.GetType()));\n\n            if (node.CanReduce)\n            {\n                Out(Flow.Space, \"{\", Flow.NewLine);\n                indent();\n                Visit(node.Reduce());\n                dedent();\n                Out(Flow.NewLine, \"}\");\n            }\n\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitGoto(GotoExpression node)\n        {\n            Out($\".{node.Kind}\", Flow.Space);\n            Out(getLabelTargetName(node.Target), Flow.Space);\n            Out(\"{\", Flow.Space);\n            Visit(node.Value);\n            Out(Flow.Space, \"}\");\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitIndex(IndexExpression node)\n        {\n            if (node.Indexer != null)\n            {\n                outMember(node, node.Object, node.Indexer);\n            }\n            else\n            {\n                parenthesizedVisit(node, node.Object);\n            }\n\n            visitExpressions('[', node.Arguments);\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitInvocation(InvocationExpression node)\n        {\n            Out(\".Invoke \");\n            parenthesizedVisit(node, node.Expression);\n            visitExpressions('(', node.Arguments);\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitLabel(LabelExpression node)\n        {\n            Out(\".Label\", Flow.NewLine);\n            indent();\n            Visit(node.DefaultValue);\n            dedent();\n            newLine();\n            dumpLabel(node.Target);\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <typeparam name=\"T\"></typeparam>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitLambda<T>(Expression<T> node)\n        {\n            Out(\n                string.Format(CultureInfo.CurrentCulture,\n                    \"{0} {1}<{2}>\",\n                    \".Lambda\",\n                    getLambdaName(node),\n                    node.Type\n                )\n            );\n            addType(node.Type);\n\n            if (_lambdas == null)\n            {\n                _lambdas = new Queue<LambdaExpression>();\n            }\n\n            // N^2 performance, for keeping the order of the lambdas.\n            if (!_lambdas.Contains(node))\n            {\n                _lambdas.Enqueue(node);\n            }\n\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitListInit(ListInitExpression node)\n        {\n            Visit(node.NewExpression);\n            visitExpressions('{', ',', node.Initializers, e => VisitElementInit(e));\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitLoop(LoopExpression node)\n        {\n            Out(\".Loop\", Flow.Space);\n            if (node.ContinueLabel != null)\n            {\n                dumpLabel(node.ContinueLabel);\n            }\n            Out(\" {\", Flow.NewLine);\n            indent();\n            Visit(node.Body);\n            dedent();\n            Out(Flow.NewLine, \"}\");\n            if (node.BreakLabel != null)\n            {\n                Out(\"\", Flow.NewLine);\n                dumpLabel(node.BreakLabel);\n            }\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitMember(MemberExpression node)\n        {\n            outMember(node, node.Expression, node.Member);\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"assignment\"></param>\n        /// <returns></returns>\n        protected override MemberAssignment VisitMemberAssignment(MemberAssignment assignment)\n        {\n            Out(assignment.Member.Name);\n            Out(Flow.Space, \"=\", Flow.Space);\n            Visit(assignment.Expression);\n            return assignment;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitMemberInit(MemberInitExpression node)\n        {\n            Visit(node.NewExpression);\n            visitExpressions('{', ',', node.Bindings, e => VisitMemberBinding(e));\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"binding\"></param>\n        /// <returns></returns>\n        protected override MemberListBinding VisitMemberListBinding(MemberListBinding binding)\n        {\n            Out(binding.Member.Name);\n            Out(Flow.Space, \"=\", Flow.Space);\n            visitExpressions('{', ',', binding.Initializers, e => VisitElementInit(e));\n            return binding;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"binding\"></param>\n        /// <returns></returns>\n        protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding binding)\n        {\n            Out(binding.Member.Name);\n            Out(Flow.Space, \"=\", Flow.Space);\n            visitExpressions('{', ',', binding.Bindings, e => VisitMemberBinding(e));\n            return binding;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitMethodCall(MethodCallExpression node)\n        {\n            Out(\".Call \");\n            if (node.Object != null)\n            {\n                parenthesizedVisit(node, node.Object);\n            }\n            else if (node.Method.DeclaringType != null)\n            {\n                Out(node.Method.DeclaringType.ToString());\n            }\n            else\n            {\n                Out(\"<UnknownType>\");\n            }\n            Out(\".\");\n            Out(node.Method.Name);\n            visitExpressions('(', node.Arguments);\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitNew(NewExpression node)\n        {\n            Out($\".New {node.Type}\");\n            addType(node.Type);\n            visitExpressions('(', node.Arguments);\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitNewArray(NewArrayExpression node)\n        {\n            if (node.NodeType == ExpressionType.NewArrayBounds)\n            {\n                // .NewArray MyType[expr1, expr2]\n                Out($\".NewArray {node.Type.GetElementType()}\");\n                addType(node.Type);\n                visitExpressions('[', node.Expressions);\n            }\n            else\n            {\n                // .NewArray MyType {expr1, expr2}\n                Out($\".NewArray {node.Type}\", Flow.Space);\n                addType(node.Type);\n                visitExpressions('{', node.Expressions);\n            }\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitParameter(ParameterExpression node)\n        {\n            // Have '$' for the DebugView of ParameterExpressions\n            Out(\"$\");\n            if (string.IsNullOrEmpty(node.Name))\n            {\n                // If no name if provided, generate a name as $var1, $var2.\n                // No guarantee for not having name conflicts with user provided variable names.\n                //\n                int id = getParamId(node);\n                Out($\"var{id}\");\n            }\n            else\n            {\n                Out(getDisplayName(node.Name));\n            }\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node)\n        {\n            Out(\".RuntimeVariables\");\n            visitExpressions('(', node.Variables);\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitSwitch(SwitchExpression node)\n        {\n            Out(\".Switch \");\n            Out(\"(\");\n            Visit(node.SwitchValue);\n            Out(\") {\", Flow.NewLine);\n            Visit(node.Cases, VisitSwitchCase);\n            if (node.DefaultBody != null)\n            {\n                Out(\".Default:\", Flow.NewLine);\n                indent(); indent();\n                Visit(node.DefaultBody);\n                dedent(); dedent();\n                newLine();\n            }\n            Out(\"}\");\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override SwitchCase VisitSwitchCase(SwitchCase node)\n        {\n            foreach (var test in node.TestValues)\n            {\n                Out(\".Case (\");\n                Visit(test);\n                Out(\"):\", Flow.NewLine);\n            }\n            indent(); indent();\n            Visit(node.Body);\n            dedent(); dedent();\n            newLine();\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitTry(TryExpression node)\n        {\n            Out(\".Try {\", Flow.NewLine);\n            indent();\n            Visit(node.Body);\n            dedent();\n            Visit(node.Handlers, VisitCatchBlock);\n            if (node.Finally != null)\n            {\n                Out(Flow.NewLine, \"} .Finally {\", Flow.NewLine);\n                indent();\n                Visit(node.Finally);\n                dedent();\n            }\n            else if (node.Fault != null)\n            {\n                Out(Flow.NewLine, \"} .Fault {\", Flow.NewLine);\n                indent();\n                Visit(node.Fault);\n                dedent();\n            }\n\n            Out(Flow.NewLine, \"}\");\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitTypeBinary(TypeBinaryExpression node)\n        {\n            parenthesizedVisit(node, node.Expression);\n            switch (node.NodeType)\n            {\n                case ExpressionType.TypeIs:\n                    Out(Flow.Space, \".Is\", Flow.Space);\n                    break;\n                case ExpressionType.TypeEqual:\n                    Out(Flow.Space, \".TypeEqual\", Flow.Space);\n                    break;\n            }\n            Out(node.TypeOperand.ToString());\n            return node;\n        }\n\n        /// <summary>\n        ///\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        protected override Expression VisitUnary(UnaryExpression node)\n        {\n            switch (node.NodeType)\n            {\n                case ExpressionType.Convert:\n                    Out($\"({node.Type})\");\n                    addType(node.Type);\n                    break;\n                case ExpressionType.ConvertChecked:\n                    Out($\"#({node.Type})\");\n                    addType(node.Type);\n                    break;\n                case ExpressionType.TypeAs:\n                    break;\n                case ExpressionType.Not:\n                    Out(node.Type == typeof(bool) ? \"!\" : \"~\");\n                    break;\n                case ExpressionType.OnesComplement:\n                    Out(\"~\");\n                    break;\n                case ExpressionType.Negate:\n                    Out(\"-\");\n                    break;\n                case ExpressionType.NegateChecked:\n                    Out(\"#-\");\n                    break;\n                case ExpressionType.UnaryPlus:\n                    Out(\"+\");\n                    break;\n                case ExpressionType.ArrayLength:\n                    break;\n                case ExpressionType.Quote:\n                    Out(\"'\");\n                    break;\n                case ExpressionType.Throw:\n                    if (node.Operand == null)\n                    {\n                        Out(\".Rethrow\");\n                    }\n                    else\n                    {\n                        Out(\".Throw\", Flow.Space);\n                    }\n                    break;\n                case ExpressionType.IsFalse:\n                    Out(\".IsFalse\");\n                    break;\n                case ExpressionType.IsTrue:\n                    Out(\".IsTrue\");\n                    break;\n                case ExpressionType.Decrement:\n                    Out(\".Decrement\");\n                    break;\n                case ExpressionType.Increment:\n                    Out(\".Increment\");\n                    break;\n                case ExpressionType.PreDecrementAssign:\n                    Out(\"--\");\n                    break;\n                case ExpressionType.PreIncrementAssign:\n                    Out(\"++\");\n                    break;\n                case ExpressionType.Unbox:\n                    Out(\".Unbox\");\n                    break;\n            }\n\n            parenthesizedVisit(node, node.Operand);\n\n            switch (node.NodeType)\n            {\n                case ExpressionType.TypeAs:\n                    Out(Flow.Space, \".As\", Flow.Space | Flow.Break);\n                    Out(node.Type.ToString());\n                    addType(node.Type);\n                    break;\n\n                case ExpressionType.ArrayLength:\n                    Out(\".Length\");\n                    break;\n\n                case ExpressionType.PostDecrementAssign:\n                    Out(\"--\");\n                    break;\n\n                case ExpressionType.PostIncrementAssign:\n                    Out(\"++\");\n                    break;\n            }\n            return node;\n        }\n\n        /// <summary>\n        /// Return true if the input string contains any whitespace character.\n        /// Otherwise false.\n        /// </summary>\n        private static bool containsWhiteSpace(string name)\n        {\n            foreach (char c in name)\n            {\n                if (char.IsWhiteSpace(c))\n                {\n                    return true;\n                }\n            }\n            return false;\n        }\n\n        private static string getConstantValueSuffix(Type type)\n        {\n            if (type == typeof(uint))\n            {\n                return \"U\";\n            }\n            if (type == typeof(long))\n            {\n                return \"L\";\n            }\n            if (type == typeof(ulong))\n            {\n                return \"UL\";\n            }\n            if (type == typeof(double))\n            {\n                return \"D\";\n            }\n            if (type == typeof(float))\n            {\n                return \"F\";\n            }\n            if (type == typeof(decimal))\n            {\n                return \"M\";\n            }\n            return null;\n        }\n\n        private static string getDisplayName(string name)\n        {\n            if (containsWhiteSpace(name))\n            {\n                // if name has whitespace in it, quote it\n                return quoteName(name);\n            }\n            else\n            {\n                return name;\n            }\n        }\n\n        private static int getId<T>(T e, ref Dictionary<T, int> ids)\n        {\n            if (ids == null)\n            {\n                ids = new Dictionary<T, int> { { e, 1 } };\n                return 1;\n            }\n            else\n            {\n                if (!ids.TryGetValue(e, out int id))\n                {\n                    // e is met the first time\n                    id = ids.Count + 1;\n                    ids.Add(e, id);\n                }\n                return id;\n            }\n        }\n\n        // the greater the higher\n\n        private static int getOperatorPrecedence(Expression node)\n        {\n            // Roughly matches C# operator precedence, with some additional\n            // operators. Also things which are not binary/unary expressions,\n            // such as conditional and type testing, don't use this mechanism.\n            switch (node.NodeType)\n            {\n                // Assignment\n                case ExpressionType.Assign:\n                case ExpressionType.ExclusiveOrAssign:\n                case ExpressionType.AddAssign:\n                case ExpressionType.AddAssignChecked:\n                case ExpressionType.SubtractAssign:\n                case ExpressionType.SubtractAssignChecked:\n                case ExpressionType.DivideAssign:\n                case ExpressionType.ModuloAssign:\n                case ExpressionType.MultiplyAssign:\n                case ExpressionType.MultiplyAssignChecked:\n                case ExpressionType.LeftShiftAssign:\n                case ExpressionType.RightShiftAssign:\n                case ExpressionType.AndAssign:\n                case ExpressionType.OrAssign:\n                case ExpressionType.PowerAssign:\n                case ExpressionType.Coalesce:\n                    return 1;\n\n                // Conditional (?:) would go here\n\n                // Conditional OR\n                case ExpressionType.OrElse:\n                    return 2;\n\n                // Conditional AND\n                case ExpressionType.AndAlso:\n                    return 3;\n\n                // Logical OR\n                case ExpressionType.Or:\n                    return 4;\n\n                // Logical XOR\n                case ExpressionType.ExclusiveOr:\n                    return 5;\n\n                // Logical AND\n                case ExpressionType.And:\n                    return 6;\n\n                // Equality\n                case ExpressionType.Equal:\n                case ExpressionType.NotEqual:\n                    return 7;\n\n                // Relational, type testing\n                case ExpressionType.GreaterThan:\n                case ExpressionType.LessThan:\n                case ExpressionType.GreaterThanOrEqual:\n                case ExpressionType.LessThanOrEqual:\n                case ExpressionType.TypeAs:\n                case ExpressionType.TypeIs:\n                case ExpressionType.TypeEqual:\n                    return 8;\n\n                // Shift\n                case ExpressionType.LeftShift:\n                case ExpressionType.RightShift:\n                    return 9;\n\n                // Additive\n                case ExpressionType.Add:\n                case ExpressionType.AddChecked:\n                case ExpressionType.Subtract:\n                case ExpressionType.SubtractChecked:\n                    return 10;\n\n                // Multiplicative\n                case ExpressionType.Divide:\n                case ExpressionType.Modulo:\n                case ExpressionType.Multiply:\n                case ExpressionType.MultiplyChecked:\n                    return 11;\n\n                // Unary\n                case ExpressionType.Negate:\n                case ExpressionType.NegateChecked:\n                case ExpressionType.UnaryPlus:\n                case ExpressionType.Not:\n                case ExpressionType.Convert:\n                case ExpressionType.ConvertChecked:\n                case ExpressionType.PreIncrementAssign:\n                case ExpressionType.PreDecrementAssign:\n                case ExpressionType.OnesComplement:\n                case ExpressionType.Increment:\n                case ExpressionType.Decrement:\n                case ExpressionType.IsTrue:\n                case ExpressionType.IsFalse:\n                case ExpressionType.Unbox:\n                case ExpressionType.Throw:\n                    return 12;\n\n                // Power, which is not in C#\n                // But VB/Python/Ruby put it here, above unary.\n                case ExpressionType.Power:\n                    return 13;\n\n                // Primary, which includes all other node types:\n                //   member access, calls, indexing, new.\n                case ExpressionType.PostIncrementAssign:\n                case ExpressionType.PostDecrementAssign:\n                default:\n                    return 14;\n\n                // These aren't expressions, so never need parentheses:\n                //   constants, variables\n                case ExpressionType.Constant:\n                case ExpressionType.Parameter:\n                    return 15;\n            }\n        }\n\n        private static bool isSimpleExpression(Expression node)\n        {\n            if (node is BinaryExpression binary)\n            {\n                return !(binary.Left is BinaryExpression || binary.Right is BinaryExpression);\n            }\n\n            return false;\n        }\n\n\n        private static bool needsParentheses(Expression parent, Expression child)\n        {\n            Debug.Assert(parent != null);\n            if (child == null)\n            {\n                return false;\n            }\n\n            // Some nodes always have parentheses because of how they are\n            // displayed, for example: \".Unbox(obj.Foo)\"\n            switch (parent.NodeType)\n            {\n                case ExpressionType.Increment:\n                case ExpressionType.Decrement:\n                case ExpressionType.IsTrue:\n                case ExpressionType.IsFalse:\n                case ExpressionType.Unbox:\n                    return true;\n            }\n\n            int childOpPrec = getOperatorPrecedence(child);\n            int parentOpPrec = getOperatorPrecedence(parent);\n\n            if (childOpPrec == parentOpPrec)\n            {\n                // When parent op and child op has the same precedence,\n                // we want to be a little conservative to have more clarity.\n                // Parentheses are not needed if\n                // 1) Both ops are &&, ||, &, |, or ^, all of them are the only\n                // op that has the precedence.\n                // 2) Parent op is + or *, e.g. x + (y - z) can be simplified to\n                // x + y - z.\n                // 3) Parent op is -, / or %, and the child is the left operand.\n                // In this case, if left and right operand are the same, we don't\n                // remove parenthesis, e.g. (x + y) - (x + y)\n                //\n                switch (parent.NodeType)\n                {\n                    case ExpressionType.AndAlso:\n                    case ExpressionType.OrElse:\n                    case ExpressionType.And:\n                    case ExpressionType.Or:\n                    case ExpressionType.ExclusiveOr:\n                        // Since these ops are the only ones on their precedence,\n                        // the child op must be the same.\n                        Debug.Assert(child.NodeType == parent.NodeType);\n                        // We remove the parenthesis, e.g. x && y && z\n                        return false;\n                    case ExpressionType.Add:\n                    case ExpressionType.AddChecked:\n                    case ExpressionType.Multiply:\n                    case ExpressionType.MultiplyChecked:\n                        return false;\n                    case ExpressionType.Subtract:\n                    case ExpressionType.SubtractChecked:\n                    case ExpressionType.Divide:\n                    case ExpressionType.Modulo:\n                        BinaryExpression binary = parent as BinaryExpression;\n                        Debug.Assert(binary != null);\n                        // Need to have parenthesis for the right operand.\n                        return child == binary.Right;\n                }\n                return true;\n            }\n\n            // Special case: negate of a constant needs parentheses, to\n            // disambiguate it from a negative constant.\n            if (child.NodeType == ExpressionType.Constant\n                && (parent.NodeType == ExpressionType.Negate || parent.NodeType == ExpressionType.NegateChecked))\n            {\n                return true;\n            }\n\n            // If the parent op has higher precedence, need parentheses for the child.\n            return childOpPrec < parentOpPrec;\n        }\n\n        private static string quoteName(string name)\n        {\n            return string.Format(CultureInfo.CurrentCulture, \"'{0}'\", name);\n        }\n\n        private Flow checkBreak(Flow flow)\n        {\n            if ((flow & Flow.Break) != 0)\n            {\n                if (_column > (MaxColumn + Depth))\n                {\n                    flow = Flow.NewLine;\n                }\n                else\n                {\n                    flow &= ~Flow.Break;\n                }\n            }\n            return flow;\n        }\n\n        private void dedent()\n        {\n            Delta -= Tab;\n        }\n\n        private void dumpLabel(LabelTarget target)\n        {\n            Out(string.Format(CultureInfo.CurrentCulture, \".LabelTarget {0}:\", getLabelTargetName(target)));\n        }\n\n        private Flow getFlow(Flow flow)\n        {\n            var last = checkBreak(_flow);\n            flow = checkBreak(flow);\n\n            // Get the biggest flow that is requested None < Space < NewLine\n            return (Flow)Math.Max((int)last, (int)flow);\n        }\n\n        private int getLabelTargetId(LabelTarget target)\n        {\n            Debug.Assert(string.IsNullOrEmpty(target.Name));\n            return getId(target, ref _labelIds);\n        }\n\n        private string getLabelTargetName(LabelTarget target)\n        {\n            if (string.IsNullOrEmpty(target.Name))\n            {\n                // Create the label target name as #Label1, #Label2, etc.\n                return string.Format(CultureInfo.CurrentCulture, \"#Label{0}\", getLabelTargetId(target));\n            }\n            else\n            {\n                return getDisplayName(target.Name);\n            }\n        }\n\n        private int getLambdaId(LambdaExpression le)\n        {\n            Debug.Assert(string.IsNullOrEmpty(le.Name));\n            return getId(le, ref _lambdaIds);\n        }\n\n        private string getLambdaName(LambdaExpression lambda)\n        {\n            if (string.IsNullOrEmpty(lambda.Name))\n            {\n                return $\"#Lambda{getLambdaId(lambda)}\";\n            }\n            return getDisplayName(lambda.Name);\n        }\n\n        private int getParamId(ParameterExpression p)\n        {\n            Debug.Assert(string.IsNullOrEmpty(p.Name));\n            return getId(p, ref _paramIds);\n        }\n\n        private void indent()\n        {\n            Delta += Tab;\n        }\n\n        private void newLine()\n        {\n            _flow = Flow.NewLine;\n        }\n\n        private void Out(string s)\n        {\n            Out(Flow.None, s, Flow.None);\n        }\n\n        private void Out(Flow before, string s)\n        {\n            Out(before, s, Flow.None);\n        }\n\n        private void Out(string s, Flow after)\n        {\n            Out(Flow.None, s, after);\n        }\n\n        private void Out(Flow before, string s, Flow after)\n        {\n            switch (getFlow(before))\n            {\n                case Flow.None:\n                    break;\n                case Flow.Space:\n                    write(\" \");\n                    break;\n                case Flow.NewLine:\n                    writeLine();\n                    write(new string(' ', Depth));\n                    break;\n            }\n            write(s);\n            _flow = after;\n        }\n\n        // Prints \".instanceField\" or \"declaringType.staticField\"\n        private void outMember(Expression node, Expression instance, MemberInfo member)\n        {\n            if (instance != null)\n            {\n                parenthesizedVisit(node, instance);\n                Out($\".{member.Name}\");\n\n                if (instance is ConstantExpression instanceExp)\n                {\n                    var memberInfoValue = getMemberInfoValue(member, instanceExp.Value);\n                    printConstant(instanceExp, memberInfoValue);\n                }\n            }\n            else\n            {\n                // For static members, include the type name\n                Out($\"{member.DeclaringType}.{member.Name}\");\n            }\n        }\n\n        private static object getMemberInfoValue(MemberInfo memberInfo, object forObject)\n        {\n            var field = memberInfo as FieldInfo;\n            if (field != null)\n            {\n                return field.GetValue(forObject);\n            }\n\n            var property = memberInfo as PropertyInfo;\n            if (property != null)\n            {\n                return property.GetValue(forObject, null);\n            }\n\n            return null;\n        }\n\n        private void parenthesizedVisit(Expression parent, Expression nodeToVisit)\n        {\n            if (needsParentheses(parent, nodeToVisit))\n            {\n                Out(\"(\");\n                Visit(nodeToVisit);\n                Out(\")\");\n            }\n            else\n            {\n                Visit(nodeToVisit);\n            }\n        }\n\n        private void visitDeclarations(IList<ParameterExpression> expressions)\n        {\n            visitExpressions('(', ',', expressions, variable =>\n            {\n                Out(variable.Type.ToString());\n                if (variable.IsByRef)\n                {\n                    Out(\"&\");\n                }\n                Out(\" \");\n                VisitParameter(variable);\n            });\n        }\n\n        private void visitExpressions<T>(char open, IList<T> expressions) where T : Expression\n        {\n            visitExpressions<T>(open, ',', expressions);\n        }\n\n        private void visitExpressions<T>(char open, char separator, IList<T> expressions) where T : Expression\n        {\n            visitExpressions(open, separator, expressions, e => Visit(e));\n        }\n\n        private void visitExpressions<T>(char open, char separator, IList<T> expressions, Action<T> visit)\n        {\n            Out(open.ToString());\n\n            if (expressions != null)\n            {\n                indent();\n                bool isFirst = true;\n                foreach (T e in expressions)\n                {\n                    if (isFirst)\n                    {\n                        if (open == '{' || expressions.Count > 1)\n                        {\n                            newLine();\n                        }\n                        isFirst = false;\n                    }\n                    else\n                    {\n                        Out(separator.ToString(), Flow.NewLine);\n                    }\n                    visit(e);\n                }\n                dedent();\n            }\n\n            char close;\n            switch (open)\n            {\n                case '(': close = ')'; break;\n                case '{': close = '}'; break;\n                case '[': close = ']'; break;\n                case '<': close = '>'; break;\n                default: throw new InvalidOperationException();\n            }\n\n            if (open == '{')\n            {\n                newLine();\n            }\n            Out(close.ToString(), Flow.Break);\n        }\n\n        private void write(string s)\n        {\n            _out.Write(s);\n            _column += s.Length;\n        }\n\n        private void writeLambda(LambdaExpression lambda)\n        {\n            Out(\n                string.Format(\n                    CultureInfo.CurrentCulture,\n                    \".Lambda {0}<{1}>\",\n                    getLambdaName(lambda),\n                    lambda.Type)\n            );\n\n            visitDeclarations(lambda.Parameters);\n\n            Out(Flow.Space, \"{\", Flow.NewLine);\n            indent();\n            Visit(lambda.Body);\n            dedent();\n            Out(Flow.NewLine, \"}\");\n            Debug.Assert(_stack.Count == 0);\n        }\n\n        private void writeLine()\n        {\n            _out.WriteLine();\n            _column = 0;\n        }\n\n        private void writeTo(Expression node)\n        {\n            if (node is LambdaExpression lambda)\n            {\n                writeLambda(lambda);\n            }\n            else\n            {\n                Visit(node);\n                Debug.Assert(_stack.Count == 0);\n            }\n\n            //\n            // Output all lambda expression definitions.\n            // in the order of their appearances in the tree.\n            //\n            while (_lambdas?.Count > 0)\n            {\n                writeLine();\n                writeLine();\n                writeLambda(_lambdas.Dequeue());\n            }\n        }\n\n        private void addType(Type type)\n        {\n            var typeInfo = type.GetTypeInfo();\n            if (typeInfo.IsGenericType)\n            {\n                foreach (var genericType in type.GetGenericArguments())\n                {\n                    var genericTypeInfo = genericType.GetTypeInfo();\n                    if (genericTypeInfo.IsGenericType)\n                    {\n                        addType(genericType);\n                    }\n                    else\n                    {\n                        if (genericTypeInfo.IsClass)\n                        {\n                            var item = genericType.ToString();\n                            if (!isCompilerGenerated(item))\n                            {\n                                _types.Add(item);\n                            }\n                        }\n                    }\n                }\n            }\n            else\n            {\n                if (typeInfo.IsClass)\n                {\n                    var item = type.ToString();\n                    if (!isCompilerGenerated(item))\n                    {\n                        _types.Add(item);\n                    }\n                }\n            }\n        }\n\n        private bool isCompilerGenerated(string name)\n        {\n            return name.Contains(\"c__DisplayClass\");\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFSecondLevelCache.Core.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <Description>Entity Framework Core Second Level Caching Library.</Description>\n    <VersionPrefix>2.9.1</VersionPrefix>\n    <Authors>Vahid Nasiri</Authors>\n    <TargetFrameworks>netstandard2.0;net461;</TargetFrameworks>\n    <TargetFrameworks Condition=\"'$(OS)' != 'Windows_NT'\">netstandard2.0</TargetFrameworks>\n    <GenerateDocumentationFile>true</GenerateDocumentationFile>\n    <AssemblyName>EFSecondLevelCache.Core</AssemblyName>\n    <PackageId>EFSecondLevelCache.Core</PackageId>\n    <PackageTags>EntityFramework;Cache;Caching;SecondLevelCache;EFCore;ORM;.NET Core;aspnetcore</PackageTags>\n    <PackageProjectUrl>https://github.com/VahidN/EFSecondLevelCache.Core</PackageProjectUrl>\n    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>\n    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>\n    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>\n    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n  </PropertyGroup>\n  <ItemGroup Condition=\" '$(TargetFramework)' == 'net461' \">\n    <Reference Include=\"System\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n    <PackageReference Include=\"CacheManager.Core\" Version=\"1.2.0\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.0\" />\n  </ItemGroup>\n  <PropertyGroup Condition=\"'$(TargetFramework)' == 'net461'\">\n    <DefineConstants>NET4_6_1</DefineConstants>\n  </PropertyGroup>\n  <ItemGroup Condition=\" '$(TargetFramework)' == 'netstandard2.0' \">\n    <PackageReference Include=\"CacheManager.Core\" Version=\"1.2.0\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.0\" />\n  </ItemGroup>\n  <PropertyGroup Condition=\"'$(TargetFramework)' == 'netstandard2.0'\">\n    <DefineConstants>NETSTANDARD2_0</DefineConstants>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n    <PlatformTarget>anycpu</PlatformTarget>\n  </PropertyGroup>\n</Project>"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFServiceCollectionExtensions.cs",
    "content": "﻿using System;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// ServiceCollection Extensions\n    /// </summary>\n    public static class EFServiceCollectionExtensions\n    {\n        /// <summary>\n        /// A collection of service descriptors.\n        /// </summary>\n        public static IServiceCollection ServiceCollection { get; set; }\n\n        /// <summary>\n        /// Registers the required services of the EFSecondLevelCache.Core.\n        /// </summary>\n        public static IServiceCollection AddEFSecondLevelCache(this IServiceCollection services)\n        {\n            services.AddSingleton<IEFCacheKeyHashProvider, EFCacheKeyHashProvider>();\n            services.AddSingleton<IEFCacheKeyProvider, EFCacheKeyProvider>();\n            services.AddSingleton<IEFCacheServiceProvider, EFCacheServiceProvider>();\n\n            ServiceCollection = services;\n            return services;\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/EFStaticServiceProvider.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// A lazy loaded thread-safe singleton App ServiceProvider.\n    /// It's required for static `Cacheable()` methods.\n    /// </summary>\n    public static class EFStaticServiceProvider\n    {\n        private static readonly Lazy<IServiceProvider> _serviceProviderBuilder =\n            new Lazy<IServiceProvider>(getServiceProvider, LazyThreadSafetyMode.ExecutionAndPublication);\n\n        /// <summary>\n        /// Defines a mechanism for retrieving a service object.\n        /// </summary>\n        public static IServiceProvider Instance { get; } = _serviceProviderBuilder.Value;\n\n        private static IServiceProvider getServiceProvider()\n        {\n            var serviceProvider = EFServiceCollectionExtensions.ServiceCollection?.BuildServiceProvider();\n            return serviceProvider ?? throw new InvalidOperationException(\"Please add `AddEFSecondLevelCache()` method to your `IServiceCollection`.\");\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/ParallelExtensions.cs",
    "content": "using System;\nusing System.Threading;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// Reader writer locking utils\n    /// </summary>\n    public static class ParallelExtensions\n    {\n        /// <summary>\n        /// Tries to enter the lock in read mode, with an optional integer time-out.\n        /// </summary>\n        public static void TryReadLocked(this ReaderWriterLockSlim readerWriterLock,\n                                         Action action, int timeout = Timeout.Infinite)\n        {\n            if (!readerWriterLock.TryEnterReadLock(timeout))\n            {\n                throw new TimeoutException();\n            }\n            try\n            {\n                action();\n            }\n            finally\n            {\n                readerWriterLock.ExitReadLock();\n            }\n        }\n\n        /// <summary>\n        /// Tries to enter the lock in write mode, with an optional time-out.\n        /// </summary>\n        public static void TryWriteLocked(this ReaderWriterLockSlim readerWriterLock,\n                                          Action action, int timeout = Timeout.Infinite)\n        {\n            if (!readerWriterLock.TryEnterWriteLock(timeout))\n            {\n                throw new TimeoutException();\n            }\n            try\n            {\n                action();\n            }\n            finally\n            {\n                readerWriterLock.ExitWriteLock();\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled through the following\n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"EFSecondLevelCache.Core\")]\n[assembly: AssemblyTrademark(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible\n// to COM components.  If you need to access a type in this assembly from\n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n// The following GUID is for the ID of the typelib if this project is exposed to COM\n[assembly: Guid(\"755d6a18-e67a-4e14-8289-bd33a901c52b\")]\n"
  },
  {
    "path": "src/EFSecondLevelCache.Core/XxHashUnsafe.cs",
    "content": "﻿\nusing System;\n\nnamespace EFSecondLevelCache.Core\n{\n    /// <summary>\n    /// xxHash is an extremely fast non-cryptographic Hash algorithm, working at speeds close to RAM limits.\n    /// http://code.google.com/p/xxhash/\n    /// </summary>\n    public static class XxHashUnsafe\n    {\n        private const uint Prime1 = 2654435761U;\n        private const uint Prime2 = 2246822519U;\n        private const uint Prime3 = 3266489917U;\n        private const uint Prime4 = 668265263U;\n        private const int Prime5 = 0x165667b1;\n        private const uint Seed = 0xc58f1a7b;\n\n        /// <summary>\n        /// Computes the xxHash of the input string. xxHash is an extremely fast non-cryptographic Hash algorithm.\n        /// </summary>\n        /// <param name=\"data\">the input string</param>\n        /// <returns>xxHash</returns>\n        public static unsafe UInt32 ComputeHash(string data)\n        {\n            fixed (char* input = data)\n            {\n                return hash((byte*)input, (uint)data.Length * sizeof(char), Seed);\n            }\n        }\n\n        /// <summary>\n        /// Computes the xxHash of the input byte array. xxHash is an extremely fast non-cryptographic Hash algorithm.\n        /// </summary>\n        /// <param name=\"data\">the input byte array</param>\n        /// <returns>xxHash</returns>\n        public static unsafe uint ComputeHash(byte[] data)\n        {\n            fixed (byte* input = &data[0])\n            {\n                return hash(input, (uint)data.Length, Seed);\n            }\n        }\n\n        /// <summary>\n        /// Computes the xxHash of the input byte array. xxHash is an extremely fast non-cryptographic Hash algorithm.\n        /// </summary>\n        /// <param name=\"data\">the input byte array</param>\n        /// <param name=\"offset\">start offset</param>\n        /// <param name=\"len\">length</param>\n        /// <param name=\"seed\">initial seed</param>\n        /// <returns>xxHash</returns>\n        public static unsafe uint ComputeHash(byte[] data, int offset, uint len, uint seed)\n        {\n            fixed (byte* input = &data[offset])\n            {\n                return hash(input, len, seed);\n            }\n        }\n\n        private unsafe static uint hash(byte* data, uint len, uint seed)\n        {\n            if (len < 16)\n                return hashSmall(data, len, seed);\n            var v1 = seed + Prime1;\n            var v2 = v1 * Prime2 + len;\n            var v3 = v2 * Prime3;\n            var v4 = v3 * Prime4;\n            var p = (uint*)data;\n            var limit = (uint*)(data + len - 16);\n            while (p < limit)\n            {\n                v1 += rotl32(v1, 13); v1 *= Prime1; v1 += *p; p++;\n                v2 += rotl32(v2, 11); v2 *= Prime1; v2 += *p; p++;\n                v3 += rotl32(v3, 17); v3 *= Prime1; v3 += *p; p++;\n                v4 += rotl32(v4, 19); v4 *= Prime1; v4 += *p; p++;\n            }\n            p = limit;\n            v1 += rotl32(v1, 17); v2 += rotl32(v2, 19); v3 += rotl32(v3, 13); v4 += rotl32(v4, 11);\n            v1 *= Prime1; v2 *= Prime1; v3 *= Prime1; v4 *= Prime1;\n            v1 += *p; p++; v2 += *p; p++; v3 += *p; p++; v4 += *p;\n            v1 *= Prime2; v2 *= Prime2; v3 *= Prime2; v4 *= Prime2;\n            v1 += rotl32(v1, 11); v2 += rotl32(v2, 17); v3 += rotl32(v3, 19); v4 += rotl32(v4, 13);\n            v1 *= Prime3; v2 *= Prime3; v3 *= Prime3; v4 *= Prime3;\n            var crc = v1 + rotl32(v2, 3) + rotl32(v3, 6) + rotl32(v4, 9);\n            crc ^= crc >> 11;\n            crc += (Prime4 + len) * Prime1;\n            crc ^= crc >> 15;\n            crc *= Prime2;\n            crc ^= crc >> 13;\n            return crc;\n        }\n\n        private unsafe static uint hashSmall(byte* data, uint len, uint seed)\n        {\n            var p = data;\n            var bEnd = data + len;\n            var limit = bEnd - 4;\n            var idx = seed + Prime1;\n            uint crc = Prime5;\n            while (p < limit)\n            {\n                crc += (*(uint*)p) + idx;\n                idx++;\n                crc += rotl32(crc, 17) * Prime4;\n                crc *= Prime1;\n                p += 4;\n            }\n            while (p < bEnd)\n            {\n                crc += (*p) + idx;\n                idx++;\n                crc *= Prime1;\n                p++;\n            }\n            crc += len;\n            crc ^= crc >> 15;\n            crc *= Prime2;\n            crc ^= crc >> 13;\n            crc *= Prime3;\n            crc ^= crc >> 16;\n            return crc;\n        }\n\n        private static UInt32 rotl32(UInt32 x, int r)\n        {\n            return (x << r) | (x >> (32 - r));\n        }\n    }\n}"
  },
  {
    "path": "src/EFSecondLevelCache.Core/_0-restore.bat",
    "content": "rmdir /S /Q bin\nrmdir /S /Q obj\ndotnet restore\npause"
  },
  {
    "path": "src/EFSecondLevelCache.Core/_1-dotnet_pack.bat",
    "content": "dotnet pack -c release\npause"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/App_Data/.gitkeep.txt",
    "content": ""
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/Controllers/HomeController.cs",
    "content": "﻿using System.Linq;\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer;\nusing Microsoft.AspNetCore.Mvc;\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities;\nusing EFSecondLevelCache.Core.Contracts;\nusing System.Threading.Tasks;\nusing Microsoft.EntityFrameworkCore;\nusing System;\nusing System.Collections.Generic;\nusing AutoMapper;\nusing EFSecondLevelCache.Core.AspNetCoreSample.Models;\nusing AutoMapper.QueryableExtensions;\nusing EFSecondLevelCache.Core.AspNetCoreSample.Others;\n\nnamespace EFSecondLevelCache.Core.AspNetCoreSample.Controllers\n{\n    public class HomeController : Controller\n    {\n        private readonly SampleContext _context;\n        private readonly IMapper _mapper;\n        private static List<Post> _inMemoryPosts;\n\n        public HomeController(SampleContext context, IMapper mapper)\n        {\n            _context = context;\n            _mapper = mapper;\n            cacheInMemory();\n        }\n\n        private void cacheInMemory()\n        {\n            if (_inMemoryPosts == null)\n            {\n                _inMemoryPosts = _context.Set<Post>().AsNoTracking().ToList();\n            }\n        }\n\n        /// <summary>\n        /// Get https://localhost:5001/home/RunInMemory\n        /// </summary>\n        public IActionResult RunInMemory()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var post1 = _inMemoryPosts.AsQueryable()\n                .Where(x => x.Id > 0)\n                .OrderBy(x => x.Id)\n                .Cacheable(debugInfo)\n                .FirstOrDefault();\n            return Json(new { post1?.Title, debugInfo });\n        }\n\n        public async Task<IActionResult> Index()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var post1 = await _context.Set<Post>()\n                .Where(x => x.Id > 0)\n                .OrderBy(x => x.Id)\n                .Cacheable(debugInfo)\n                .FirstOrDefaultAsync();\n            return Json(new { post1.Title, debugInfo });\n        }\n\n        public async Task<IActionResult> TaskWhenAll()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var task1 = _context.Set<Post>().Where(x => x.Id > 0).Cacheable(debugInfo).ToListAsync();\n            var results = await Task.WhenAll(task1);\n            return Json(new { results, debugInfo });\n        }\n\n        public async Task<IActionResult> MapToDtoBefore()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var posts = await _context.Set<Post>()\n                .Where(x => x.Id > 0)\n                .OrderBy(x => x.Id)\n                .ProjectTo<PostDto>(configuration: _mapper.ConfigurationProvider)\n                .Cacheable(debugInfo)\n                .ToListAsync();\n            return Json(new { posts, debugInfo });\n        }\n\n        public async Task<IActionResult> MapToDtoAfter()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var posts = await _context.Set<Post>()\n                .Where(x => x.Id > 0)\n                .OrderBy(x => x.Id)\n                .Cacheable(debugInfo)\n                .ProjectTo<PostDto>(configuration: _mapper.ConfigurationProvider)\n                .ToListAsync();\n            return Json(new { posts, debugInfo });\n        }\n\n        /// <summary>\n        /// Get https://localhost:5001/home/WithSlidingExpiration\n        /// </summary>\n        public async Task<IActionResult> WithSlidingExpiration()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var post1 = await _context.Set<Post>()\n                .Where(x => x.Id > 0)\n                .OrderBy(x => x.Id)\n                .Cacheable(new EFCachePolicy(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(5)), debugInfo)\n                .FirstOrDefaultAsync();\n            return Json(new { post1.Title, debugInfo });\n        }\n\n        /// <summary>\n        /// Get https://localhost:5001/home/WithAbsoluteExpiration\n        /// </summary>\n        public async Task<IActionResult> WithAbsoluteExpiration()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var post1 = await _context.Set<Post>()\n                .Where(x => x.Id > 0)\n                .OrderBy(x => x.Id)\n                .Cacheable(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(5), debugInfo)\n                .FirstOrDefaultAsync();\n            return Json(new { post1.Title, debugInfo });\n        }\n\n        public async Task<IActionResult> AsyncTest()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var post1 = await _context.Posts\n                .Where(x => x.Id > 0)\n                .Cacheable(debugInfo)\n                .FirstOrDefaultAsync();\n            return Json(new { post1.Title, debugInfo });\n        }\n\n        public async Task<IActionResult> CountTest()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var count = await _context.Posts\n                .Where(x => x.Id > 0)\n                .Cacheable(debugInfo)\n                .CountAsync();\n            return Json(new { count, debugInfo });\n        }\n\n        public async Task<IActionResult> CountWithParamsTest()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var count = await _context.Posts\n                .Cacheable(debugInfo)\n                .CountAsync(x => x.Id > 0);\n            return Json(new { count, debugInfo });\n        }\n\n        public async Task<IActionResult> CollectionsTest()\n        {\n            var collection1 = new[] { 1, 2, 3 };\n            var debugInfo1 = new EFCacheDebugInfo();\n            var post1 = await _context.Posts\n                .Where(x => collection1.Contains(x.Id))\n                .Cacheable(debugInfo1)\n                .FirstOrDefaultAsync();\n\n            var collection2 = new[] { 1, 2, 3, 4 };\n            var debugInfo2 = new EFCacheDebugInfo();\n            var post2 = await _context.Posts\n                .Where(x => collection2.Contains(x.Id))\n                .Cacheable(debugInfo2)\n                .FirstOrDefaultAsync();\n            return Json(new { post1.Title, post2.Id, debugInfo1, debugInfo2 });\n        }\n\n        /// <summary>\n        /// Get https://localhost:5001/home/StringEqualsTest\n        /// </summary>\n        public async Task<IActionResult> StringEqualsTest()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var rnd = new Random();\n            var value = rnd.Next(1, 1000000).ToString();\n            var post1 = await _context.Posts\n                .Where(x => x.Title.Equals(value))\n                .Cacheable(debugInfo)\n                .FirstOrDefaultAsync();\n            return Json(new { post1?.Title, debugInfo });\n        }\n\n        /// <summary>\n        /// Get https://localhost:5001/home/Issue36\n        /// </summary>\n        public IActionResult Issue36()\n        {\n            User user1;\n            const string user1Name = \"User1\";\n            if (!_context.Users.Any(user => user.Name == user1Name))\n            {\n                user1 = new User { Name = user1Name };\n                user1 = _context.Users.Add(user1).Entity;\n            }\n            else\n            {\n                user1 = _context.Users.First(user => user.Name == user1Name);\n            }\n\n            var product = new Product\n            {\n                ProductName = \"P981122\",\n                IsActive = true,\n                Notes = \"Notes ...\",\n                ProductNumber = \"098112\",\n                User = user1\n            };\n\n            product = _context.Products.Add(product).Entity;\n            _context.SaveChanges();\n\n            // 1st query, reading from db\n            var debugInfo1 = new EFCacheDebugInfo();\n            var firstQueryResult = _context.Products\n                .Cacheable(debugInfo1)\n                .FirstOrDefault(p => p.ProductId == product.ProductId);\n\n            var debugInfoWithWhere1 = new EFCacheDebugInfo();\n            var firstQueryWithWhereClauseResult = _context.Products.Where(p => p.ProductId == product.ProductId)\n                .Cacheable(debugInfoWithWhere1)\n                .FirstOrDefault();\n\n            // Delete it from db, invalidates the cache on SaveChanges\n            _context.Products.Remove(product);\n            _context.SaveChanges();\n\n            // same query, reading from 2nd level cache? No.\n            var debugInfo2 = new EFCacheDebugInfo();\n            var secondQueryResult = _context.Products\n                .Cacheable(debugInfo2)\n                .FirstOrDefault(p => p.ProductId == product.ProductId);\n\n            // same query, reading from 2nd level cache? No.\n            var debugInfo3 = new EFCacheDebugInfo();\n            var thirdQueryResult = _context.Products.Where(p => p.ProductId == product.ProductId)\n                .Cacheable(debugInfo3)\n                .FirstOrDefault();\n\n            // retrieving it directly from database\n            var p98 = _context.Products.FirstOrDefault(p => p.ProductId == product.ProductId);\n\n            return Json(new\n            {\n                firstQueryResult,\n                isFirstQueryCached = debugInfo1,\n\n                firstQueryWithWhereClauseResult,\n                isFirstQueryWithWhereClauseCached = debugInfoWithWhere1,\n\n                secondQueryResult,\n                isSecondQueryCached = debugInfo2,\n\n                thirdQueryResult,\n                isThirdQueryCached = debugInfo3,\n\n                directlyFromDatabase = p98\n            });\n        }\n\n        public IActionResult DynamicGetWithCacheableAtFirst()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var users = _context.Users.DynamicGetWithCacheableAtFirst(debugInfo, x => x.Id > 0, x => x.Posts);\n            return Json(new { users, debugInfo });\n        }\n\n        public IActionResult DynamicGetWithCacheableAtEnd()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var users = _context.Users.DynamicGetWithCacheableAtEnd(debugInfo, x => x.Id > 0, x => x.Posts);\n            return Json(new { users, debugInfo }); // https://github.com/aspnet/EntityFrameworkCore/issues/12098\n        }\n\n        public async Task<IActionResult> FirstOrDefaultInline()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var post1 = await _context.Set<Post>()\n                .Where(x => x.Id == 1)\n                .Cacheable(debugInfo)\n                .FirstOrDefaultAsync();\n            return Json(new { post1.Title, debugInfo });\n        }\n\n        public async Task<IActionResult> FirstOrDefaultInlineAtTheEnd()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var post1 = await _context.Set<Post>()\n                .Cacheable(debugInfo)\n                .FirstOrDefaultAsync(x => x.Id == 1);\n            return Json(new { post1.Title, debugInfo });\n        }\n\n        public async Task<IActionResult> FirstOrDefaultWithParam()\n        {\n            var param1 = 1;\n            var debugInfo = new EFCacheDebugInfo();\n            var post1 = await _context.Set<Post>()\n                .Where(x => x.Id == param1)\n                .Cacheable(debugInfo)\n                .FirstOrDefaultAsync();\n            return Json(new { post1.Title, debugInfo });\n        }\n\n        public async Task<IActionResult> FirstOrDefaultAtTheEndWithParam()\n        {\n            var param1 = 1;\n            var debugInfo = new EFCacheDebugInfo();\n            var post1 = await _context.Set<Post>()\n                .Cacheable(debugInfo)\n                .FirstOrDefaultAsync(x => x.Id == param1);\n            return Json(new { post1.Title, debugInfo });\n        }\n\n        // https://localhost:5001/home/FirstOrDefaultWithParams\n        public async Task<IActionResult> FirstOrDefaultWithParams()\n        {\n            var param1 = 1;\n            var debugInfo1 = new EFCacheDebugInfo();\n            var param2 = param1;\n            var post1 = await _context.Set<Post>()\n                .Where(x => x.Id == param2)\n                .Cacheable(debugInfo1)\n                .FirstOrDefaultAsync();\n\n            param1 = 2;\n            var debugInfo2 = new EFCacheDebugInfo();\n            var post2 = await _context.Set<Post>()\n                .Where(x => x.Id == param1)\n                .Cacheable(debugInfo2)\n                .FirstOrDefaultAsync();\n\n            return Json(new { post1Title = post1.Title, debugInfo1, post2Title = post2.Title, debugInfo2 });\n        }\n\n        // https://github.com/VahidN/EFSecondLevelCache.Core/issues/53\n        // https://localhost:5001/home/FirstOrDefaultWithParams2\n        public async Task<IActionResult> FirstOrDefaultWithParams2()\n        {\n            var param1 = 1;\n            var debugInfo1 = new EFCacheDebugInfo();\n            var post1 = await _context.GetFirstOrDefaultAsync<Post, SampleContext>(debugInfo1, x => x.Id == param1);\n\n            param1 = 2;\n            var debugInfo2 = new EFCacheDebugInfo();\n            var post2 = await _context.GetFirstOrDefaultAsync<Post, SampleContext>(debugInfo2, x => x.Id == param1);\n\n            return Json(new { post1Title = post1.Title, debugInfo1, post2Title = post2.Title, debugInfo2 });\n        }\n\n        public async Task<IActionResult> TestEnumsWithParams()\n        {\n            var param1 = UserStatus.Active;\n            var debugInfo = new EFCacheDebugInfo();\n            var user1 = await _context.Set<User>()\n                .Cacheable(debugInfo)\n                .FirstOrDefaultAsync(x => x.UserStatus == param1);\n            return Json(new { user1, debugInfo });\n        }\n\n        public async Task<IActionResult> TestInlineEnums()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var user1 = await _context.Set<User>()\n                .Cacheable(debugInfo)\n                .FirstOrDefaultAsync(x => x.UserStatus == UserStatus.Active);\n            return Json(new { user1, debugInfo });\n        }\n\n        public async Task<IActionResult> TestEFCachedDbSet()\n        {\n            var users1 = await _context.CachedPosts.OrderByDescending(x => x.Id).ToListAsync();\n\n            var debugInfo2 = new EFCacheDebugInfo();\n            var users2 = await _context.Set<Post>().Cacheable(debugInfo2).OrderByDescending(x => x.Id).ToListAsync();\n\n            var debugInfo3 = new EFCacheDebugInfo();\n            var users3 = await _context.Set<Post>().OrderByDescending(x => x.Id).Cacheable(debugInfo3).ToListAsync();\n            return Json(new { users1, debugInfo2, debugInfo3 });\n        }\n\n        // https://github.com/VahidN/EFSecondLevelCache.Core/issues/65\n        // https://localhost:5001/home/TestIncludes\n        public async Task<IActionResult> TestIncludes()\n        {\n            var debugInfo1 = new EFCacheDebugInfo();\n            var firstProductIncludeTags1 = await _context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                                             .Cacheable(debugInfo1)\n                                             .FirstOrDefaultAsync();\n\n            var debugInfo2 = new EFCacheDebugInfo();\n            var firstProductIncludeTags2 = await _context.Products\n                                                         .Cacheable(debugInfo2)\n                                                         .Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                                                         .FirstOrDefaultAsync();\n\n            return Json(new { productName1 = firstProductIncludeTags1.ProductName, productName2 = debugInfo1, firstProductIncludeTags2.ProductName, debugInfo2 });\n        }\n\n        public async Task<IActionResult> TestFind()\n        {\n            var debugInfo = new EFCacheDebugInfo();\n            var product1 = await _context.Products\n                .Cacheable(debugInfo)\n                .FindAsync(1);\n\n            return Json(new { product1.ProductName, debugInfo });\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/DataLayer/Entities/Post.cs",
    "content": "﻿namespace EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities\n{\n    public class Post\n    {\n        public int Id { get; set; }\n        public string Title { get; set; }\n\n        public virtual User User { get; set; }\n        public int UserId { get; set; }\n    }\n\n    public class Page : Post\n    {\n    }\n}\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/DataLayer/Entities/Product.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities\n{\n    public class Product\n    {\n        public Product()\n        {\n            TagProducts = new HashSet<TagProduct>();\n        }\n\n        public int ProductId { get; set; }\n        public string ProductNumber { get; set; }\n        public string ProductName { get; set; }\n        public string Notes { get; set; }\n        public bool IsActive { get; set; }\n\n        public virtual ICollection<TagProduct> TagProducts { get; set; }\n\n        public virtual User User { get; set; }\n        public int UserId { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/DataLayer/Entities/Tag.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities\n{\n    public class Tag\n    {\n        public Tag()\n        {\n            TagProducts = new HashSet<TagProduct>();\n        }\n\n        public int Id { get; set; }\n        public string Name { get; set; }\n\n        public virtual ICollection<TagProduct> TagProducts { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/DataLayer/Entities/TagProduct.cs",
    "content": "﻿namespace EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities\n{\n    public class TagProduct\n    {\n        public int TagId { get; set; }\n        public int ProductProductId { get; set; }\n\n        public virtual Product Product { get; set; }\n        public virtual Tag Tag { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/DataLayer/Entities/User.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities\n{\n    public class User\n    {\n        public User()\n        {\n            Posts = new HashSet<Post>();\n            Products = new HashSet<Product>();\n        }\n\n        public int Id { get; set; }\n        public string Name { get; set; }\n        public UserStatus UserStatus { get; set; }\n\n        public virtual ICollection<Post> Posts { get; set; }\n        public virtual ICollection<Product> Products { get; set; }\n    }\n\n    public enum UserStatus\n    {\n        Active,\n        Disabled\n    }\n}\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/DataLayer/SampleContext.cs",
    "content": "﻿using System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\n\nnamespace EFSecondLevelCache.Core.AspNetCoreSample.DataLayer\n{\n    public class SampleContext : DbContext\n    {\n        public virtual DbSet<Post> Posts { get; set; }\n        public virtual DbSet<Product> Products { get; set; }\n        public virtual DbSet<TagProduct> TagProducts { get; set; }\n        public virtual DbSet<Tag> Tags { get; set; }\n        public virtual DbSet<User> Users { get; set; }\n\n        public EFCachedDbSet<Post> CachedPosts => this.Set<Post>().Cacheable();\n\n        public SampleContext(DbContextOptions<SampleContext> options) : base(options)\n        { }\n\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            modelBuilder.Entity<Post>(entity =>\n            {\n                entity.HasIndex(e => e.UserId);\n\n                entity.HasOne(d => d.User)\n                    .WithMany(p => p.Posts)\n                    .HasForeignKey(d => d.UserId);\n\n                entity.HasDiscriminator<string>(\"post_type\")\n                      .HasValue<Post>(\"post_base\")\n                      .HasValue<Page>(\"post_page\");\n            });\n\n            modelBuilder.Entity<Product>(entity =>\n            {\n                entity.HasKey(e => e.ProductId);\n\n                entity.HasIndex(e => e.ProductName)\n                    .IsUnique();\n\n                entity.HasIndex(e => e.UserId);\n\n                entity.Property(e => e.ProductName)\n                    .IsRequired()\n                    .HasMaxLength(50);\n\n                entity.Property(e => e.ProductNumber)\n                    .IsRequired()\n                    .HasMaxLength(30);\n\n                entity.HasOne(d => d.User)\n                    .WithMany(p => p.Products)\n                    .HasForeignKey(d => d.UserId);\n            });\n\n            modelBuilder.Entity<TagProduct>(entity =>\n            {\n                entity.HasKey(e => new { e.TagId, e.ProductProductId });\n\n                entity.HasIndex(e => e.ProductProductId);\n\n                entity.HasIndex(e => e.TagId);\n\n                entity.Property(e => e.TagId);\n\n                entity.Property(e => e.ProductProductId);\n\n                entity.HasOne(d => d.Product)\n                    .WithMany(p => p.TagProducts)\n                    .HasForeignKey(d => d.ProductProductId);\n\n                entity.HasOne(d => d.Tag)\n                    .WithMany(p => p.TagProducts)\n                    .HasForeignKey(d => d.TagId);\n            });\n\n            modelBuilder.Entity<User>(entity =>\n            {\n                entity.Property(e => e.Name).IsRequired();\n            });\n        }\n\n        public override int SaveChanges()\n        {\n            var changedEntityNames = this.GetChangedEntityNames();\n\n            this.ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again.\n            var result = base.SaveChanges();\n            this.ChangeTracker.AutoDetectChangesEnabled = true;\n\n            this.GetService<IEFCacheServiceProvider>().InvalidateCacheDependencies(changedEntityNames);\n\n            return result;\n        }\n\n        public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())\n        {\n            var changedEntityNames = this.GetChangedEntityNames();\n\n            this.ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again.\n            var result = base.SaveChangesAsync(cancellationToken);\n            this.ChangeTracker.AutoDetectChangesEnabled = true;\n\n            this.GetService<IEFCacheServiceProvider>().InvalidateCacheDependencies(changedEntityNames);\n\n            return result;\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/DataLayer/Utils/ApplicationDbContextSeedData.cs",
    "content": "﻿using System.Linq;\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Utils\n{\n    public static class ApplicationDbContextSeedData\n    {\n        public static void SeedData(this IServiceScopeFactory scopeFactory)\n        {\n            using (var serviceScope = scopeFactory.CreateScope())\n            {\n                var context = serviceScope.ServiceProvider.GetService<SampleContext>();\n                User user1;\n\n                const string user1Name = \"User1\";\n                if (!context.Users.Any(user => user.Name == user1Name))\n                {\n                    user1 = new User { Name = user1Name };\n                    user1 = context.Users.Add(user1).Entity;\n                }\n                else\n                {\n                    user1 = context.Users.First(user => user.Name == user1Name);\n                }\n\n                const string product4Name = \"Product4\";\n                if (!context.Products.Any(product => product.ProductName == product4Name))\n                {\n                    var product4 = new Product\n                    {\n                        ProductName = product4Name,\n                        IsActive = false,\n                        Notes = \"Notes ...\",\n                        ProductNumber = \"004\",\n                        User = user1\n                    };\n                    product4 = context.Products.Add(product4).Entity;\n\n                    var tag4 = new Tag\n                    {\n                        Name = \"Tag4\"\n                    };\n                    context.Tags.Add(tag4);\n\n                    var productTag = new TagProduct { Tag = tag4, Product = product4 };\n                    context.TagProducts.Add(productTag);\n                }\n\n                const string product1Name = \"Product1\";\n                if (!context.Products.Any(product => product.ProductName == product1Name))\n                {\n                    var product1 = new Product\n                    {\n                        ProductName = product1Name,\n                        IsActive = true,\n                        Notes = \"Notes ...\",\n                        ProductNumber = \"001\",\n                        User = user1\n                    };\n                    product1 = context.Products.Add(product1).Entity;\n\n                    var tag1 = new Tag\n                    {\n                        Name = \"Tag1\"\n                    };\n                    context.Tags.Add(tag1);\n\n                    var productTag = new TagProduct { Tag = tag1, Product = product1 };\n                    context.TagProducts.Add(productTag);\n                }\n\n\n                const string product2Name = \"Product2\";\n                if (!context.Products.Any(product => product.ProductName == product2Name))\n                {\n                    var product2 = new Product\n                    {\n                        ProductName = product2Name,\n                        IsActive = true,\n                        Notes = \"Notes ...\",\n                        ProductNumber = \"002\",\n                        User = user1\n                    };\n                    product2 = context.Products.Add(product2).Entity;\n\n                    var tag2 = new Tag\n                    {\n                        Name = \"Tag2\"\n                    };\n                    context.Tags.Add(tag2);\n\n                    var productTag = new TagProduct { Tag = tag2, Product = product2 };\n                    context.TagProducts.Add(productTag);\n                }\n\n                const string product3Name = \"Product3\";\n                if (!context.Products.Any(product => product.ProductName == product3Name))\n                {\n                    var product3 = new Product\n                    {\n                        ProductName = product3Name,\n                        IsActive = true,\n                        Notes = \"Notes ...\",\n                        ProductNumber = \"003\",\n                        User = user1\n                    };\n                    product3 = context.Products.Add(product3).Entity;\n\n                    var tag3 = new Tag\n                    {\n                        Name = \"Tag3\"\n                    };\n                    context.Tags.Add(tag3);\n\n                    var productTag = new TagProduct { Tag = tag3, Product = product3 };\n                    context.TagProducts.Add(productTag);\n                }\n\n                const string post1Title = \"Post1\";\n                if (!context.Posts.Any(post => post.Title == post1Title))\n                {\n                    var page1 = new Page\n                    {\n                        Title = post1Title,\n                        User = user1\n                    };\n                    context.Posts.Add(page1);\n                }\n\n                const string post2Title = \"Post2\";\n                if (!context.Posts.Any(post => post.Title == post2Title))\n                {\n                    var page2 = new Page\n                    {\n                        Title = post2Title,\n                        User = user1\n                    };\n                    context.Posts.Add(page2);\n                }\n\n                context.SaveChanges();\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/DataLayer/Utils/DBInitialization.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Utils\n{\n    public static class DbInitialization\n    {\n        public static void Initialize(this IServiceScopeFactory scopeFactory)\n        {\n            using (var serviceScope = scopeFactory.CreateScope())\n            {\n                var context = serviceScope.ServiceProvider.GetService<SampleContext>();\n                // Applies any pending migrations for the context to the database.\n                // Will create the database if it does not already exist.\n                context.Database.Migrate();\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/EFSecondLevelCache.Core.AspNetCoreSample.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <PreserveCompilationContext>true</PreserveCompilationContext>\n    <AssemblyName>EFSecondLevelCache.Core.AspNetCoreSample</AssemblyName>\n    <OutputType>Exe</OutputType>\n    <PackageId>EFSecondLevelCache.Core.AspNetCoreSample</PackageId>\n  </PropertyGroup>\n  <ItemGroup>\n    <None Update=\"wwwroot\\**\\*\">\n      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n    </None>\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\..\\EFSecondLevelCache.Core\\EFSecondLevelCache.Core.csproj\" />\n  </ItemGroup>\n  <ItemGroup>\n    <PackageReference Include=\"CacheManager.Core\" Version=\"1.2.0\" />\n    <PackageReference Include=\"CacheManager.Microsoft.Extensions.Caching.Memory\" Version=\"1.2.0\" />\n    <PackageReference Include=\"CacheManager.Serialization.Json\" Version=\"1.2.0\" />\n    <PackageReference Include=\"CacheManager.StackExchange.Redis\" Version=\"1.2.0\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Design\" Version=\"3.1.0\">\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n      <PrivateAssets>all</PrivateAssets>\n    </PackageReference>\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.InMemory\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Ben.BlockingDetector\" Version=\"0.0.3\" />\n    <PackageReference Include=\"AutoMapper.Extensions.Microsoft.DependencyInjection\" Version=\"7.0.0\" />\n  </ItemGroup>\n  <PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n    <PlatformTarget>anycpu</PlatformTarget>\n  </PropertyGroup>\n</Project>"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/Migrations/20191022095356_V2019_10_22_1323.Designer.cs",
    "content": "﻿// <auto-generated />\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\n\nnamespace EFSecondLevelCache.Core.AspNetCoreSample.Migrations\n{\n    [DbContext(typeof(SampleContext))]\n    [Migration(\"20191022095356_V2019_10_22_1323\")]\n    partial class V2019_10_22_1323\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Post\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"Title\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<int>(\"UserId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<string>(\"post_type\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"Posts\");\n\n                    b.HasDiscriminator<string>(\"post_type\").HasValue(\"post_base\");\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Product\", b =>\n                {\n                    b.Property<int>(\"ProductId\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<bool>(\"IsActive\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"Notes\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ProductName\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(50)\")\n                        .HasMaxLength(50);\n\n                    b.Property<string>(\"ProductNumber\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(30)\")\n                        .HasMaxLength(30);\n\n                    b.Property<int>(\"UserId\")\n                        .HasColumnType(\"int\");\n\n                    b.HasKey(\"ProductId\");\n\n                    b.HasIndex(\"ProductName\")\n                        .IsUnique();\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"Products\");\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Tag\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Tags\");\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.TagProduct\", b =>\n                {\n                    b.Property<int>(\"TagId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<int>(\"ProductProductId\")\n                        .HasColumnType(\"int\");\n\n                    b.HasKey(\"TagId\", \"ProductProductId\");\n\n                    b.HasIndex(\"ProductProductId\");\n\n                    b.HasIndex(\"TagId\");\n\n                    b.ToTable(\"TagProducts\");\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.User\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"Name\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<int>(\"UserStatus\")\n                        .HasColumnType(\"int\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Users\");\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Page\", b =>\n                {\n                    b.HasBaseType(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Post\");\n\n                    b.HasDiscriminator().HasValue(\"post_page\");\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Post\", b =>\n                {\n                    b.HasOne(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.User\", \"User\")\n                        .WithMany(\"Posts\")\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Product\", b =>\n                {\n                    b.HasOne(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.User\", \"User\")\n                        .WithMany(\"Products\")\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.TagProduct\", b =>\n                {\n                    b.HasOne(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Product\", \"Product\")\n                        .WithMany(\"TagProducts\")\n                        .HasForeignKey(\"ProductProductId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Tag\", \"Tag\")\n                        .WithMany(\"TagProducts\")\n                        .HasForeignKey(\"TagId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/Migrations/20191022095356_V2019_10_22_1323.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Migrations;\n\nnamespace EFSecondLevelCache.Core.AspNetCoreSample.Migrations\n{\n    public partial class V2019_10_22_1323 : Migration\n    {\n        protected override void Up(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.CreateTable(\n                name: \"Tags\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Name = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Tags\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"Users\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Name = table.Column<string>(nullable: false),\n                    UserStatus = table.Column<int>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Users\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"Posts\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Title = table.Column<string>(nullable: true),\n                    UserId = table.Column<int>(nullable: false),\n                    post_type = table.Column<string>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Posts\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_Posts_Users_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"Users\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"Products\",\n                columns: table => new\n                {\n                    ProductId = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    ProductNumber = table.Column<string>(maxLength: 30, nullable: false),\n                    ProductName = table.Column<string>(maxLength: 50, nullable: false),\n                    Notes = table.Column<string>(nullable: true),\n                    IsActive = table.Column<bool>(nullable: false),\n                    UserId = table.Column<int>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Products\", x => x.ProductId);\n                    table.ForeignKey(\n                        name: \"FK_Products_Users_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"Users\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"TagProducts\",\n                columns: table => new\n                {\n                    TagId = table.Column<int>(nullable: false),\n                    ProductProductId = table.Column<int>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_TagProducts\", x => new { x.TagId, x.ProductProductId });\n                    table.ForeignKey(\n                        name: \"FK_TagProducts_Products_ProductProductId\",\n                        column: x => x.ProductProductId,\n                        principalTable: \"Products\",\n                        principalColumn: \"ProductId\",\n                        onDelete: ReferentialAction.Cascade);\n                    table.ForeignKey(\n                        name: \"FK_TagProducts_Tags_TagId\",\n                        column: x => x.TagId,\n                        principalTable: \"Tags\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_Posts_UserId\",\n                table: \"Posts\",\n                column: \"UserId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_Products_ProductName\",\n                table: \"Products\",\n                column: \"ProductName\",\n                unique: true);\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_Products_UserId\",\n                table: \"Products\",\n                column: \"UserId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_TagProducts_ProductProductId\",\n                table: \"TagProducts\",\n                column: \"ProductProductId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_TagProducts_TagId\",\n                table: \"TagProducts\",\n                column: \"TagId\");\n        }\n\n        protected override void Down(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.DropTable(\n                name: \"Posts\");\n\n            migrationBuilder.DropTable(\n                name: \"TagProducts\");\n\n            migrationBuilder.DropTable(\n                name: \"Products\");\n\n            migrationBuilder.DropTable(\n                name: \"Tags\");\n\n            migrationBuilder.DropTable(\n                name: \"Users\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/Migrations/SampleContextModelSnapshot.cs",
    "content": "﻿// <auto-generated />\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\n\nnamespace EFSecondLevelCache.Core.AspNetCoreSample.Migrations\n{\n    [DbContext(typeof(SampleContext))]\n    partial class SampleContextModelSnapshot : ModelSnapshot\n    {\n        protected override void BuildModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Post\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"Title\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<int>(\"UserId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<string>(\"post_type\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"Posts\");\n\n                    b.HasDiscriminator<string>(\"post_type\").HasValue(\"post_base\");\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Product\", b =>\n                {\n                    b.Property<int>(\"ProductId\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<bool>(\"IsActive\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"Notes\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ProductName\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(50)\")\n                        .HasMaxLength(50);\n\n                    b.Property<string>(\"ProductNumber\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(30)\")\n                        .HasMaxLength(30);\n\n                    b.Property<int>(\"UserId\")\n                        .HasColumnType(\"int\");\n\n                    b.HasKey(\"ProductId\");\n\n                    b.HasIndex(\"ProductName\")\n                        .IsUnique();\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"Products\");\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Tag\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Tags\");\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.TagProduct\", b =>\n                {\n                    b.Property<int>(\"TagId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<int>(\"ProductProductId\")\n                        .HasColumnType(\"int\");\n\n                    b.HasKey(\"TagId\", \"ProductProductId\");\n\n                    b.HasIndex(\"ProductProductId\");\n\n                    b.HasIndex(\"TagId\");\n\n                    b.ToTable(\"TagProducts\");\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.User\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"Name\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<int>(\"UserStatus\")\n                        .HasColumnType(\"int\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Users\");\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Page\", b =>\n                {\n                    b.HasBaseType(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Post\");\n\n                    b.HasDiscriminator().HasValue(\"post_page\");\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Post\", b =>\n                {\n                    b.HasOne(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.User\", \"User\")\n                        .WithMany(\"Posts\")\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Product\", b =>\n                {\n                    b.HasOne(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.User\", \"User\")\n                        .WithMany(\"Products\")\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.TagProduct\", b =>\n                {\n                    b.HasOne(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Product\", \"Product\")\n                        .WithMany(\"TagProducts\")\n                        .HasForeignKey(\"ProductProductId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Tag\", \"Tag\")\n                        .WithMany(\"TagProducts\")\n                        .HasForeignKey(\"TagId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/Models/PostDto.cs",
    "content": "namespace EFSecondLevelCache.Core.AspNetCoreSample.Models\n{\n    public class PostDto\n    {\n        public string Title { get; set; }\n        public string Author { get; set; }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/Others/TestUtils.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Query;\n\nnamespace EFSecondLevelCache.Core.AspNetCoreSample.Others\n{\n    public static class TestUtils\n    {\n        public static IEnumerable<T> DynamicGetWithCacheableAtFirst<T>(\n            this IQueryable<T> query,\n            EFCacheDebugInfo debugInfo,\n            Expression<Func<T, bool>> filter = null,\n            params Expression<Func<T, object>>[] include) where T : class\n        {\n            query = query.Cacheable(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(5), debugInfo);\n\n            if (filter != null)\n                query = query.Where(filter);\n\n            if (include != null)\n            {\n                foreach (var includeProperty in include.ToList())\n                    query = query.Include(includeProperty);\n            }\n            return query.ToList();\n        }\n\n        public static IEnumerable<T> DynamicGetWithCacheableAtEnd<T>(\n            this IQueryable<T> query,\n            EFCacheDebugInfo debugInfo,\n            Expression<Func<T, bool>> filter = null,\n            params Expression<Func<T, object>>[] include) where T : class\n        {\n            if (filter != null)\n                query = query.Where(filter);\n\n            if (include != null)\n            {\n                foreach (var includeProperty in include.ToList())\n                    query = query.Include(includeProperty);\n            }\n            query = query.Cacheable(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(5), debugInfo);\n            return query.ToList();\n        }\n\n        public static async Task<TEntity> GetFirstOrDefaultAsync<TEntity, TDbContext>(\n               this TDbContext dbContext,\n               EFCacheDebugInfo eFCacheDebugInfo,\n               Expression<Func<TEntity, bool>> predicate = null,\n               Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,\n               Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,\n               bool disableTracking = true,\n               CancellationToken cancellationToken = default(CancellationToken))\n            where TEntity : class\n            where TDbContext : DbContext\n        {\n            IQueryable<TEntity> query = dbContext.Set<TEntity>();\n            if (disableTracking)\n            {\n                query = query.AsNoTracking();\n            }\n\n            if (include != null)\n            {\n                query = include(query);\n            }\n\n            if (predicate != null)\n            {\n                query = query.Where(predicate);\n            }\n\n            if (orderBy != null)\n            {\n                return await orderBy(query)\n                        .Cacheable(eFCacheDebugInfo)\n                        .FirstOrDefaultAsync(cancellationToken);\n            }\n            else\n            {\n                return await query\n                        .Cacheable(eFCacheDebugInfo)\n                        .FirstOrDefaultAsync(cancellationToken);\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/Profiles/PostProfile.cs",
    "content": "using AutoMapper;\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities;\nusing EFSecondLevelCache.Core.AspNetCoreSample.Models;\n\nnamespace EFSecondLevelCache.Core.AspNetCoreSample.Profiles\n{\n    public class PostProfile : Profile\n    {\n        public PostProfile()\n        {\n            CreateMap<Post, PostDto>()\n                .ForMember(dest => dest.Author, opt => opt.MapFrom(src => $\"{src.User.Name}\"))\n                .ForMember(dest => dest.Title, opt => opt.MapFrom(src => $\"{src.Title}\"));\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/Program.cs",
    "content": "﻿using Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Hosting;\n\nnamespace EFSecondLevelCache.Core.AspNetCoreSample\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateHostBuilder(args).Build().Run();\n        }\n\n        public static IHostBuilder CreateHostBuilder(string[] args) =>\n            Host.CreateDefaultBuilder(args)\n                .ConfigureWebHostDefaults(webBuilder =>\n                {\n                    webBuilder.UseStartup<Startup>();\n                });\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/Properties/launchSettings.json",
    "content": "{\n  \"iisSettings\": {\n    \"windowsAuthentication\": false, \n    \"anonymousAuthentication\": true, \n    \"iisExpress\": {\n      \"applicationUrl\": \"http://localhost:19452\",\n      \"sslPort\": 44364\n    }\n  },\n  \"profiles\": {\n    \"IIS Express\": {\n      \"commandName\": \"IISExpress\",\n      \"launchBrowser\": true,\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      }\n    },\n    \"MVCFinal3\": {\n      \"commandName\": \"Project\",\n      \"launchBrowser\": true,\n      \"applicationUrl\": \"https://localhost:5001;http://localhost:5000\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/Startup.cs",
    "content": "﻿using System;\nusing AutoMapper;\nusing CacheManager.Core;\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer;\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Utils;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Newtonsoft.Json;\nusing EFSecondLevelCache.Core.AspNetCoreSample.Profiles;\nusing System.Reflection;\nusing Microsoft.Extensions.Hosting;\n\nnamespace EFSecondLevelCache.Core.AspNetCoreSample\n{\n    public class Startup\n    {\n        private readonly string _contentRootPath;\n\n        public Startup(IConfiguration configuration, IWebHostEnvironment env)\n        {\n            _contentRootPath = env.ContentRootPath;\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        public void Configure(\n            IApplicationBuilder app,\n            IWebHostEnvironment env,\n            IServiceScopeFactory scopeFactory)\n        {\n            //app.UseBlockingDetection();\n\n            scopeFactory.Initialize();\n            scopeFactory.SeedData();\n\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles();\n\n            app.UseRouting();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller=Home}/{action=Index}/{id?}\");\n            });\n        }\n\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddEFSecondLevelCache();\n            // addInMemoryCacheServiceProvider(services);\n            addRedisCacheServiceProvider(services);\n\n            services.AddDbContext<SampleContext>(optionsBuilder =>\n            {\n                var useInMemoryDatabase = Configuration[\"UseInMemoryDatabase\"].Equals(\"true\", StringComparison.OrdinalIgnoreCase);\n                if (useInMemoryDatabase)\n                {\n                    optionsBuilder.UseInMemoryDatabase(\"TestDb\");\n                }\n                else\n                {\n                    var connectionString = Configuration[\"ConnectionStrings:ApplicationDbContextConnection\"];\n                    if (connectionString.Contains(\"%CONTENTROOTPATH%\"))\n                    {\n                        connectionString = connectionString.Replace(\"%CONTENTROOTPATH%\", _contentRootPath);\n                    }\n                    optionsBuilder.UseSqlServer(\n                        connectionString\n                        , serverDbContextOptionsBuilder =>\n                        {\n                            var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;\n                            serverDbContextOptionsBuilder.CommandTimeout(minutes);\n                        });\n                    optionsBuilder.EnableSensitiveDataLogging();\n                    optionsBuilder.ConfigureWarnings(w =>\n                    {\n                    });\n                }\n            });\n\n            services.AddAutoMapper(typeof(PostProfile).GetTypeInfo().Assembly);\n            services.AddControllersWithViews();\n        }\n\n        private static void addInMemoryCacheServiceProvider(IServiceCollection services)\n        {\n            var jss = new JsonSerializerSettings\n            {\n                NullValueHandling = NullValueHandling.Ignore,\n                ReferenceLoopHandling = ReferenceLoopHandling.Ignore\n            };\n\n            services.AddSingleton(typeof(ICacheManagerConfiguration),\n                new CacheManager.Core.ConfigurationBuilder()\n                    .WithJsonSerializer(serializationSettings: jss, deserializationSettings: jss)\n                    .WithMicrosoftMemoryCacheHandle(instanceName: \"MemoryCache1\")\n                    .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))\n                    .DisablePerformanceCounters()\n                    .DisableStatistics()\n                    .Build());\n            services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));\n        }\n\n        private static void addRedisCacheServiceProvider(IServiceCollection services)\n        {\n            var jss = new JsonSerializerSettings\n            {\n                NullValueHandling = NullValueHandling.Ignore,\n                ReferenceLoopHandling = ReferenceLoopHandling.Ignore\n            };\n\n            const string redisConfigurationKey = \"redis\";\n            services.AddSingleton(typeof(ICacheManagerConfiguration),\n                new CacheManager.Core.ConfigurationBuilder()\n                    .WithJsonSerializer(serializationSettings: jss, deserializationSettings: jss)\n                    .WithUpdateMode(CacheUpdateMode.Up)\n                    .WithRedisConfiguration(redisConfigurationKey, config =>\n                    {\n                        config.WithAllowAdmin()\n                            .WithDatabase(0)\n                            .WithEndpoint(\"localhost\", 6379)\n                            // Enables keyspace notifications to react on eviction/expiration of items.\n                            // Make sure that all servers are configured correctly and 'notify-keyspace-events' is at least set to 'Exe', otherwise CacheManager will not retrieve any events.\n                            // See https://redis.io/topics/notifications#configuration for configuration details.\n                            .EnableKeyspaceEvents();\n                    })\n                    .WithMaxRetries(100)\n                    .WithRetryTimeout(50)\n                    .WithRedisCacheHandle(redisConfigurationKey)\n                    .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))\n                    .Build());\n            services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/_0-restore.bat",
    "content": "rmdir /S /Q bin\nrmdir /S /Q obj\ndotnet restore\npause"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/_1-dotnet_run.bat",
    "content": "dotnet watch run"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/_update_db.bat",
    "content": "For /f \"tokens=2-4 delims=/ \" %%a in ('date /t') do (set mydate=%%c_%%a_%%b)\nFor /f \"tokens=1-2 delims=/:\" %%a in (\"%TIME: =0%\") do (set mytime=%%a%%b)\necho %mydate%_%mytime%\n\ndotnet ef --configuration Release migrations add V%mydate%_%mytime%\ndotnet ef --configuration Release database update\npause"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/appsettings.json",
    "content": "﻿{\n    \"Logging\": {\n        \"IncludeScopes\": false,\n        \"LogLevel\": {\n            \"Default\": \"Debug\",\n            \"System\": \"Information\",\n            \"Microsoft\": \"Information\"\n        }\n    },\n    \"ConnectionStrings\": {\n        \"ApplicationDbContextConnection\": \"Server=(localdb)\\\\mssqllocaldb;Initial Catalog=EFSecondLevelCacheCore2019;AttachDBFilename=%CONTENTROOTPATH%\\\\App_Data\\\\EFSecondLevelCacheCore2019.mdf;Trusted_Connection=True;\"\n    },\n    \"UseInMemoryDatabase\": false\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/web.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n\n  <!--\n    Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380\n  -->\n\n  <system.webServer>\n    <handlers>\n      <add name=\"aspNetCore\" path=\"*\" verb=\"*\" modules=\"AspNetCoreModule\" resourceType=\"Unspecified\"/>\n    </handlers>\n    <aspNetCore processPath=\"%LAUNCHER_PATH%\" arguments=\"%LAUNCHER_ARGS%\" stdoutLogEnabled=\"false\" stdoutLogFile=\".\\logs\\stdout\" forwardWindowsAuthToken=\"false\"/>\n  </system.webServer>\n</configuration>\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.AspNetCoreSample/wwwroot/App_Data/.gitkeep",
    "content": ""
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.NET46Sample/EFSecondLevelCache.Core.NET46Sample/App.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n    <startup> \n        <supportedRuntime version=\"v4.0\" sku=\".NETFramework,Version=v4.6\" />\n    </startup>\n  <runtime>\n    <assemblyBinding xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n      <dependentAssembly>\n        <assemblyIdentity name=\"System.Diagnostics.DiagnosticSource\" publicKeyToken=\"cc7b13ffcd2ddd51\" culture=\"neutral\" />\n        <bindingRedirect oldVersion=\"0.0.0.0-4.0.1.0\" newVersion=\"4.0.1.0\" />\n      </dependentAssembly>\n      <dependentAssembly>\n        <assemblyIdentity name=\"Microsoft.Extensions.Caching.Memory\" publicKeyToken=\"adb9793829ddae60\" culture=\"neutral\" />\n        <bindingRedirect oldVersion=\"0.0.0.0-1.1.1.0\" newVersion=\"1.1.1.0\" />\n      </dependentAssembly>\n      <dependentAssembly>\n        <assemblyIdentity name=\"Microsoft.Extensions.Caching.Abstractions\" publicKeyToken=\"adb9793829ddae60\" culture=\"neutral\" />\n        <bindingRedirect oldVersion=\"0.0.0.0-1.1.1.0\" newVersion=\"1.1.1.0\" />\n      </dependentAssembly>\n      <dependentAssembly>\n        <assemblyIdentity name=\"Microsoft.Extensions.Options\" publicKeyToken=\"adb9793829ddae60\" culture=\"neutral\" />\n        <bindingRedirect oldVersion=\"0.0.0.0-1.1.1.0\" newVersion=\"1.1.1.0\" />\n      </dependentAssembly>\n      <dependentAssembly>\n        <assemblyIdentity name=\"Newtonsoft.Json\" publicKeyToken=\"30ad4fe6b2a6aeed\" culture=\"neutral\" />\n        <bindingRedirect oldVersion=\"0.0.0.0-10.0.0.0\" newVersion=\"10.0.0.0\" />\n      </dependentAssembly>\n      <dependentAssembly>\n        <assemblyIdentity name=\"System.Interactive.Async\" publicKeyToken=\"94bc3704cddfc263\" culture=\"neutral\" />\n        <bindingRedirect oldVersion=\"0.0.0.0-3.0.3000.0\" newVersion=\"3.0.3000.0\" />\n      </dependentAssembly>\n      <dependentAssembly>\n        <assemblyIdentity name=\"CacheManager.Core\" publicKeyToken=\"5b450b4fb65c4cdb\" culture=\"neutral\" />\n        <bindingRedirect oldVersion=\"0.0.0.0-1.0.0.0\" newVersion=\"1.0.0.0\" />\n      </dependentAssembly>\n      <dependentAssembly>\n        <assemblyIdentity name=\"System.IO.Compression\" publicKeyToken=\"b77a5c561934e089\" culture=\"neutral\" />\n        <bindingRedirect oldVersion=\"0.0.0.0-4.1.2.0\" newVersion=\"4.1.2.0\" />\n      </dependentAssembly>\n    </assemblyBinding>\n  </runtime>\n</configuration>"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.NET46Sample/EFSecondLevelCache.Core.NET46Sample/DataLayer/ConfigureServices.cs",
    "content": "﻿using System;\nusing CacheManager.Core;\nusing Microsoft.Extensions.DependencyInjection;\nusing System.Threading;\nusing EFSecondLevelCache.Core.Contracts;\n\nnamespace EFSecondLevelCache.Core.NET46Sample.DataLayer\n{\n    public static class ConfigureServices\n    {\n        private static readonly Lazy<IServiceProvider> _serviceProviderBuilder =\n            new Lazy<IServiceProvider>(getServiceProvider, LazyThreadSafetyMode.ExecutionAndPublication);\n\n        /// <summary>\n        /// A lazy loaded thread-safe singleton\n        /// </summary>\n        public static IServiceProvider Instance { get; } = _serviceProviderBuilder.Value;\n\n        public static IEFCacheServiceProvider GetEFCacheServiceProvider()\n        {\n            return Instance.GetRequiredService<IEFCacheServiceProvider>();\n        }\n\n        private static IServiceProvider getServiceProvider()\n        {\n            var services = new ServiceCollection();\n\n            services.AddEntityFrameworkInMemoryDatabase()\n                    .AddDbContext<SampleContext>(ServiceLifetime.Scoped);\n\n            services.AddEFSecondLevelCache();\n\n            services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));\n            services.AddSingleton(typeof(ICacheManagerConfiguration),\n                new CacheManager.Core.ConfigurationBuilder()\n                        .WithJsonSerializer()\n                        .WithMicrosoftMemoryCacheHandle(instanceName: \"MemoryCache1\")\n                        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))\n                        .DisablePerformanceCounters()\n                        .DisableStatistics()\n                        .Build());\n\n            var serviceProvider = services.BuildServiceProvider();\n            return serviceProvider;\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.NET46Sample/EFSecondLevelCache.Core.NET46Sample/DataLayer/Entities/Post.cs",
    "content": "﻿namespace EFSecondLevelCache.Core.NET46Sample.DataLayer.Entities\n{\n    public class Post\n    {\n        public int Id { get; set; }\n        public string Title { get; set; }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.NET46Sample/EFSecondLevelCache.Core.NET46Sample/DataLayer/SampleContext.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing EFSecondLevelCache.Core.Contracts;\nusing EFSecondLevelCache.Core.NET46Sample.DataLayer.Entities;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace EFSecondLevelCache.Core.NET46Sample.DataLayer\n{\n    public class SampleContext : DbContext\n    {\n        private static readonly IEFCacheServiceProvider _efCacheServiceProvider = ConfigureServices.GetEFCacheServiceProvider();\n\n        public virtual DbSet<Post> Posts { get; set; }\n\n        public override int SaveChanges()\n        {\n            this.ChangeTracker.DetectChanges();\n            var changedEntityNames = this.GetChangedEntityNames();\n\n            this.ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again.\n            var result = base.SaveChanges();\n            this.ChangeTracker.AutoDetectChangesEnabled = true;\n\n\n            _efCacheServiceProvider.InvalidateCacheDependencies(changedEntityNames);\n\n            return result;\n        }\n\n        public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())\n        {\n            this.ChangeTracker.DetectChanges();\n            var changedEntityNames = this.GetChangedEntityNames();\n\n            this.ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again.\n            var result = base.SaveChangesAsync(cancellationToken);\n            this.ChangeTracker.AutoDetectChangesEnabled = true;\n\n            _efCacheServiceProvider.InvalidateCacheDependencies(changedEntityNames);\n\n            return result;\n        }\n\n        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)\n        {\n            optionsBuilder.UseInMemoryDatabase(\"TestDb\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.NET46Sample/EFSecondLevelCache.Core.NET46Sample/EFSecondLevelCache.Core.NET46Sample.csproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"14.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" />\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n    <ProjectGuid>{CEE7F26B-7A7E-4021-A37F-F9625EAF85CE}</ProjectGuid>\n    <OutputType>Exe</OutputType>\n    <AppDesignerFolder>Properties</AppDesignerFolder>\n    <RootNamespace>EFSecondLevelCache.Core.NET46Sample</RootNamespace>\n    <AssemblyName>EFSecondLevelCache.Core.NET46Sample</AssemblyName>\n    <TargetFrameworkVersion>v4.6</TargetFrameworkVersion>\n    <FileAlignment>512</FileAlignment>\n    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    <PlatformTarget>AnyCPU</PlatformTarget>\n    <DebugSymbols>true</DebugSymbols>\n    <DebugType>full</DebugType>\n    <Optimize>false</Optimize>\n    <OutputPath>bin\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    <PlatformTarget>AnyCPU</PlatformTarget>\n    <DebugType>pdbonly</DebugType>\n    <Optimize>true</Optimize>\n    <OutputPath>bin\\Release\\</OutputPath>\n    <DefineConstants>TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n  </PropertyGroup>\n  <ItemGroup>\n    <Reference Include=\"CacheManager.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5b450b4fb65c4cdb, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\CacheManager.Core.1.0.0\\lib\\net45\\CacheManager.Core.dll</HintPath>\n    </Reference>\n    <Reference Include=\"CacheManager.Microsoft.Extensions.Caching.Memory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5b450b4fb65c4cdb, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\CacheManager.Microsoft.Extensions.Caching.Memory.1.0.0\\lib\\net451\\CacheManager.Microsoft.Extensions.Caching.Memory.dll</HintPath>\n    </Reference>\n    <Reference Include=\"CacheManager.Serialization.Json, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5b450b4fb65c4cdb, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\CacheManager.Serialization.Json.1.0.0\\lib\\net45\\CacheManager.Serialization.Json.dll</HintPath>\n    </Reference>\n    <Reference Include=\"CacheManager.StackExchange.Redis, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5b450b4fb65c4cdb, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\CacheManager.StackExchange.Redis.1.0.0\\lib\\net45\\CacheManager.StackExchange.Redis.dll</HintPath>\n    </Reference>\n    <Reference Include=\"EFSecondLevelCache.Core, Version=1.0.3.0, Culture=neutral, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\EFSecondLevelCache.Core.1.0.4\\lib\\netstandard1.3\\EFSecondLevelCache.Core.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Microsoft.AspNetCore.Http.Abstractions, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Microsoft.AspNetCore.Http.Abstractions.1.1.1\\lib\\net451\\Microsoft.AspNetCore.Http.Abstractions.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Microsoft.AspNetCore.Http.Features, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Microsoft.AspNetCore.Http.Features.1.1.1\\lib\\net451\\Microsoft.AspNetCore.Http.Features.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Microsoft.EntityFrameworkCore, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Microsoft.EntityFrameworkCore.1.1.1\\lib\\net451\\Microsoft.EntityFrameworkCore.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Microsoft.EntityFrameworkCore.InMemory, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Microsoft.EntityFrameworkCore.InMemory.1.1.1\\lib\\net451\\Microsoft.EntityFrameworkCore.InMemory.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Microsoft.EntityFrameworkCore.Relational, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Microsoft.EntityFrameworkCore.Relational.1.1.1\\lib\\net451\\Microsoft.EntityFrameworkCore.Relational.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Microsoft.Extensions.Caching.Abstractions, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Microsoft.Extensions.Caching.Abstractions.1.1.1\\lib\\netstandard1.0\\Microsoft.Extensions.Caching.Abstractions.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Microsoft.Extensions.Caching.Memory, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Microsoft.Extensions.Caching.Memory.1.1.1\\lib\\net451\\Microsoft.Extensions.Caching.Memory.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Microsoft.Extensions.DependencyInjection, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Microsoft.Extensions.DependencyInjection.1.1.0\\lib\\netstandard1.1\\Microsoft.Extensions.DependencyInjection.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"Microsoft.Extensions.DependencyInjection.Abstractions, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Microsoft.Extensions.DependencyInjection.Abstractions.1.1.0\\lib\\netstandard1.0\\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"Microsoft.Extensions.Logging, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Microsoft.Extensions.Logging.1.1.1\\lib\\netstandard1.1\\Microsoft.Extensions.Logging.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Microsoft.Extensions.Logging.Abstractions, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Microsoft.Extensions.Logging.Abstractions.1.1.1\\lib\\netstandard1.1\\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Microsoft.Extensions.Options, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Microsoft.Extensions.Options.1.1.1\\lib\\netstandard1.0\\Microsoft.Extensions.Options.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Microsoft.Extensions.Primitives, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Microsoft.Extensions.Primitives.1.1.0\\lib\\netstandard1.0\\Microsoft.Extensions.Primitives.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"Microsoft.Win32.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Microsoft.Win32.Primitives.4.3.0\\lib\\net46\\Microsoft.Win32.Primitives.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Newtonsoft.Json.10.0.2\\lib\\net45\\Newtonsoft.Json.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Remotion.Linq, Version=2.1.0.0, Culture=neutral, PublicKeyToken=fee00910d6e5f53b, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\Remotion.Linq.2.1.1\\lib\\net45\\Remotion.Linq.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"StackExchange.Redis.StrongName, Version=1.2.1.0, Culture=neutral, PublicKeyToken=c219ff1ca8c2ce46, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\StackExchange.Redis.StrongName.1.2.1\\lib\\net46\\StackExchange.Redis.StrongName.dll</HintPath>\n    </Reference>\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.AppContext, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.AppContext.4.3.0\\lib\\net46\\System.AppContext.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.Collections.Immutable, Version=1.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Collections.Immutable.1.3.1\\lib\\portable-net45+win8+wp8+wpa81\\System.Collections.Immutable.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.ComponentModel.Composition\" />\n    <Reference Include=\"System.ComponentModel.DataAnnotations\" />\n    <Reference Include=\"System.Configuration\" />\n    <Reference Include=\"System.Console, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Console.4.3.0\\lib\\net46\\System.Console.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Diagnostics.DiagnosticSource, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Diagnostics.DiagnosticSource.4.3.0\\lib\\net46\\System.Diagnostics.DiagnosticSource.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.Globalization.Calendars, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Globalization.Calendars.4.3.0\\lib\\net46\\System.Globalization.Calendars.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.Interactive.Async, Version=3.0.3000.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Interactive.Async.3.1.1\\lib\\net46\\System.Interactive.Async.dll</HintPath>\n    </Reference>\n    <Reference Include=\"System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.IO.Compression.4.3.0\\lib\\net46\\System.IO.Compression.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.IO.Compression.FileSystem\" />\n    <Reference Include=\"System.IO.Compression.ZipFile, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.IO.Compression.ZipFile.4.3.0\\lib\\net46\\System.IO.Compression.ZipFile.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.IO.FileSystem, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.IO.FileSystem.4.3.0\\lib\\net46\\System.IO.FileSystem.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.IO.FileSystem.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.IO.FileSystem.Primitives.4.3.0\\lib\\net46\\System.IO.FileSystem.Primitives.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.Net.Http, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Net.Http.4.3.1\\lib\\net46\\System.Net.Http.dll</HintPath>\n    </Reference>\n    <Reference Include=\"System.Net.Sockets, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Net.Sockets.4.3.0\\lib\\net46\\System.Net.Sockets.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.Numerics\" />\n    <Reference Include=\"System.Reflection.TypeExtensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Reflection.TypeExtensions.4.3.0\\lib\\net46\\System.Reflection.TypeExtensions.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.Runtime.CompilerServices.Unsafe, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Runtime.CompilerServices.Unsafe.4.3.0\\lib\\netstandard1.0\\System.Runtime.CompilerServices.Unsafe.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.Runtime.InteropServices.RuntimeInformation, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Runtime.InteropServices.RuntimeInformation.4.3.0\\lib\\net45\\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.Security.Cryptography.Algorithms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Security.Cryptography.Algorithms.4.3.0\\lib\\net46\\System.Security.Cryptography.Algorithms.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.Security.Cryptography.Encoding, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Security.Cryptography.Encoding.4.3.0\\lib\\net46\\System.Security.Cryptography.Encoding.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.Security.Cryptography.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Security.Cryptography.Primitives.4.3.0\\lib\\net46\\System.Security.Cryptography.Primitives.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.Security.Cryptography.X509Certificates, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Security.Cryptography.X509Certificates.4.3.0\\lib\\net46\\System.Security.Cryptography.X509Certificates.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.Text.Encodings.Web, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Text.Encodings.Web.4.3.0\\lib\\netstandard1.0\\System.Text.Encodings.Web.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"System.Transactions\" />\n    <Reference Include=\"System.Xml.Linq\" />\n    <Reference Include=\"System.Data.DataSetExtensions\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n    <Reference Include=\"System.Data\" />\n    <Reference Include=\"System.Xml\" />\n    <Reference Include=\"System.Xml.ReaderWriter, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\..\\..\\..\\packages\\System.Xml.ReaderWriter.4.3.0\\lib\\net46\\System.Xml.ReaderWriter.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"DataLayer\\Entities\\Post.cs\" />\n    <Compile Include=\"DataLayer\\SampleContext.cs\" />\n    <Compile Include=\"DataLayer\\ConfigureServices.cs\" />\n    <Compile Include=\"Program.cs\" />\n    <Compile Include=\"Properties\\AssemblyInfo.cs\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"App.config\" />\n    <None Include=\"packages.config\" />\n  </ItemGroup>\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. \n       Other similar extension points exist, see Microsoft.Common.targets.\n  <Target Name=\"BeforeBuild\">\n  </Target>\n  <Target Name=\"AfterBuild\">\n  </Target>\n  -->\n</Project>"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.NET46Sample/EFSecondLevelCache.Core.NET46Sample/Program.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing EFSecondLevelCache.Core.NET46Sample.DataLayer;\nusing EFSecondLevelCache.Core.NET46Sample.DataLayer.Entities;\n\nnamespace EFSecondLevelCache.Core.NET46Sample\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            using (var context = new SampleContext())\n            {\n                context.Posts.Add(new Post { Title = \"Title 1\" });\n                context.SaveChanges();\n\n                var posts = context.Posts.Cacheable().ToList();\n                Console.WriteLine(posts.First().Title);\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.NET46Sample/EFSecondLevelCache.Core.NET46Sample/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled through the following \n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyTitle(\"EFSecondLevelCache.Core.NET46Sample\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"EFSecondLevelCache.Core.NET46Sample\")]\n[assembly: AssemblyCopyright(\"Copyright ©  2017\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible \n// to COM components.  If you need to access a type in this assembly from \n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n// The following GUID is for the ID of the typelib if this project is exposed to COM\n[assembly: Guid(\"cee7f26b-7a7e-4021-a37f-f9625eaf85ce\")]\n\n// Version information for an assembly consists of the following four values:\n//\n//      Major Version\n//      Minor Version \n//      Build Number\n//      Revision\n//\n// You can specify all the values or you can default the Build and Revision Numbers \n// by using the '*' as shown below:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"1.0.0.0\")]\n[assembly: AssemblyFileVersion(\"1.0.0.0\")]\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.NET46Sample/EFSecondLevelCache.Core.NET46Sample/packages.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"CacheManager.Core\" version=\"1.0.0\" targetFramework=\"net46\" />\n  <package id=\"CacheManager.Microsoft.Extensions.Caching.Memory\" version=\"1.0.0\" targetFramework=\"net46\" />\n  <package id=\"CacheManager.Serialization.Json\" version=\"1.0.0\" targetFramework=\"net46\" />\n  <package id=\"CacheManager.StackExchange.Redis\" version=\"1.0.0\" targetFramework=\"net46\" />\n  <package id=\"EFSecondLevelCache.Core\" version=\"1.1.1\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.AspNetCore.Http.Abstractions\" version=\"1.1.1\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.AspNetCore.Http.Features\" version=\"1.1.1\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.EntityFrameworkCore\" version=\"1.1.1\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.EntityFrameworkCore.InMemory\" version=\"1.1.1\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.EntityFrameworkCore.Relational\" version=\"1.1.1\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.Extensions.Caching.Abstractions\" version=\"1.1.1\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.Extensions.Caching.Memory\" version=\"1.1.1\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.Extensions.DependencyInjection\" version=\"1.1.0\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.Extensions.DependencyInjection.Abstractions\" version=\"1.1.0\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.Extensions.Logging\" version=\"1.1.1\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.Extensions.Logging.Abstractions\" version=\"1.1.1\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.Extensions.Options\" version=\"1.1.1\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.Extensions.Primitives\" version=\"1.1.0\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.NETCore.Platforms\" version=\"1.1.0\" targetFramework=\"net46\" />\n  <package id=\"Microsoft.Win32.Primitives\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"NETStandard.Library\" version=\"1.6.1\" targetFramework=\"net46\" />\n  <package id=\"Newtonsoft.Json\" version=\"10.0.2\" targetFramework=\"net46\" />\n  <package id=\"redis-64\" version=\"3.0.503\" targetFramework=\"net46\" />\n  <package id=\"Remotion.Linq\" version=\"2.1.1\" targetFramework=\"net46\" />\n  <package id=\"StackExchange.Redis.StrongName\" version=\"1.2.1\" targetFramework=\"net46\" />\n  <package id=\"System.AppContext\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Collections\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Collections.Concurrent\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Collections.Immutable\" version=\"1.3.1\" targetFramework=\"net46\" />\n  <package id=\"System.ComponentModel\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Console\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Diagnostics.Debug\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Diagnostics.DiagnosticSource\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Diagnostics.Tools\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Diagnostics.Tracing\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Globalization\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Globalization.Calendars\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Interactive.Async\" version=\"3.1.1\" targetFramework=\"net46\" />\n  <package id=\"System.IO\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.IO.Compression\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.IO.Compression.ZipFile\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.IO.FileSystem\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.IO.FileSystem.Primitives\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Linq\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Linq.Expressions\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Linq.Queryable\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Net.Http\" version=\"4.3.1\" targetFramework=\"net46\" />\n  <package id=\"System.Net.Primitives\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Net.Sockets\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.ObjectModel\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Reflection\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Reflection.Extensions\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Reflection.Primitives\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Reflection.TypeExtensions\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Resources.ResourceManager\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Runtime\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Runtime.CompilerServices.Unsafe\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Runtime.Extensions\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Runtime.Handles\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Runtime.InteropServices\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Runtime.InteropServices.RuntimeInformation\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Runtime.Numerics\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Security.Cryptography.Algorithms\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Security.Cryptography.Encoding\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Security.Cryptography.Primitives\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Security.Cryptography.X509Certificates\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Text.Encoding\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Text.Encoding.Extensions\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Text.Encodings.Web\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Text.RegularExpressions\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Threading\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Threading.Tasks\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Threading.Timer\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Xml.ReaderWriter\" version=\"4.3.0\" targetFramework=\"net46\" />\n  <package id=\"System.Xml.XDocument\" version=\"4.3.0\" targetFramework=\"net46\" />\n</packages>"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.PerformanceTests/BenchmarkTests.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing BenchmarkDotNet.Attributes;\nusing BenchmarkDotNet.Order;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace EFSecondLevelCache.Core.PerformanceTests\n{\n    public class BenchmarkTests\n    {\n        private int _count;\n\n        [GlobalSetup]\n        public void Setup()\n        {\n            Console.WriteLine(\"SetupDatabase\");\n            SetupDatabase();\n        }\n\n        private static void SetupDatabase()\n        {\n            using (var serviceScope = TestsServiceProvider.WithJsonSerializerInstance.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var db = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    if (db.Database.EnsureCreated())\n                    {\n                        var student1 = new Student { Name = \"user 1\" };\n                        db.Students.Add(student1);\n\n                        var student2 = new Student { Name = \"user 2\" };\n                        db.Students.Add(student2);\n\n                        var student3 = new Student { Name = \"user 3\" };\n                        db.Students.Add(student3);\n\n                        db.SaveChanges();\n                    }\n                }\n            }\n        }\n\n        [Benchmark(Baseline = true)]\n        public void RunQueryDirectly()\n        {\n            using (var serviceScope = TestsServiceProvider.WithJsonSerializerInstance.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var db = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var students = db.Students.Where(x => x.Id > 0).ToList();\n                    _count = students.Count;\n                }\n            }\n        }\n\n        [Benchmark]\n        public void RunCacheableQueryWithJsonSerializer()\n        {\n            using (var serviceScope = TestsServiceProvider.WithJsonSerializerInstance.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var db = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var students = db.Students.Where(x => x.Id > 0).Cacheable().ToList();\n                    _count = students.Count;\n                }\n            }\n        }\n\n        [Benchmark]\n        public void RunCacheableQueryWithGzJsonSerializer()\n        {\n            using (var serviceScope = TestsServiceProvider.WithGzJsonSerializerInstance.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var db = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var students = db.Students.Where(x => x.Id > 0).Cacheable().ToList();\n                    _count = students.Count;\n                }\n            }\n        }\n\n        [Benchmark]\n        public void RunCacheableQueryWithDictionaryHandle()\n        {\n            using (var serviceScope = TestsServiceProvider.WithDictionaryHandleInstance.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var db = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var students = db.Students.Where(x => x.Id > 0).Cacheable().ToList();\n                    _count = students.Count;\n                }\n            }\n        }\n\n        [Benchmark]\n        public void RunCacheableQueryWithMicrosoftMemoryCache()\n        {\n            using (var serviceScope = TestsServiceProvider.WithMicrosoftMemoryCacheInstance.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var db = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var students = db.Students.Where(x => x.Id > 0).Cacheable().ToList();\n                    _count = students.Count;\n                }\n            }\n        }\n\n        [GlobalCleanup]\n        public void GlobalCleanup()\n        {\n            Console.WriteLine($\"_count: {_count}\");\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.PerformanceTests/EFSecondLevelCache.Core.PerformanceTests.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <OutputType>Exe</OutputType>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Logging.Console\" Version=\"3.1.0\" />\n    <PackageReference Include=\"CacheManager.Core\" Version=\"1.2.0\" />\n    <PackageReference Include=\"CacheManager.Microsoft.Extensions.Caching.Memory\" Version=\"1.2.0\" />\n    <PackageReference Include=\"CacheManager.Serialization.Json\" Version=\"1.2.0\" />\n    <PackageReference Include=\"BenchmarkDotNet\" Version=\"0.12.0\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\..\\EFSecondLevelCache.Core\\EFSecondLevelCache.Core.csproj\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.PerformanceTests/Program.cs",
    "content": "﻿using BenchmarkDotNet.Columns;\nusing BenchmarkDotNet.Configs;\nusing BenchmarkDotNet.Horology;\nusing BenchmarkDotNet.Jobs;\nusing BenchmarkDotNet.Running;\n\nnamespace EFSecondLevelCache.Core.PerformanceTests\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            var config = ManualConfig.Create(DefaultConfig.Instance)\n                    .With(BenchmarkDotNet.Analysers.EnvironmentAnalyser.Default)\n                    .With(BenchmarkDotNet.Exporters.MarkdownExporter.GitHub)\n                    .With(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default)\n                    .With(StatisticColumn.Mean)\n                    .With(StatisticColumn.Median)\n                    .With(StatisticColumn.StdDev)\n                    .With(StatisticColumn.OperationsPerSecond)\n                    .With(BaselineRatioColumn.RatioMean)\n                    .With(RankColumn.Arabic)\n                    .With(Job.Core\n                        .WithIterationCount(10)\n                        .WithInvocationCount(16)\n                        .WithIterationTime(TimeInterval.FromSeconds(10))\n                        .WithWarmupCount(4)\n                        .WithLaunchCount(1));\n            BenchmarkRunner.Run<BenchmarkTests>(config);\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.PerformanceTests/SampleContext.cs",
    "content": "using EFSecondLevelCache.Core.Contracts;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\n\nnamespace EFSecondLevelCache.Core.PerformanceTests\n{\n    public class Student\n    {\n        public int Id { get; set; }\n        public string Name { get; set; }\n    }\n\n    public class SampleContext : DbContext\n    {\n        public virtual DbSet<Student> Students { get; set; }\n\n        public SampleContext(DbContextOptions<SampleContext> options) : base(options)\n        { }\n\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            modelBuilder.Entity<Student>(entity =>\n            {\n                entity.Property(e => e.Name)\n                    .IsRequired()\n                    .HasMaxLength(450);\n            });\n        }\n\n        public override int SaveChanges()\n        {\n            var changedEntityNames = this.GetChangedEntityNames();\n\n            this.ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again.\n            var result = base.SaveChanges();\n            this.ChangeTracker.AutoDetectChangesEnabled = true;\n\n            this.GetService<IEFCacheServiceProvider>().InvalidateCacheDependencies(changedEntityNames);\n\n            return result;\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.PerformanceTests/TestsServiceProvider.cs",
    "content": "﻿using System;\nusing System.IO;\nusing CacheManager.Core;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.DependencyInjection;\nusing System.Threading;\nusing Newtonsoft.Json;\n\nnamespace EFSecondLevelCache.Core.PerformanceTests\n{\n    /// <summary>\n    /// A lazy loaded thread-safe singleton\n    /// </summary>\n    public static class TestsServiceProvider\n    {\n        private static readonly Lazy<IServiceProvider> _jsonSerializerProviderBuilder =\n            new Lazy<IServiceProvider>(getWithJsonSerializerServiceProvider, LazyThreadSafetyMode.ExecutionAndPublication);\n\n        private static readonly Lazy<IServiceProvider> _gzJsonSerializerProviderBuilder =\n            new Lazy<IServiceProvider>(getWithGzJsonSerializerServiceProvider, LazyThreadSafetyMode.ExecutionAndPublication);\n\n        private static readonly Lazy<IServiceProvider> _dictionaryHandleProviderBuilder =\n            new Lazy<IServiceProvider>(getWithDictionaryHandleServiceProvider, LazyThreadSafetyMode.ExecutionAndPublication);\n\n        private static readonly Lazy<IServiceProvider> _microsoftMemoryCacheProviderBuilder =\n            new Lazy<IServiceProvider>(getWithMicrosoftMemoryCacheHandleServiceProvider, LazyThreadSafetyMode.ExecutionAndPublication);\n\n        public static IServiceProvider WithJsonSerializerInstance { get; } = _jsonSerializerProviderBuilder.Value;\n\n        public static IServiceProvider WithGzJsonSerializerInstance { get; } = _gzJsonSerializerProviderBuilder.Value;\n\n        public static IServiceProvider WithDictionaryHandleInstance { get; } = _dictionaryHandleProviderBuilder.Value;\n\n        public static IServiceProvider WithMicrosoftMemoryCacheInstance { get; } = _microsoftMemoryCacheProviderBuilder.Value;\n\n        private static IServiceProvider getWithJsonSerializerServiceProvider()\n        {\n            return createServiceProvider(sc => addJsonSerializer(sc));\n        }\n\n        private static IServiceProvider getWithGzJsonSerializerServiceProvider()\n        {\n            return createServiceProvider(sc => addGzJsonSerializer(sc));\n        }\n\n        private static IServiceProvider getWithDictionaryHandleServiceProvider()\n        {\n            return createServiceProvider(sc => addDictionaryHandle(sc));\n        }\n\n        private static IServiceProvider getWithMicrosoftMemoryCacheHandleServiceProvider()\n        {\n            return createServiceProvider(sc => addMicrosoftMemoryCacheHandle(sc));\n        }\n\n        private static void addJsonSerializer(ServiceCollection services)\n        {\n            var jss = new JsonSerializerSettings\n            {\n                NullValueHandling = NullValueHandling.Ignore,\n                ReferenceLoopHandling = ReferenceLoopHandling.Ignore\n            };\n\n            services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));\n            services.AddSingleton(typeof(ICacheManagerConfiguration),\n                new CacheManager.Core.ConfigurationBuilder()\n                        .WithJsonSerializer(serializationSettings: jss, deserializationSettings: jss)\n                        .WithUpdateMode(CacheUpdateMode.Up)\n                        .WithMicrosoftMemoryCacheHandle(instanceName: \"MemoryCache1\")\n                        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))\n                        .DisablePerformanceCounters()\n                        .DisableStatistics()\n                        .Build());\n        }\n\n        private static void addGzJsonSerializer(ServiceCollection services)\n        {\n            var jss = new JsonSerializerSettings\n            {\n                NullValueHandling = NullValueHandling.Ignore,\n                ReferenceLoopHandling = ReferenceLoopHandling.Ignore\n            };\n\n            services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));\n            services.AddSingleton(typeof(ICacheManagerConfiguration),\n                new CacheManager.Core.ConfigurationBuilder()\n                        .WithGzJsonSerializer(serializationSettings: jss, deserializationSettings: jss)\n                        .WithUpdateMode(CacheUpdateMode.Up)\n                        .WithMicrosoftMemoryCacheHandle(instanceName: \"MemoryCache2\")\n                        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))\n                        .DisablePerformanceCounters()\n                        .DisableStatistics()\n                        .Build());\n        }\n\n        private static void addDictionaryHandle(ServiceCollection services)\n        {\n            services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));\n            services.AddSingleton(typeof(ICacheManagerConfiguration),\n                new CacheManager.Core.ConfigurationBuilder()\n                        .WithDictionaryHandle()\n                        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))\n                        .DisablePerformanceCounters()\n                        .DisableStatistics()\n                        .Build());\n        }\n\n        private static void addMicrosoftMemoryCacheHandle(ServiceCollection services)\n        {\n            services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));\n            services.AddSingleton(typeof(ICacheManagerConfiguration),\n                new CacheManager.Core.ConfigurationBuilder()\n                        .WithMicrosoftMemoryCacheHandle()\n                        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))\n                        .DisablePerformanceCounters()\n                        .DisableStatistics()\n                        .Build());\n        }\n\n        private static IServiceProvider createServiceProvider(Action<ServiceCollection> config)\n        {\n            var services = new ServiceCollection();\n\n            services.AddEFSecondLevelCache();\n\n            config(services);\n\n            var connectionString = getConnectionString();\n            services.AddDbContext<SampleContext>(optionsBuilder => optionsBuilder.UseSqlServer(connectionString));\n\n            return services.BuildServiceProvider();\n        }\n\n        private static string getConnectionString()\n        {\n            var appPath = Environment.CurrentDirectory.Split(new[] { \"bin\" }, StringSplitOptions.None);\n            var appDataDir = Path.Combine(appPath[0], \"app_data\");\n            if (!Directory.Exists(appDataDir))\n            {\n                Directory.CreateDirectory(appDataDir);\n            }\n\n            var connectionString = \"Server=(localdb)\\\\mssqllocaldb;Initial Catalog=EFSecondLevelCacheCore.Perf.Test;AttachDBFilename=|DataDirectory|\\\\EFSecondLevelCacheCore.Perf.Test.mdf;Trusted_Connection=True;\"\n            .Replace(\"|DataDirectory|\", appDataDir);\n            Console.WriteLine($\"Using {connectionString}\");\n            return connectionString;\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.PerformanceTests/_0-restore.bat",
    "content": "rmdir /S /Q bin\nrmdir /S /Q obj\ndotnet restore\npause"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.PerformanceTests/_1-dotnet_run.bat",
    "content": "dotnet watch run -c Release"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.PerformanceTests/app_data/.gitkeep",
    "content": ""
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.Tests/EFCacheServiceProviderTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\n\nnamespace EFSecondLevelCache.Core.Tests\n{\n    [TestClass]\n    public class EFCacheServiceProviderTests\n    {\n        private readonly IEFCacheServiceProvider _cacheService;\n\n        public EFCacheServiceProviderTests()\n        {\n            _cacheService = TestsBase.GetInMemoryCacheServiceProvider();\n            //_cacheService = TestsBase.GetRedisCacheServiceProvider();\n        }\n\n        [TestInitialize]\n        public void ClearEFGlobalCacheBeforeEachTest()\n        {\n            _cacheService.ClearAllCachedEntries();\n        }\n\n        [TestMethod]\n        public void TestCacheInvalidationWithTwoRoots()\n        {\n            _cacheService.InsertValue(\"EF_key1\", \"value1\", new HashSet<string> { \"entity1.model\", \"entity2.model\" }, null);\n\n            _cacheService.InsertValue(\"EF_key2\", \"value2\", new HashSet<string> { \"entity1.model\", \"entity2.model\" }, null);\n\n\n            var value1 = _cacheService.GetValue(\"EF_key1\");\n            Assert.IsNotNull(value1);\n\n            var value2 = _cacheService.GetValue(\"EF_key2\");\n            Assert.IsNotNull(value2);\n\n            _cacheService.InvalidateCacheDependencies(new[] { \"entity2.model\" });\n\n            value1 = _cacheService.GetValue(\"EF_key1\");\n            Assert.IsNull(value1);\n\n            value2 = _cacheService.GetValue(\"EF_key2\");\n            Assert.IsNull(value2);\n        }\n\n        [TestMethod]\n        public void TestCacheInvalidationWithOneRoot()\n        {\n            _cacheService.InsertValue(\"EF_key1\", \"value1\", new HashSet<string> { \"entity1\" }, null);\n\n            _cacheService.InsertValue(\"EF_key2\", \"value2\", new HashSet<string> { \"entity1\" }, null);\n\n            var value1 = _cacheService.GetValue(\"EF_key1\");\n            Assert.IsNotNull(value1);\n\n            var value2 = _cacheService.GetValue(\"EF_key2\");\n            Assert.IsNotNull(value2);\n\n            _cacheService.InvalidateCacheDependencies(new[] { \"entity1\" });\n\n            value1 = _cacheService.GetValue(\"EF_key1\");\n            Assert.IsNull(value1);\n\n            value2 = _cacheService.GetValue(\"EF_key2\");\n            Assert.IsNull(value2);\n        }\n\n        [TestMethod]\n        public void TestObjectCacheInvalidationWithOneRoot()\n        {\n            const string rootCacheKey = \"EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities.Product\";\n\n            _cacheService.InvalidateCacheDependencies(new string[] { rootCacheKey });\n\n            var val11888622 = _cacheService.GetValue(\"11888622\");\n            Assert.IsNull(val11888622);\n\n            _cacheService.InsertValue(\"11888622\", new Product { ProductId = 5041 }, new HashSet<string> { rootCacheKey }, null);\n\n            var val44513A63 = _cacheService.GetValue(\"44513A63\");\n            Assert.IsNull(val44513A63);\n\n            _cacheService.InsertValue(\"44513A63\", new Product { ProductId = 5041 }, new HashSet<string> { rootCacheKey }, null);\n\n            _cacheService.InvalidateCacheDependencies(new string[] { rootCacheKey });\n\n            val11888622 = _cacheService.GetValue(\"11888622\");\n            Assert.IsNull(val11888622);\n\n            val44513A63 = _cacheService.GetValue(\"44513A63\");\n            Assert.IsNull(val44513A63);\n        }\n\n        [TestMethod]\n        public void TestCacheInvalidationWithSimilarRoots()\n        {\n            _cacheService.InsertValue(\"EF_key1\", \"value1\", new HashSet<string> { \"entity1\", \"entity2\" }, null);\n\n            _cacheService.InsertValue(\"EF_key2\", \"value2\", new HashSet<string> { \"entity2\" }, null);\n\n            var value1 = _cacheService.GetValue(\"EF_key1\");\n            Assert.IsNotNull(value1);\n\n            var value2 = _cacheService.GetValue(\"EF_key2\");\n            Assert.IsNotNull(value2);\n\n            _cacheService.InvalidateCacheDependencies(new[] { \"entity2\" });\n\n            value1 = _cacheService.GetValue(\"EF_key1\");\n            Assert.IsNull(value1);\n\n            value2 = _cacheService.GetValue(\"EF_key2\");\n            Assert.IsNull(value2);\n        }\n\n        [TestMethod]\n        public void TestInsertingNullValues()\n        {\n            _cacheService.InsertValue(\"EF_key1\", null, new HashSet<string> { \"entity1\", \"entity2\" }, null);\n\n            var value1 = _cacheService.GetValue(\"EF_key1\");\n            Assert.IsTrue(Equals(value1, _cacheService.NullObject), $\"value1 is `{value1}`\");\n        }\n\n        [TestMethod]\n        public void TestParallelInsertsAndRemoves()\n        {\n            var tests = new List<Action>();\n\n            for (var i = 0; i < 4000; i++)\n            {\n                var i1 = i;\n                tests.Add(() => _cacheService.InsertValue($\"EF_key{i1}\", i1, new HashSet<string> { \"entity1\", \"entity2\" }, null));\n            }\n\n            for (var i = 0; i < 400; i++)\n            {\n                if (i % 2 == 0)\n                {\n                    tests.Add(() => _cacheService.InvalidateCacheDependencies(new[] { \"entity1\" }));\n                }\n                else\n                {\n                    tests.Add(() => _cacheService.InvalidateCacheDependencies(new[] { \"entity2\" }));\n                }\n            }\n\n            var rnd = new Random();\n            Parallel.Invoke(tests.OrderBy(a => rnd.Next()).ToArray());\n\n            var value1 = _cacheService.GetValue(\"EF_key1\");\n            Assert.IsNull(value1);\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.Tests/EFCachedQueryProviderAsyncTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\n\nnamespace EFSecondLevelCache.Core.Tests\n{\n    [TestClass]\n    public class EFCachedQueryProviderAsyncTests\n    {\n        private readonly IServiceProvider _serviceProvider;\n\n        public EFCachedQueryProviderAsyncTests()\n        {\n            _serviceProvider = TestsBase.GetServiceProvider();\n        }\n\n        [TestInitialize]\n        public void ClearEFGlobalCacheBeforeEachTest()\n        {\n            _serviceProvider.GetRequiredService<IEFCacheServiceProvider>().ClearAllCachedEntries();\n        }\n\n        [TestMethod]\n        public async Task TestSecondLevelCacheUsingAsyncMethodsDoesNotHitTheDatabase()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var isActive = true;\n                    var name = \"Product1\";\n\n                    Console.WriteLine(\"1st async query, reading from db\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var list1 = await context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo1, _serviceProvider)\n                        .ToListAsync();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsTrue(list1.Any());\n                    var hash1 = debugInfo1.EFCacheKey.KeyHash;\n\n\n                    Console.WriteLine(\"same async query, reading from 2nd level cache\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var list2 = await context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo2, _serviceProvider)\n                        .ToListAsync();\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(list2.Any());\n                    var hash2 = debugInfo2.EFCacheKey.KeyHash;\n\n\n                    Console.WriteLine(\"same async query, reading from 2nd level cache.\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var list3 = await context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo3, _serviceProvider)\n                        .ToListAsync();\n                    Assert.AreEqual(true, debugInfo3.IsCacheHit);\n                    Assert.IsTrue(list3.Any());\n                    var hash3 = debugInfo3.EFCacheKey.KeyHash;\n\n                    Assert.AreEqual(hash1, hash2);\n                    Assert.AreEqual(hash2, hash3);\n\n                    Console.WriteLine(\"different async query, reading from db.\");\n                    var debugInfo4 = new EFCacheDebugInfo();\n                    var list4 = await context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == \"Product2\")\n                        .Cacheable(debugInfo4, _serviceProvider)\n                        .ToListAsync();\n                    Assert.AreEqual(false, debugInfo4.IsCacheHit);\n                    Assert.IsTrue(list4.Any());\n\n                    var hash4 = debugInfo4.EFCacheKey.KeyHash;\n                    Assert.AreNotSame(hash3, hash4);\n\n                    Console.WriteLine(\"different async query, reading from db.\");\n                    var debugInfo5 = new EFCacheDebugInfo();\n                    var product1 = await context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == \"Product2\")\n                        .Cacheable(debugInfo5, _serviceProvider)\n                        .FirstOrDefaultAsync();\n                    Assert.AreEqual(false, debugInfo5.IsCacheHit);\n                    Assert.IsNotNull(product1);\n\n                    var hash5 = debugInfo5.EFCacheKey.KeyHash;\n                    Assert.AreNotSame(hash4, hash5);\n                }\n            }\n        }\n\n        [TestMethod]\n        public async Task TestSecondLevelCacheUsingDifferentAsyncMethods()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var isActive = true;\n                    var name = \"Product3\";\n\n                    Console.WriteLine(\"ToListAsync\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var list1 = await context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo1, _serviceProvider)\n                        .ToListAsync();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsTrue(list1.Any());\n\n\n                    Console.WriteLine(\"CountAsync\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var count = await context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo2, _serviceProvider)\n                        .CountAsync();\n                    Assert.AreEqual(false, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(count > 0);\n\n\n                    Console.WriteLine(\"FirstOrDefaultAsync\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var product1 = await context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo3, _serviceProvider)\n                        .FirstOrDefaultAsync();\n                    Assert.AreEqual(false, debugInfo3.IsCacheHit);\n                    Assert.IsTrue(product1 != null);\n\n\n                    Console.WriteLine(\"AnyAsync\");\n                    var debugInfo4 = new EFCacheDebugInfo();\n                    var any = await context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == \"Product2\")\n                        .Cacheable(debugInfo4, _serviceProvider)\n                        .AnyAsync();\n                    Assert.AreEqual(false, debugInfo4.IsCacheHit);\n                    Assert.IsTrue(any);\n\n\n                    Console.WriteLine(\"SumAsync\");\n                    var debugInfo5 = new EFCacheDebugInfo();\n                    var sum = await context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == \"Product2\")\n                        .Cacheable(debugInfo5, _serviceProvider)\n                        .SumAsync(x => x.ProductId);\n                    Assert.AreEqual(false, debugInfo5.IsCacheHit);\n                    Assert.IsTrue(sum > 0);\n                }\n            }\n        }\n\n        [TestMethod]\n        public async Task TestSecondLevelCacheUsingTwoCountAsyncMethods()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var isActive = true;\n                    var name = \"Product2\";\n\n                    Console.WriteLine(\"Count 1, From DB\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var count = await context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo2, _serviceProvider)\n                        .CountAsync();\n                    Assert.AreEqual(false, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(count > 0);\n\n                    Console.WriteLine(\"Count 2, Reading from cache\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    count = await context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo3, _serviceProvider)\n                        .CountAsync();\n                    Assert.AreEqual(true, debugInfo3.IsCacheHit);\n                    Assert.IsTrue(count > 0);\n                }\n            }\n        }\n\n        [TestMethod]\n        public async Task TestSecondLevelCacheUsingFindAsyncMethods()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var debugInfo = new EFCacheDebugInfo();\n                    var product1 = await context.Products\n                        .Cacheable(debugInfo, _serviceProvider)\n                        .FindAsync(1);\n                    Assert.AreEqual(false, debugInfo.IsCacheHit);\n                    Assert.IsTrue(product1 != null);\n\n\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    product1 = await context.Products\n                        .Cacheable(debugInfo2, _serviceProvider)\n                        .FindAsync(1);\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(product1 != null);\n                }\n            }\n        }\n\n        [TestMethod]\n        [Ignore]\n        public void TestParallelAsyncCalls()\n        {\n            var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();\n            var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>();\n\n            var tests = new List<Action>();\n            for (var i = 0; i < 4000; i++)\n            {\n                var i1 = i.ToString();\n                tests.Add(async () =>\n                {\n                    var count = await context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive && product.ProductName == i1)\n                        .Cacheable(_serviceProvider)\n                        .CountAsync();\n                });\n            }\n\n            var rnd = new Random();\n            Parallel.Invoke(tests.OrderBy(a => rnd.Next()).ToArray());\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.Tests/EFCachedQueryProviderBasicTests.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing AutoMapper;\nusing AutoMapper.QueryableExtensions;\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer;\nusing EFSecondLevelCache.Core.AspNetCoreSample.Models;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\n\nnamespace EFSecondLevelCache.Core.Tests\n{\n    [TestClass]\n    public class EFCachedQueryProviderBasicTests\n    {\n        private readonly IServiceProvider _serviceProvider;\n\n        public EFCachedQueryProviderBasicTests()\n        {\n            _serviceProvider = TestsBase.GetServiceProvider();\n        }\n\n        [TestInitialize]\n        public void ClearEFGlobalCacheBeforeEachTest()\n        {\n            _serviceProvider.GetRequiredService<IEFCacheServiceProvider>().ClearAllCachedEntries();\n        }\n\n        [TestMethod]\n        public void TestIncludeMethodAffectsKeyCache()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    Console.WriteLine(\"a normal query\");\n                    var product1IncludeTags = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag).FirstOrDefault();\n                    Assert.IsNotNull(product1IncludeTags);\n\n\n                    Console.WriteLine(\"1st query using Include method.\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var firstProductIncludeTags = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                                                                 .Cacheable(debugInfo1, _serviceProvider)\n                                                                 .FirstOrDefault();\n                    Assert.IsNotNull(firstProductIncludeTags);\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    var hash1 = debugInfo1.EFCacheKey.KeyHash;\n                    var cacheDependencies1 = debugInfo1.EFCacheKey.CacheDependencies;\n\n                    Console.WriteLine(\n                        @\"2nd query looks the same, but it doesn't have the Include method, so it shouldn't produce the same queryKeyHash.\n                 This was the problem with just parsing the LINQ expression, without considering the produced SQL.\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var firstProduct = context.Products.Cacheable(debugInfo2, _serviceProvider)\n                                                      .FirstOrDefault();\n                    Assert.IsNotNull(firstProduct);\n                    Assert.AreEqual(false, debugInfo2.IsCacheHit);\n                    var hash2 = debugInfo2.EFCacheKey.KeyHash;\n                    var cacheDependencies2 = debugInfo2.EFCacheKey.CacheDependencies;\n\n                    Assert.AreNotEqual(hash1, hash2);\n                    Assert.AreNotEqual(cacheDependencies1, cacheDependencies2);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestQueriesUsingDifferentParameterValuesWillNotUseTheCache()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    Console.WriteLine(\"1st query, reading from db.\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var list1 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive && product.ProductName == \"Product1\")\n                        .Cacheable(debugInfo1, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsNotNull(list1);\n\n                    Console.WriteLine(\"2nd query, reading from db.\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var list2 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == false && product.ProductName == \"Product1\")\n                        .Cacheable(debugInfo2, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(false, debugInfo2.IsCacheHit);\n                    Assert.IsNotNull(list2);\n\n                    Console.WriteLine(\"third query, reading from db.\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var list3 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == false && product.ProductName == \"Product2\")\n                        .Cacheable(debugInfo3, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(false, debugInfo3.IsCacheHit);\n                    Assert.IsNotNull(list3);\n\n                    Console.WriteLine(\"4th query, same as third one, reading from cache.\");\n                    var debugInfo4 = new EFCacheDebugInfo();\n                    var list4 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == false && product.ProductName == \"Product2\")\n                        .Cacheable(debugInfo4, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(true, debugInfo4.IsCacheHit);\n                    Assert.IsNotNull(list4);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestSecondLevelCacheCreatesTheCommandTreeAfterCallingTheSameNormalQuery()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var isActive = true;\n                    var name = \"Product3\";\n\n                    Console.WriteLine(\"1st normal query, reading from db.\");\n                    var list1 = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name)\n                                       .ToList();\n                    Assert.IsTrue(list1.Any());\n\n\n                    Console.WriteLine(\"same query as Cacheable, reading from db.\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var list2 = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name)\n                                       .Cacheable(debugInfo2, _serviceProvider)\n                                       .ToList();\n                    Assert.AreEqual(false, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(list2.Any());\n                    var hash2 = debugInfo2.EFCacheKey.KeyHash;\n\n\n                    Console.WriteLine(\"same query, reading from 2nd level cache.\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var list3 = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name)\n                                       .Cacheable(debugInfo3, _serviceProvider)\n                                       .ToList();\n                    Assert.AreEqual(true, debugInfo3.IsCacheHit);\n                    Assert.IsTrue(list3.Any());\n                    var hash3 = debugInfo3.EFCacheKey.KeyHash;\n\n                    Assert.AreEqual(hash2, hash3);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestSecondLevelCacheDoesNotHitTheDatabase()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var isActive = true;\n                    var name = \"Product1\";\n\n                    Console.WriteLine(\"1st query, reading from db.\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var list1 = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name)\n                                       .Cacheable(debugInfo1, _serviceProvider)\n                                       .ToList();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsTrue(list1.Any());\n                    var hash1 = debugInfo1.EFCacheKey.KeyHash;\n\n\n                    Console.WriteLine(\"same query, reading from 2nd level cache.\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var list2 = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name)\n                                       .Cacheable(debugInfo2, _serviceProvider)\n                                       .ToList();\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(list2.Any());\n                    var hash2 = debugInfo2.EFCacheKey.KeyHash;\n\n\n                    Console.WriteLine(\"same query, reading from 2nd level cache.\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var list3 = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name)\n                                       .Cacheable(debugInfo3, _serviceProvider)\n                                       .ToList();\n                    Assert.AreEqual(true, debugInfo3.IsCacheHit);\n                    Assert.IsTrue(list3.Any());\n                    var hash3 = debugInfo3.EFCacheKey.KeyHash;\n\n                    Assert.AreEqual(hash1, hash2);\n                    Assert.AreEqual(hash2, hash3);\n\n                    Console.WriteLine(\"different query, reading from db.\");\n                    var debugInfo4 = new EFCacheDebugInfo();\n                    var list4 = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == \"Product2\")\n                                       .Cacheable(debugInfo4, _serviceProvider)\n                                       .ToList();\n                    Assert.AreEqual(false, debugInfo4.IsCacheHit);\n                    Assert.IsTrue(list4.Any());\n\n                    var hash4 = debugInfo4.EFCacheKey.KeyHash;\n                    Assert.AreNotSame(hash3, hash4);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestSecondLevelCacheInTwoDifferentContextsDoesNotHitTheDatabase()\n        {\n            var isActive = true;\n            var name = \"Product2\";\n            string hash2;\n            string hash3;\n\n            Console.WriteLine(\"context 1.\");\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    Console.WriteLine(\"1st query as Cacheable, reading from db.\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var list2 = context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo2, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(false, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(list2.Any());\n                    hash2 = debugInfo2.EFCacheKey.KeyHash;\n                }\n            }\n\n            Console.WriteLine(\"context 2\");\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    Console.WriteLine(\"same query, reading from 2nd level cache.\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var list3 = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name)\n                                       .Cacheable(debugInfo3, _serviceProvider)\n                                       .ToList();\n                    Assert.AreEqual(true, debugInfo3.IsCacheHit);\n                    Assert.IsTrue(list3.Any());\n                    hash3 = debugInfo3.EFCacheKey.KeyHash;\n                }\n            }\n\n            Assert.AreEqual(hash2, hash3);\n        }\n\n\n        [TestMethod]\n        public void TestSecondLevelCacheInTwoDifferentParallelContexts()\n        {\n            var isActive = true;\n            var name = \"Product1\";\n            var debugInfo2 = new EFCacheDebugInfo();\n            var debugInfo3 = new EFCacheDebugInfo();\n\n            var task1 = Task.Factory.StartNew(() =>\n            {\n                Console.WriteLine(\"context 1.\");\n                using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n                {\n                    using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                    {\n                        Console.WriteLine(\"1st query as Cacheable.\");\n                        var list2 = context.Products\n                            .OrderBy(product => product.ProductNumber)\n                            .Where(product => product.IsActive == isActive && product.ProductName == name)\n                            .Cacheable(debugInfo2, _serviceProvider)\n                            .ToList();\n                        Assert.IsTrue(list2.Any());\n                    }\n                }\n            });\n\n            var task2 = Task.Factory.StartNew(() =>\n            {\n                Console.WriteLine(\"context 2\");\n                using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n                {\n                    using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                    {\n                        Console.WriteLine(\"same query\");\n                        var list3 = context.Products\n                            .OrderBy(product => product.ProductNumber)\n                            .Where(product => product.IsActive == isActive && product.ProductName == name)\n                            .Cacheable(debugInfo3, _serviceProvider)\n                            .ToList();\n                        Assert.IsTrue(list3.Any());\n                    }\n                }\n            });\n\n            Task.WaitAll(task1, task2);\n\n            Assert.AreEqual(debugInfo2.EFCacheKey.KeyHash, debugInfo3.EFCacheKey.KeyHash);\n        }\n\n        [TestMethod]\n        public void TestSecondLevelCacheUsingDifferentSyncMethods()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var isActive = true;\n                    var name = \"Product1\";\n\n                    Console.WriteLine(\"Count\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var count = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name)\n                                       .Cacheable(debugInfo2, _serviceProvider)\n                                       .Count();\n                    Assert.AreEqual(false, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(count > 0);\n\n\n                    Console.WriteLine(\"ToList\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var list1 = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name)\n                                       .Cacheable(debugInfo1, _serviceProvider)\n                                       .ToList();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsTrue(list1.Any());\n\n\n                    Console.WriteLine(\"FirstOrDefault\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var product1 = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name)\n                                       .Cacheable(debugInfo3, _serviceProvider)\n                                       .FirstOrDefault();\n                    Assert.AreEqual(false, debugInfo3.IsCacheHit);\n                    Assert.IsTrue(product1 != null);\n\n\n                    Console.WriteLine(\"Any\");\n                    var debugInfo4 = new EFCacheDebugInfo();\n                    var any = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == \"Product2\")\n                                       .Cacheable(debugInfo4, _serviceProvider)\n                                       .Any();\n                    Assert.AreEqual(false, debugInfo4.IsCacheHit);\n                    Assert.IsTrue(any);\n\n\n                    Console.WriteLine(\"Sum\");\n                    var debugInfo5 = new EFCacheDebugInfo();\n                    var sum = context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == \"Product2\")\n                        .Cacheable(debugInfo5, _serviceProvider)\n                        .Sum(x => x.ProductId);\n                    Assert.AreEqual(false, debugInfo5.IsCacheHit);\n                    Assert.IsTrue(sum > 0);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestSecondLevelCacheUsingTwoCountMethods()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var isActive = true;\n                    var name = \"Product3\";\n\n                    Console.WriteLine(\"Count 1\");\n\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var count = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name)\n                                       .Cacheable(debugInfo2, _serviceProvider)\n                                       .Count();\n                    Assert.AreEqual(false, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(count > 0);\n\n                    Console.WriteLine(\"Count 2\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    count = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name)\n                                       .Cacheable(debugInfo3, _serviceProvider)\n                                       .Count();\n                    Assert.AreEqual(true, debugInfo3.IsCacheHit);\n                    Assert.IsTrue(count > 0);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestSecondLevelCacheUsingProjections()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var isActive = true;\n                    var name = \"Product1\";\n\n                    Console.WriteLine(\"Projection 1\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var list2 = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name)\n                                       .Select(x => x.ProductId)\n                                       .Cacheable(debugInfo2, _serviceProvider)\n                                       .ToList();\n                    Assert.AreEqual(false, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(list2.Any());\n\n                    Console.WriteLine(\"Projection 2\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    list2 = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name)\n                                       .Select(x => x.ProductId)\n                                       .Cacheable(debugInfo3, _serviceProvider)\n                                       .ToList();\n                    Assert.AreEqual(true, debugInfo3.IsCacheHit);\n                    Assert.IsTrue(list2.Any());\n                }\n            }\n        }\n\n\n        [TestMethod]\n        public void TestSecondLevelCacheUsingFiltersAfterCacheableMethod()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    Console.WriteLine(\"Filters After Cacheable Method 1.\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var product1 = context.Products\n                                       .Cacheable(debugInfo2, _serviceProvider)\n                                       .FirstOrDefault(product => product.IsActive);\n                    Assert.AreEqual(false, debugInfo2.IsCacheHit);\n                    Assert.IsNotNull(product1);\n\n\n                    Console.WriteLine(\"Filters After Cacheable Method 2, Same query.\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    product1 = context.Products\n                                       .Cacheable(debugInfo3, _serviceProvider)\n                                       .FirstOrDefault(product => product.IsActive);\n                    Assert.AreEqual(true, debugInfo3.IsCacheHit);\n                    Assert.IsNotNull(product1);\n\n\n                    Console.WriteLine(\"Filters After Cacheable Method 3, Different query.\");\n                    var debugInfo4 = new EFCacheDebugInfo();\n                    product1 = context.Products\n                                       .Cacheable(debugInfo4, _serviceProvider)\n                                       .FirstOrDefault(product => !product.IsActive);\n                    Assert.AreEqual(false, debugInfo4.IsCacheHit);\n                    Assert.IsNotNull(product1);\n\n\n                    Console.WriteLine(\"Filters After Cacheable Method 4, Different query.\");\n                    var debugInfo5 = new EFCacheDebugInfo();\n                    product1 = context.Products\n                                       .Cacheable(debugInfo5, _serviceProvider)\n                                       .FirstOrDefault(product => product.ProductName == \"Product2\");\n                    Assert.AreEqual(false, debugInfo5.IsCacheHit);\n                    Assert.IsNotNull(product1);\n\n\n                    Console.WriteLine(\"Filters After Cacheable Method 5, Different query.\");\n                    var debugInfo6 = new EFCacheDebugInfo();\n                    product1 = context.Products\n                                       .Cacheable(debugInfo6, _serviceProvider)\n                                       .FirstOrDefault(product => product.TagProducts.Any(tag => tag.TagId == 1));\n                    Assert.AreEqual(false, debugInfo6.IsCacheHit);\n                    Assert.IsNotNull(product1);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestEagerlyLoadingMultipleLevels()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    Console.WriteLine(\"a normal query\");\n                    var product1IncludeTags = context.Users\n                                                     .Include(x => x.Products)\n                                                     .ThenInclude(x => x.TagProducts)\n                                                     .ThenInclude(x => x.Tag)\n                                                     .FirstOrDefault();\n                    Assert.IsNotNull(product1IncludeTags);\n\n\n                    Console.WriteLine(\"1st query using Include method.\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var firstProductIncludeTags = context.Users\n                                                         .Include(x => x.Products)\n                                                         .ThenInclude(x => x.TagProducts)\n                                                         .ThenInclude(x => x.Tag)\n                                                         .Cacheable(debugInfo1, _serviceProvider)\n                                                         .FirstOrDefault();\n                    Assert.IsNotNull(firstProductIncludeTags);\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    var hash1 = debugInfo1.EFCacheKey.KeyHash;\n                    var cacheDependencies1 = debugInfo1.EFCacheKey.CacheDependencies;\n\n\n                    Console.WriteLine(\"same cached query using Include method.\");\n                    var debugInfo11 = new EFCacheDebugInfo();\n                    var firstProductIncludeTags11 = context.Users\n                                                        .Include(x => x.Products)\n                                                        .ThenInclude(x => x.TagProducts)\n                                                        .ThenInclude(x => x.Tag)\n                                                        .Cacheable(debugInfo11, _serviceProvider)\n                                                        .FirstOrDefault();\n                    Assert.IsNotNull(firstProductIncludeTags11);\n                    Assert.AreEqual(true, debugInfo11.IsCacheHit);\n\n\n                    Console.WriteLine(\n                        @\"2nd query looks the same, but it doesn't have the Include method, so it shouldn't produce the same queryKeyHash.\n                 This was the problem with just parsing the LINQ expression, without considering the produced SQL.\");\n\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var firstProduct = context.Users.Cacheable(debugInfo2, _serviceProvider)\n                                                   .FirstOrDefault();\n                    Assert.IsNotNull(firstProduct);\n                    Assert.AreEqual(false, debugInfo2.IsCacheHit);\n                    var hash2 = debugInfo2.EFCacheKey.KeyHash;\n                    var cacheDependencies2 = debugInfo2.EFCacheKey.CacheDependencies;\n\n                    Assert.AreNotEqual(hash1, hash2);\n                    Assert.AreNotEqual(cacheDependencies1, cacheDependencies2);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestIncludeMethodAndProjectionAffectsKeyCache()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    Console.WriteLine(\"a normal query\");\n                    var product1IncludeTags = context.Products\n                        .Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .Select(x => new { Name = x.ProductName, Tag = x.TagProducts.Select(y => y.Tag) })\n                        .OrderBy(x => x.Name)\n                        .FirstOrDefault();\n                    Assert.IsNotNull(product1IncludeTags);\n                }\n            }\n\n            string hash1;\n            ISet<string> cacheDependencies1;\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    Console.WriteLine(\"1st Cacheable query using Include method, reading from db\");\n\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var firstProductIncludeTags = context.Products\n                        .Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .Select(x => new { Name = x.ProductName, Tag = x.TagProducts.Select(y => y.Tag) })\n                        .OrderBy(x => x.Name)\n                        .Cacheable(debugInfo1, _serviceProvider)\n                        .FirstOrDefault();\n                    Assert.IsNotNull(firstProductIncludeTags);\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    hash1 = debugInfo1.EFCacheKey.KeyHash;\n                    cacheDependencies1 = debugInfo1.EFCacheKey.CacheDependencies;\n                }\n            }\n\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    Console.WriteLine(\"same Cacheable query, reading from 2nd level cache\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var firstProductIncludeTags2 = context.Products\n                        .Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .Select(x => new { Name = x.ProductName, Tag = x.TagProducts.Select(y => y.Tag) })\n                        .OrderBy(x => x.Name)\n                        .Cacheable(debugInfo2, _serviceProvider)\n                        .FirstOrDefault();\n                    Assert.IsNotNull(firstProductIncludeTags2);\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                }\n            }\n\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    Console.WriteLine(\n                        @\"3rd query looks the same, but it doesn't have the Include method, so it shouldn't produce the same queryKeyHash.\n                 This was the problem with just parsing the LINQ expression, without considering the produced SQL.\");\n\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var firstProduct = context.Products\n                        .Select(x => new { Name = x.ProductName, Tag = x.TagProducts.Select(y => y.Tag) })\n                        .OrderBy(x => x.Name)\n                        .Cacheable(debugInfo3, _serviceProvider)\n                        .FirstOrDefault();\n                    Assert.IsNotNull(firstProduct);\n                    Assert.AreEqual(false, debugInfo3.IsCacheHit);\n                    var hash3 = debugInfo3.EFCacheKey.KeyHash;\n                    var cacheDependencies3 = debugInfo3.EFCacheKey.CacheDependencies;\n\n                    Assert.AreNotEqual(hash1, hash3);\n                    Assert.AreNotEqual(cacheDependencies1, cacheDependencies3);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestParallelQueriesShouldCacheData()\n        {\n            var debugInfo1 = new EFCacheDebugInfo();\n            TestsBase.ExecuteInParallel(() =>\n             {\n                 using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n                 {\n                     using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                     {\n                         var firstProductIncludeTags = context.Products\n                             .Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                             .Select(x => new { Name = x.ProductName, Tag = x.TagProducts.Select(y => y.Tag) })\n                             .OrderBy(x => x.Name)\n                             .Cacheable(debugInfo1, _serviceProvider)\n                             .FirstOrDefault();\n                         Assert.IsNotNull(firstProductIncludeTags);\n                     }\n                 }\n             });\n            Assert.AreEqual(true, debugInfo1.IsCacheHit);\n        }\n\n        [TestMethod]\n        public void TestSecondLevelCacheUsingFindMethods()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var debugInfo = new EFCacheDebugInfo();\n                    var product1 = context.Products\n                        .Cacheable(debugInfo, _serviceProvider)\n                        .Find(1);\n                    Assert.AreEqual(false, debugInfo.IsCacheHit);\n                    Assert.IsTrue(product1 != null);\n\n\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    product1 = context.Products\n                        .Cacheable(debugInfo2, _serviceProvider)\n                        .Find(1);\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(product1 != null);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestNullValuesWillUseTheCache()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    Console.WriteLine(\"1st query, reading from db.\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var item1 = context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive && product.ProductName == \"Product1xx\")\n                        .Cacheable(debugInfo1)\n                        .FirstOrDefault();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsNull(item1);\n\n                    Console.WriteLine(\"2nd query, reading from cache.\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var item2 = context.Products\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive && product.ProductName == \"Product1xx\")\n                        .Cacheable(debugInfo2)\n                        .FirstOrDefault();\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                    Assert.IsNull(item2);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestEqualsMethodWillUseTheCache()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    Console.WriteLine(\"1st query, reading from db.\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var item1 = context.Products\n                        .Where(product => product.ProductId == 2 && product.ProductName.Equals(\"Product1\"))\n                        .Cacheable(debugInfo1)\n                        .FirstOrDefault();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsNotNull(item1);\n\n                    Console.WriteLine(\"2nd query, reading from cache.\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var item2 = context.Products\n                        .Where(product => product.ProductId == 2 && product.ProductName.Equals(\"Product1\"))\n                        .Cacheable(debugInfo2)\n                        .FirstOrDefault();\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                    Assert.IsNotNull(item2);\n\n                    Console.WriteLine(\"3rd query, reading from db.\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var item3 = context.Products\n                        .Where(product => product.ProductId == 1 && product.ProductName.Equals(\"Product1\"))\n                        .Cacheable(debugInfo3)\n                        .FirstOrDefault();\n                    Assert.AreEqual(false, debugInfo3.IsCacheHit);\n                    Assert.IsNull(item3);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestSecondLevelCacheDoesNotHitTheDatabaseForIQueryableCacheables()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var isActive = true;\n                    var name = \"Product1\";\n\n                    Console.WriteLine(\"1st query, reading from db.\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var list1IQueryable = context.Products\n                            .OrderBy(product => product.ProductNumber)\n                            .Where(product => product.IsActive == isActive && product.ProductName == name) as IQueryable;\n                    var list1 = (list1IQueryable.Cacheable(debugInfo1, _serviceProvider) as IEnumerable).Cast<object>().ToList();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsTrue(list1.Any());\n\n\n                    Console.WriteLine(\"same query, reading from 2nd level cache.\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var list2IQueryable = context.Products\n                                       .OrderBy(product => product.ProductNumber)\n                                       .Where(product => product.IsActive == isActive && product.ProductName == name) as IQueryable;\n                    var list2 = (list2IQueryable.Cacheable(debugInfo2, _serviceProvider) as IEnumerable).Cast<object>().ToList();\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(list2.Any());\n                }\n            }\n        }\n\n        [TestMethod]\n        public void Test2DifferentCollectionsWillNotUseTheCache()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var collection1 = new[] { 1, 2, 3 };\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var item1 = context.Products\n                        .Where(product => collection1.Contains(product.ProductId))\n                        .Cacheable(debugInfo1)\n                        .FirstOrDefault();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsNotNull(item1);\n\n                    var collection2 = new[] { 1, 2, 3, 4 };\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var item2 = context.Products\n                        .Where(product => collection2.Contains(product.ProductId))\n                        .Cacheable(debugInfo2)\n                        .FirstOrDefault();\n                    Assert.AreEqual(false, debugInfo2.IsCacheHit); // Works with `RelationalQueryModelVisitor`\n                    Assert.IsNotNull(item2);\n                }\n            }\n        }\n\n        [TestMethod]\n        [ExpectedException(typeof(InvalidOperationException))] // This is a bug or a limitation in EF Core\n        public void TestSubqueriesWillUseTheCache()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var item1 = context.Products.Select(product => new\n                    {\n                        prop1 = product.UserId,\n                        prop2 = context.TagProducts.Where(tag => tag.ProductProductId == product.ProductId)\n                               .Cacheable().Select(tag => new\n                               {\n                                   tag.TagId\n                               })\n                    }).FirstOrDefault();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsNotNull(item1);\n\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var item2 = context.Products.Select(product => new\n                    {\n                        prop1 = product.UserId,\n                        prop2 = context.TagProducts.Where(tag => tag.ProductProductId == product.ProductId)\n                           .Cacheable().Select(tag => new\n                           {\n                               tag.TagId\n                           })\n                    }).FirstOrDefault();\n                    Assert.AreEqual(false, debugInfo2.IsCacheHit);\n                    Assert.IsNotNull(item2);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestSecondLevelCacheUsingProjectToMethods()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var mapper = serviceScope.ServiceProvider.GetRequiredService<IMapper>();\n                    var debugInfo = new EFCacheDebugInfo();\n                    var posts = context.Posts\n                        .Where(x => x.Id > 0)\n                        .OrderBy(x => x.Id)\n                        .Cacheable(debugInfo, _serviceProvider)\n                        .ProjectTo<PostDto>(configuration: mapper.ConfigurationProvider)\n                        .ToList();\n                    Assert.AreEqual(false, debugInfo.IsCacheHit);\n                    Assert.IsTrue(posts != null);\n\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    posts = context.Posts\n                        .Where(x => x.Id > 0)\n                        .OrderBy(x => x.Id)\n                        .Cacheable(debugInfo2, _serviceProvider)\n                        .ProjectTo<PostDto>(configuration: mapper.ConfigurationProvider)\n                        .ToList();\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(posts != null);\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.Tests/EFCachedQueryProviderInvalidationTests.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer;\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Entities;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace EFSecondLevelCache.Core.Tests\n{\n    [TestClass]\n    public class EFCachedQueryProviderInvalidationTests\n    {\n        private readonly IServiceProvider _serviceProvider;\n\n        public EFCachedQueryProviderInvalidationTests()\n        {\n            _serviceProvider = TestsBase.GetServiceProvider();\n        }\n\n        [TestInitialize]\n        public void ClearEFGlobalCacheBeforeEachTest()\n        {\n            _serviceProvider.GetRequiredService<IEFCacheServiceProvider>().ClearAllCachedEntries();\n        }\n\n        [TestMethod]\n        public void TestInsertingDataIntoTheSameTableShouldInvalidateTheCacheAutomatically()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var isActive = true;\n                    var name = \"Product2\";\n\n                    Console.WriteLine(\"1st query, reading from db\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var list1 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo1, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsTrue(list1.Any());\n\n\n                    Console.WriteLine(\"same query, reading from 2nd level cache\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var list2 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo2, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(list2.Any());\n\n\n                    Console.WriteLine(\"inserting data, invalidates the cache on SaveChanges\");\n                    var rnd = new Random();\n                    var newProduct = new Product\n                    {\n                        IsActive = false,\n                        ProductName = $\"Product{rnd.Next()}\",\n                        ProductNumber = rnd.Next().ToString(),\n                        Notes = \"Notes ...\",\n                        UserId = 1\n                    };\n                    context.Products.Add(newProduct);\n\n                    context.ChangeTracker.DetectChanges();\n                    var changedEntityNames = context.GetChangedEntityNames();\n                    Assert.IsTrue(debugInfo2.EFCacheKey.CacheDependencies.Any(item => changedEntityNames.Contains(item)));\n\n                    context.SaveChanges();\n\n\n                    Console.WriteLine(\"same query after insert, reading from database.\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var list3 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo3, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(false, debugInfo3.IsCacheHit);\n                    Assert.IsTrue(list3.Any());\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestInsertingDataToOtherTablesShouldNotInvalidateTheCacheDependencyAutomatically()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var isActive = true;\n                    var name = \"Product3\";\n\n                    Console.WriteLine(\"1st query, reading from db (it dependes on/includes the Tags table)\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var list1 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo1, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsTrue(list1.Any());\n\n\n                    Console.WriteLine(\"same query, reading from 2nd level cache.\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var list2 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo2, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(list2.Any());\n\n\n                    Console.WriteLine(\n                        \"inserting data into a *non-related* table, shouldn't invalidate the cache on SaveChanges.\");\n                    var rnd = new Random();\n                    var user = new User\n                    {\n                        Name = $\"User {rnd.Next()}\"\n                    };\n                    context.Users.Add(user);\n\n                    context.ChangeTracker.DetectChanges();\n                    var changedEntityNames = context.GetChangedEntityNames();\n                    Assert.IsFalse(debugInfo2.EFCacheKey.CacheDependencies.Any(item => changedEntityNames.Contains(item)));\n\n                    context.SaveChanges();\n\n\n                    Console.WriteLine(\"same query after insert, reading from 2nd level cache.\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var list3 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo3, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(true, debugInfo3.IsCacheHit);\n                    Assert.IsTrue(list3.Any());\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestInsertingDataToRelatedTablesShouldInvalidateTheCacheDependencyAutomatically()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var isActive = true;\n                    var name = \"Product1\";\n\n                    Console.WriteLine(\"1st query, reading from db (it dependes on/includes the Tags table).\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var list1 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo1, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsTrue(list1.Any());\n\n\n                    Console.WriteLine(\"same query, reading from 2nd level cache\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var list2 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo2, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(list2.Any());\n\n\n                    Console.WriteLine(\"inserting data into a *related* table, invalidates the cache on SaveChanges.\");\n                    var rnd = new Random();\n                    var tag = new Tag\n                    {\n                        Name = $\"Tag {rnd.Next()}\"\n                    };\n                    context.Tags.Add(tag);\n\n                    context.ChangeTracker.DetectChanges();\n                    var changedEntityNames = context.GetChangedEntityNames();\n                    Assert.IsTrue(debugInfo2.EFCacheKey.CacheDependencies.Any(item => changedEntityNames.Contains(item)));\n\n                    context.SaveChanges();\n\n\n                    Console.WriteLine(\n                        \"same query after insert, reading from database (it dependes on/includes the Tags table)\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var list3 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo3, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(false, debugInfo3.IsCacheHit);\n                    Assert.IsTrue(list3.Any());\n                }\n            }\n        }\n\n        [TestMethod]\n        [Ignore(\"This doesn't work with `EntityFrameworkInMemoryDatabase`. Because it doesn't support constraints.\")]\n        public void TestTransactionRollbackShouldNotInvalidateTheCacheDependencyAutomatically()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var isActive = true;\n                    var name = \"Product1\";\n\n                    Console.WriteLine(\"1st query, reading from db.\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var list1 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo1, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsTrue(list1.Any());\n\n\n                    Console.WriteLine(\"same query, reading from 2nd level cache.\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var list2 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo2, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(list2.Any());\n\n                    Console.WriteLine(\n                        \"inserting data with transaction.Rollback, shouldn't invalidate the cache on SaveChanges.\");\n                    try\n                    {\n                        var rnd = new Random();\n                        var newProduct = new Product\n                        {\n                            IsActive = false,\n                            ProductName = \"Product1\", // It has an `IsUnique` constraint.\n                            ProductNumber = rnd.Next().ToString(),\n                            Notes = \"Notes ...\",\n                            UserId = 1\n                        };\n                        context.Products.Add(newProduct);\n                        context.SaveChanges(); // it uses a transaction behind the scene.\n                    }\n                    catch (Exception ex)\n                    {\n                        // NOTE: This doesn't work with `EntityFrameworkInMemoryDatabase`. Because it doesn't support constraints.\n                        // ProductName is duplicate here and should throw an exception on save changes\n                        // and rollback the transaction automatically.\n                        Console.WriteLine(ex.ToString());\n                    }\n\n                    Console.WriteLine(\"same query after insert, reading from 2nd level cache.\");\n\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var list3 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo3, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(true, debugInfo3.IsCacheHit);\n                    Assert.IsTrue(list3.Any());\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestRemoveDataShouldInvalidateTheCacheAutomatically()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    var isActive = false;\n                    var name = \"Product4\";\n\n                    Console.WriteLine(\"1st query, reading from db\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var list1 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo1, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsNotNull(list1);\n\n\n                    Console.WriteLine(\"same query, reading from 2nd level cache\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var list2 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo2, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                    Assert.IsTrue(list2.Any());\n\n\n                    Console.WriteLine(\"removing data, invalidates the cache on SaveChanges\");\n                    var product1 = context.Products.First(product => product.ProductName == name);\n                    context.Products.Remove(product1);\n\n                    context.ChangeTracker.DetectChanges();\n                    var changedEntityNames = context.GetChangedEntityNames();\n                    Assert.IsTrue(debugInfo2.EFCacheKey.CacheDependencies.Any(item => changedEntityNames.Contains(item)));\n\n                    context.SaveChanges();\n\n\n                    Console.WriteLine(\"same query after remove, reading from database.\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var list3 = context.Products.Include(x => x.TagProducts).ThenInclude(x => x.Tag)\n                        .OrderBy(product => product.ProductNumber)\n                        .Where(product => product.IsActive == isActive && product.ProductName == name)\n                        .Cacheable(debugInfo3, _serviceProvider)\n                        .ToList();\n                    Assert.AreEqual(false, debugInfo3.IsCacheHit);\n                    Assert.IsNotNull(list3);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestRemoveTptDataShouldInvalidateTheCacheAutomatically()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    Console.WriteLine(\"1st query, reading from db\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var list1 = context.Posts.OfType<Page>().Cacheable(debugInfo1, _serviceProvider).ToList();\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.AreEqual(2, list1.Count);\n\n\n                    Console.WriteLine(\"same query, reading from 2nd level cache\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    var list2 = context.Posts.OfType<Page>().Cacheable(debugInfo2, _serviceProvider).ToList();\n                    Assert.AreEqual(true, debugInfo2.IsCacheHit);\n                    Assert.AreEqual(2, list2.Count);\n\n\n                    Console.WriteLine(\"removing data, invalidates the cache on SaveChanges\");\n                    var post1 = context.Posts.First(post => post.Title == \"Post1\");\n                    context.Posts.Remove(post1);\n\n                    context.ChangeTracker.DetectChanges();\n                    var changedEntityNames = context.GetChangedEntityNames();\n                    Assert.IsTrue(debugInfo2.EFCacheKey.CacheDependencies.Any(item => changedEntityNames.Contains(item)));\n\n                    context.SaveChanges();\n\n\n                    Console.WriteLine(\"same query after remove, reading from database.\");\n                    var debugInfo3 = new EFCacheDebugInfo();\n                    var list3 = context.Posts.OfType<Page>().Cacheable(debugInfo3, _serviceProvider).ToList();\n                    Assert.AreEqual(false, debugInfo3.IsCacheHit);\n                    Assert.AreEqual(1, list3.Count);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void TestAddThenRemoveDataShouldInvalidateTheCacheAutomatically()\n        {\n            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())\n            {\n                using (var context = serviceScope.ServiceProvider.GetRequiredService<SampleContext>())\n                {\n                    User user1;\n                    const string user1Name = \"User1\";\n                    if (!context.Users.Any(user => user.Name == user1Name))\n                    {\n                        user1 = new User { Name = user1Name };\n                        user1 = context.Users.Add(user1).Entity;\n                    }\n                    else\n                    {\n                        user1 = context.Users.First(user => user.Name == user1Name);\n                    }\n\n                    var product = new Product\n                    {\n                        ProductName = \"P98\",\n                        IsActive = true,\n                        Notes = \"Notes ...\",\n                        ProductNumber = \"098\",\n                        User = user1\n                    };\n\n                    context.Products.Add(product);\n                    context.SaveChanges();\n\n                    Console.WriteLine(\"1st query, reading from db\");\n                    var debugInfo1 = new EFCacheDebugInfo();\n                    var p98 = context.Products\n                                     .Cacheable(debugInfo1, _serviceProvider)\n                                     .FirstOrDefault(p => p.ProductId == product.ProductId);\n                    Assert.AreEqual(false, debugInfo1.IsCacheHit);\n                    Assert.IsNotNull(p98);\n\n                    var debugInfoWithWhere1 = new EFCacheDebugInfo();\n                    var firstQueryWithWhereClauseResult = context.Products.Where(p => p.ProductId == product.ProductId)\n                                    .Cacheable(debugInfoWithWhere1)\n                                    .FirstOrDefault();\n                    Assert.AreEqual(false, debugInfoWithWhere1.IsCacheHit);\n                    Assert.IsNotNull(firstQueryWithWhereClauseResult);\n\n                    Console.WriteLine(\"Delete it from db, invalidates the cache on SaveChanges\");\n                    context.Products.Remove(product);\n                    context.SaveChanges();\n\n                    Console.WriteLine(\"same query, reading from 2nd level cache?\");\n                    var debugInfo2 = new EFCacheDebugInfo();\n                    p98 = context.Products\n                                 .Cacheable(debugInfo2, _serviceProvider)\n                                 .FirstOrDefault(p => p.ProductId == product.ProductId);\n                    Assert.AreEqual(false, debugInfo2.IsCacheHit);\n                    Assert.IsNull(p98);\n\n                    var debugInfoWithWhere2 = new EFCacheDebugInfo();\n                    var firstQueryWithWhereClauseResult2 = context.Products.Where(p => p.ProductId == product.ProductId)\n                                    .Cacheable(debugInfoWithWhere2)\n                                    .FirstOrDefault();\n                    Assert.AreEqual(false, debugInfoWithWhere2.IsCacheHit);\n                    Assert.IsNull(firstQueryWithWhereClauseResult2);\n\n                    Console.WriteLine(\"retrieving it directly from database\");\n                    p98 = context.Products\n                                 .FirstOrDefault(p => p.ProductId == product.ProductId);\n                    Assert.IsNull(p98);\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.Tests/EFSecondLevelCache.Core.Tests.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <AssemblyName>EFSecondLevelCache.Core.Tests</AssemblyName>\n    <PackageId>EFSecondLevelCache.Core.Tests</PackageId>\n    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>\n    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>\n    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>\n    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>\n    <IsPackable>false</IsPackable>\n  </PropertyGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\..\\EFSecondLevelCache.Core\\EFSecondLevelCache.Core.csproj\" />\n    <ProjectReference Include=\"..\\EFSecondLevelCache.Core.AspNetCoreSample\\EFSecondLevelCache.Core.AspNetCoreSample.csproj\" />\n  </ItemGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.InMemory\" Version=\"3.1.0\" />\n    <PackageReference Include=\"CacheManager.StackExchange.Redis\" Version=\"1.2.0\" />\n    <PackageReference Include=\"CacheManager.Core\" Version=\"1.2.0\" />\n    <PackageReference Include=\"CacheManager.Microsoft.Extensions.Caching.Memory\" Version=\"1.2.0\" />\n    <PackageReference Include=\"CacheManager.Serialization.Json\" Version=\"1.2.0\" />\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"16.4.0\" />\n    <PackageReference Include=\"MSTest.TestFramework\" Version=\"2.0.0\" />\n    <PackageReference Include=\"MSTest.TestAdapter\" Version=\"2.0.0\" />\n    <PackageReference Include=\"AutoMapper.Extensions.Microsoft.DependencyInjection\" Version=\"7.0.0\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Service Include=\"{82a7f48d-3b50-4b1e-b82e-3ada8210c358}\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.Tests/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled through the following\n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"EFSecondLevelCache.Core.Tests\")]\n[assembly: AssemblyTrademark(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible\n// to COM components.  If you need to access a type in this assembly from\n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n// The following GUID is for the ID of the typelib if this project is exposed to COM\n[assembly: Guid(\"5475d985-85d6-4dd2-899e-8e8b333b0070\")]\n"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.Tests/TestsBase.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Reflection;\nusing System.Threading.Tasks;\nusing AutoMapper;\nusing CacheManager.Core;\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer;\nusing EFSecondLevelCache.Core.AspNetCoreSample.DataLayer.Utils;\nusing EFSecondLevelCache.Core.AspNetCoreSample.Profiles;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Newtonsoft.Json;\n\n//[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)] // Workers: The number of threads to run the tests. Set it to 0 to use the number of core of your computer.\nnamespace EFSecondLevelCache.Core.Tests\n{\n    public static class TestsBase\n    {\n        public static IEFCacheServiceProvider GetInMemoryCacheServiceProvider()\n        {\n            var services = new ServiceCollection();\n            services.AddEFSecondLevelCache();\n\n            addInMemoryCacheServiceProvider(services);\n\n            var serviceProvider = services.BuildServiceProvider();\n            return serviceProvider.GetRequiredService<IEFCacheServiceProvider>();\n        }\n\n        public static IEFCacheServiceProvider GetRedisCacheServiceProvider()\n        {\n            var services = new ServiceCollection();\n            services.AddEFSecondLevelCache();\n\n            addRedisCacheServiceProvider(services);\n\n            var serviceProvider = services.BuildServiceProvider();\n            return serviceProvider.GetRequiredService<IEFCacheServiceProvider>();\n        }\n\n        public static IServiceProvider GetServiceProvider()\n        {\n            var services = new ServiceCollection();\n\n            services.AddSingleton<IConfigurationRoot>(provider =>\n            {\n                return new Microsoft.Extensions.Configuration.ConfigurationBuilder()\n                                .AddInMemoryCollection(new[]\n                                {\n                                    new KeyValuePair<string,string>(\"UseInMemoryDatabase\", \"true\"),\n                                })\n                                .Build();\n            });\n\n            services.AddEntityFrameworkInMemoryDatabase().AddDbContext<SampleContext>(optionsBuilder =>\n            {\n                optionsBuilder.UseInMemoryDatabase(\"TestDb\");\n            });\n\n            services.AddAutoMapper(typeof(PostProfile).GetTypeInfo().Assembly);\n\n            services.AddEFSecondLevelCache();\n\n            addInMemoryCacheServiceProvider(services);\n            //addRedisCacheServiceProvider(services);\n\n            var serviceProvider = services.BuildServiceProvider();\n            var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>();\n            serviceScope.SeedData();\n\n            return serviceProvider;\n        }\n\n        public static void ExecuteInParallel(Action test, int count = 40)\n        {\n            var tests = new Action[count];\n            for (var i = 0; i < count; i++)\n            {\n                tests[i] = test;\n            }\n            Parallel.Invoke(tests);\n        }\n\n        private static void addInMemoryCacheServiceProvider(IServiceCollection services)\n        {\n            services.AddSingleton(typeof(ICacheManagerConfiguration),\n                new CacheManager.Core.ConfigurationBuilder()\n                    .WithJsonSerializer()\n                    .WithMicrosoftMemoryCacheHandle(instanceName: \"MemoryCache1\")\n                    .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))\n                    .DisablePerformanceCounters()\n                    .DisableStatistics()\n                    .Build());\n            services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));\n        }\n\n        private static void addRedisCacheServiceProvider(IServiceCollection services)\n        {\n            var jss = new JsonSerializerSettings\n            {\n                NullValueHandling = NullValueHandling.Ignore,\n                ReferenceLoopHandling = ReferenceLoopHandling.Ignore\n            };\n\n            const string redisConfigurationKey = \"redis\";\n            services.AddSingleton(typeof(ICacheManagerConfiguration),\n                new CacheManager.Core.ConfigurationBuilder()\n                    .WithJsonSerializer(serializationSettings: jss, deserializationSettings: jss)\n                    .WithUpdateMode(CacheUpdateMode.Up)\n                    .WithRedisConfiguration(redisConfigurationKey, config =>\n                    {\n                        config.WithAllowAdmin()\n                            .WithDatabase(0)\n                            .WithEndpoint(\"localhost\", 6379)\n                            // Enables keyspace notifications to react on eviction/expiration of items.\n                            // Make sure that all servers are configured correctly and 'notify-keyspace-events' is at least set to 'Exe', otherwise CacheManager will not retrieve any events.\n                            // See https://redis.io/topics/notifications#configuration for configuration details.\n                            .EnableKeyspaceEvents();\n                    })\n                    .WithMaxRetries(100)\n                    .WithRetryTimeout(50)\n                    .WithRedisCacheHandle(redisConfigurationKey)\n                    .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))\n                    .DisablePerformanceCounters()\n                    .DisableStatistics()\n                    .Build());\n            services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.Tests/XxHashTests.cs",
    "content": "﻿using Microsoft.VisualStudio.TestTools.UnitTesting;\n\nnamespace EFSecondLevelCache.Core.Tests\n{\n    internal sealed class TestConstants\n    {\n        public static readonly string Empty = \"\";\n        public static readonly string FooBar = \"foobar\";\n        public static readonly string LoremIpsum = \"Lorem ipsum dolor sit amet, consectetur adipiscing elit.  Ut ornare aliquam mauris, at volutpat massa.  Phasellus pulvinar purus eu venenatis commodo.\";\n    }\n\n    [TestClass]\n    public class XxHashTests\n    {\n        [TestMethod]\n        public void TestEmptyXxHashReturnsCorrectValue()\n        {\n            var hash = XxHashUnsafe.ComputeHash(TestConstants.Empty);\n            Assert.AreEqual((uint)0x02cc5d05, hash);\n        }\n\n        [TestMethod]\n        public void TestFooBarXxHashReturnsCorrectValue()\n        {\n            var hash = XxHashUnsafe.ComputeHash(TestConstants.FooBar);\n            Assert.AreEqual((uint)2348340516, hash);\n        }\n\n        [TestMethod]\n        public void TestLoremIpsumXxHashReturnsCorrectValue()\n        {\n            var hash = XxHashUnsafe.ComputeHash(TestConstants.LoremIpsum);\n            Assert.AreEqual((uint)4046722717, hash);\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.Tests/_0-restore.bat",
    "content": "rmdir /S /Q bin\nrmdir /S /Q obj\ndotnet restore\npause"
  },
  {
    "path": "src/Tests/EFSecondLevelCache.Core.Tests/_1-dotnet_test.bat",
    "content": "dotnet test\npause"
  },
  {
    "path": "src/Tests/Issues/Issue15/ConfigureServices.cs",
    "content": "using System;\nusing System.Threading;\nusing CacheManager.Core;\nusing EFSecondLevelCache.Core;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace Issue15\n{\n    public static class ConfigureServices\n    {\n        private static readonly Lazy<IServiceProvider> _serviceProviderBuilder =\n            new Lazy<IServiceProvider>(getServiceProvider, LazyThreadSafetyMode.ExecutionAndPublication);\n\n        /// <summary>\n        /// A lazy loaded thread-safe singleton\n        /// </summary>\n        public static IServiceProvider Instance { get; } = _serviceProviderBuilder.Value;\n\n        public static IEFCacheServiceProvider GetEFCacheServiceProvider()\n        {\n            return Instance.GetRequiredService<IEFCacheServiceProvider>();\n        }\n\n        private static IServiceProvider getServiceProvider()\n        {\n            var services = new ServiceCollection();\n            services.AddEFSecondLevelCache();\n\n            services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));\n            services.AddSingleton(typeof(ICacheManagerConfiguration),\n                new CacheManager.Core.ConfigurationBuilder()\n                        .WithJsonSerializer()\n                        .WithMicrosoftMemoryCacheHandle(instanceName: \"MemoryCache1\")\n                        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))\n                        .DisablePerformanceCounters()\n                        .DisableStatistics()\n                        .Build());\n\n            var serviceProvider = services.BuildServiceProvider();\n            return serviceProvider;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Tests/Issues/Issue15/Issue15.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <OutputType>Exe</OutputType>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <LangVersion>latest</LangVersion>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Design\" Version=\"3.1.0\" PrivateAssets=\"all\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Tools\" Version=\"3.1.0\" PrivateAssets=\"all\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.InMemory\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Configuration.Abstractions\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Configuration\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Configuration.Binder\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Configuration.Json\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Logging.Console\" Version=\"3.1.0\" />\n    <PackageReference Include=\"CacheManager.Core\" Version=\"1.2.0\" />\n    <PackageReference Include=\"CacheManager.Microsoft.Extensions.Caching.Memory\" Version=\"1.2.0\" />\n    <PackageReference Include=\"CacheManager.Serialization.Json\" Version=\"1.2.0\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\..\\..\\EFSecondLevelCache.Core\\EFSecondLevelCache.Core.csproj\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "src/Tests/Issues/Issue15/Payment.cs",
    "content": "namespace Issue15\n{\n    public class Payment\n    {\n        public int Id { set; get; }\n        public int IdPaymentType { set; get; }\n        public string Language { set; get; }\n    }\n}\n"
  },
  {
    "path": "src/Tests/Issues/Issue15/Program.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing EFSecondLevelCache.Core;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace Issue15\n{\n    class Program\n    {\n        static async Task Main(string[] args)\n        {\n            SetupDatabase();\n\n            var serviceProvider = ConfigureServices.Instance;\n\n            using (var context = new SampleContext())\n            {\n\n                Console.WriteLine($\"1,en\");\n                /*\n                SELECT TOP(2) [u].[Id], [u].[IdPaymentType], [u].[Language]\n                FROM [Payments] AS [u]\n                WHERE ([u].[IdPaymentType] = 1) AND ([u].[Language] = N'en')\n                */\n                var item = await context.Payments\n                  .Where(u => u.IdPaymentType == 1 && u.Language.Equals(\"en\"))\n                  .Cacheable(serviceProvider)\n                  .SingleOrDefaultAsync();\n                Console.WriteLine($\"1,en -> item.Id= {item.Id}\"); // --> 1\n\n                Console.WriteLine($\"{Environment.NewLine}2,en\");\n                /*\n                SELECT TOP(2) [u].[Id], [u].[IdPaymentType], [u].[Language]\n                FROM [Payments] AS [u]\n                WHERE ([u].[IdPaymentType] = 2) AND ([u].[Language] = N'en')\n                */\n                item = await context.Payments\n                  .Where(u => u.IdPaymentType == 2 && u.Language.Equals(\"en\"))\n                  .Cacheable(serviceProvider)\n                  .SingleOrDefaultAsync();\n                Console.WriteLine($\"2,en -> item.Id= {item.Id}\"); // --> 3\n\n                Console.WriteLine($\"{Environment.NewLine}3,en\");\n                /*\n                SELECT TOP(2) [u].[Id], [u].[IdPaymentType], [u].[Language]\n                FROM [Payments] AS [u]\n                WHERE ([u].[IdPaymentType] = 3) AND ([u].[Language] = N'en')\n                */\n                item = await context.Payments\n                  .Where(u => u.IdPaymentType == 3 && u.Language.Equals(\"en\"))\n                  .Cacheable(serviceProvider)\n                  .SingleOrDefaultAsync();\n                Console.WriteLine($\"3,en -> item.Id= {item.Id}\");  // --> 5\n\n                Console.WriteLine($\"{Environment.NewLine}3,pl\");\n                /*\n                SELECT [u].[Id], [u].[IdPaymentType], [u].[Language]\n                FROM [Payments] AS [u]\n                WHERE ([u].[IdPaymentType] = 3) AND ([u].[Language] = N'pl')\n                */\n                item = await context.Payments\n                                  .Where(u => u.IdPaymentType == 3 && u.Language.Equals(\"pl\"))\n                                  .Cacheable(serviceProvider)\n                                  .SingleOrDefaultAsync();\n                Console.WriteLine($\"3,pl -> item.Id= {item.Id}\");  // --> 6\n\n                Console.WriteLine($\"{Environment.NewLine}2,pl\");\n                /*\n                SELECT TOP(2) [u].[Id], [u].[IdPaymentType], [u].[Language]\n                FROM [Payments] AS [u]\n                WHERE ([u].[IdPaymentType] = 2) AND ([u].[Language] = N'pl')\n                */\n                item = await context.Payments\n                  .Where(u => u.IdPaymentType == 2 && u.Language.Equals(\"pl\"))\n                  .Cacheable(serviceProvider)\n                  .SingleOrDefaultAsync();\n                Console.WriteLine($\"2,pl -> item.Id= {item.Id}\");  // --> 4\n\n                Console.WriteLine($\"{Environment.NewLine}1,pl\");\n                /*\n                SELECT [u].[Id], [u].[IdPaymentType], [u].[Language]\n                FROM [Payments] AS [u]\n                WHERE ([u].[IdPaymentType] = 1) AND ([u].[Language] = N'pl')\n                */\n                item = await context.Payments\n                  .Where(u => u.IdPaymentType == 1 && u.Language.Equals(\"pl\"))\n                  .Cacheable(serviceProvider)\n                  .SingleOrDefaultAsync();\n                Console.WriteLine($\"1,pl -> item.Id= {item.Id}\");  // --> 2\n            }\n\n            Console.WriteLine(\"Press a key ...\");\n            Console.ReadKey();\n        }\n\n        private static void SetupDatabase()\n        {\n            using (var db = new SampleContext())\n            {\n                if (db.Database.EnsureCreated())\n                {\n                    var item1 = new Payment { IdPaymentType = 1, Language = \"en\" };\n                    db.Payments.Add(item1);\n\n                    var item2 = new Payment { IdPaymentType = 1, Language = \"pl\" };\n                    db.Payments.Add(item2);\n\n                    var item3 = new Payment { IdPaymentType = 2, Language = \"en\" };\n                    db.Payments.Add(item3);\n\n                    var item4 = new Payment { IdPaymentType = 2, Language = \"pl\" };\n                    db.Payments.Add(item4);\n\n                    var item5 = new Payment { IdPaymentType = 3, Language = \"en\" };\n                    db.Payments.Add(item5);\n\n                    var item6 = new Payment { IdPaymentType = 3, Language = \"pl\" };\n                    db.Payments.Add(item6);\n\n                    db.SaveChanges();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Tests/Issues/Issue15/SampleContext.cs",
    "content": "using System.IO;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing EFSecondLevelCache.Core;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace Issue15\n{\n    public class SampleContext : DbContext\n    {\n        private static readonly IEFCacheServiceProvider _efCacheServiceProvider =\n                ConfigureServices.GetEFCacheServiceProvider();\n\n        public DbSet<Payment> Payments { get; set; }\n\n        public SampleContext()\n        { }\n\n        public SampleContext(DbContextOptions options)\n            : base(options)\n        { }\n\n        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)\n        {\n            optionsBuilder.UseSqlServer(\n                @\"Data Source=(LocalDB)\\MSSQLLocalDB;Initial Catalog=EFSecondLevelCache.Issue15;AttachDbFilename=|DataDirectory|\\EFSecondLevelCache.Issue15.mdf;Integrated Security=True;MultipleActiveResultSets=True;\"\n                .Replace(\"|DataDirectory|\", Path.Combine(Directory.GetCurrentDirectory(), \"app_data\")));\n        }\n\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            modelBuilder.Entity<Payment>(entity =>\n                       {\n                           entity.HasIndex(e => new { e.IdPaymentType, e.Language }).IsUnique();\n                       });\n        }\n\n        public override int SaveChanges()\n        {\n            this.ChangeTracker.DetectChanges();\n            var changedEntityNames = this.GetChangedEntityNames();\n\n            this.ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again.\n            var result = base.SaveChanges();\n            this.ChangeTracker.AutoDetectChangesEnabled = true;\n\n\n            _efCacheServiceProvider.InvalidateCacheDependencies(changedEntityNames);\n\n            return result;\n        }\n\n        public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())\n        {\n            this.ChangeTracker.DetectChanges();\n            var changedEntityNames = this.GetChangedEntityNames();\n\n            this.ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again.\n            var result = base.SaveChangesAsync(cancellationToken);\n            this.ChangeTracker.AutoDetectChangesEnabled = true;\n\n            _efCacheServiceProvider.InvalidateCacheDependencies(changedEntityNames);\n\n            return result;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Tests/Issues/Issue15/app_data/.git.keep",
    "content": ""
  },
  {
    "path": "src/Tests/Issues/Issue43/Issue43.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <OutputType>Exe</OutputType>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Design\" Version=\"3.1.0\" PrivateAssets=\"all\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Tools\" Version=\"3.1.0\" PrivateAssets=\"all\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.InMemory\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Configuration.Abstractions\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Configuration\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Configuration.Binder\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Configuration.Json\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Logging.Console\" Version=\"3.1.0\" />\n    <PackageReference Include=\"CacheManager.Core\" Version=\"1.2.0\" />\n    <PackageReference Include=\"CacheManager.Microsoft.Extensions.Caching.Memory\" Version=\"1.2.0\" />\n    <PackageReference Include=\"CacheManager.Serialization.Json\" Version=\"1.2.0\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\..\\..\\EFSecondLevelCache.Core\\EFSecondLevelCache.Core.csproj\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "src/Tests/Issues/Issue43/Program.cs",
    "content": "﻿using System.IO;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing EFSecondLevelCache.Core;\nusing EFSecondLevelCache.Core.Contracts;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Logging;\nusing System;\nusing System.Linq;\nusing CacheManager.Core;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace Issue43\n{\n    public static class ConfigureServices\n    {\n        private static readonly Lazy<IServiceProvider> _serviceProviderBuilder =\n            new Lazy<IServiceProvider>(getServiceProvider, LazyThreadSafetyMode.ExecutionAndPublication);\n\n        /// <summary>\n        /// A lazy loaded thread-safe singleton\n        /// </summary>\n        public static IServiceProvider Instance { get; } = _serviceProviderBuilder.Value;\n\n        public static IEFCacheServiceProvider GetEFCacheServiceProvider()\n        {\n            return Instance.GetRequiredService<IEFCacheServiceProvider>();\n        }\n\n        private static IServiceProvider getServiceProvider()\n        {\n            var services = new ServiceCollection();\n            services.AddEFSecondLevelCache();\n\n            services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));\n            services.AddSingleton(typeof(ICacheManagerConfiguration),\n                new CacheManager.Core.ConfigurationBuilder()\n                    .WithJsonSerializer()\n                    .WithMicrosoftMemoryCacheHandle(instanceName: \"MemoryCache1\")\n                    .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))\n                    .DisablePerformanceCounters()\n                    .DisableStatistics()\n                    .Build());\n\n            return services.BuildServiceProvider();\n        }\n    }\n\n    public class User\n    {\n        public int Id { set; get; }\n        public string Name { set; get; }\n    }\n\n    public class SampleContext : DbContext\n    {\n        private static readonly IEFCacheServiceProvider _efCacheServiceProvider =\n            ConfigureServices.GetEFCacheServiceProvider();\n\n        public DbSet<User> Users { get; set; }\n\n        public SampleContext()\n        {\n        }\n\n        public SampleContext(DbContextOptions options)\n            : base(options)\n        {\n        }\n\n        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)\n        {\n            string connectionString = @\"Data Source=(LocalDB)\\MSSQLLocalDB;Initial Catalog=EFSecondLevelCache.Issue43;AttachDbFilename=|DataDirectory|\\EFSecondLevelCache.Issue43.mdf;Integrated Security=True;MultipleActiveResultSets=True;\"\n                    .Replace(\"|DataDirectory|\", Path.Combine(Directory.GetCurrentDirectory(), \"app_data\"));\n            optionsBuilder.UseSqlServer(connectionString);\n        }\n\n        public override int SaveChanges()\n        {\n            this.ChangeTracker.DetectChanges();\n            var changedEntityNames = this.GetChangedEntityNames();\n\n            this.ChangeTracker.AutoDetectChangesEnabled =\n                false; // for performance reasons, to avoid calling DetectChanges() again.\n            var result = base.SaveChanges();\n            this.ChangeTracker.AutoDetectChangesEnabled = true;\n\n            _efCacheServiceProvider.InvalidateCacheDependencies(changedEntityNames);\n\n            return result;\n        }\n\n        public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())\n        {\n            this.ChangeTracker.DetectChanges();\n            var changedEntityNames = this.GetChangedEntityNames();\n\n            this.ChangeTracker.AutoDetectChangesEnabled =\n                false; // for performance reasons, to avoid calling DetectChanges() again.\n            var result = base.SaveChangesAsync(cancellationToken);\n            this.ChangeTracker.AutoDetectChangesEnabled = true;\n\n            _efCacheServiceProvider.InvalidateCacheDependencies(changedEntityNames);\n\n            return result;\n        }\n    }\n\n    class Program\n    {\n        static async Task Main(string[] args)\n        {\n            SetupDatabase();\n\n            var serviceProvider = ConfigureServices.Instance;\n\n            using (var context = new SampleContext())\n            {\n                var debugInfo = new EFCacheDebugInfo();\n                var item = await context.Users.Where(x => x.Name == \"user-1\").Cacheable(debugInfo, serviceProvider).FirstOrDefaultAsync();\n                Console.WriteLine($\"FirstOrDefaultAsync->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{item.Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                item = await context.Users.Where(x => x.Name == \"user-1\").Cacheable(debugInfo, serviceProvider).FirstOrDefaultAsync();\n                Console.WriteLine($\"FirstOrDefaultAsync->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{item.Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                item = await context.Users.Where(x => x.Name == \"user-11\").Cacheable(debugInfo, serviceProvider).FirstOrDefaultAsync();\n                Console.WriteLine($\"FirstOrDefaultAsync->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{item?.Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                item = await context.Users.Where(x => x.Name == \"user-11\").Cacheable(debugInfo, serviceProvider).FirstOrDefaultAsync();\n                Console.WriteLine($\"FirstOrDefaultAsync->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{item?.Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                item = context.Users.Cacheable(debugInfo, serviceProvider).Find(1);\n                Console.WriteLine($\"Find->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{item?.Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                item = context.Users.Cacheable(debugInfo, serviceProvider).Find(1);\n                Console.WriteLine($\"Find->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{item?.Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                item = await context.Users.Cacheable(debugInfo, serviceProvider).FindAsync(1);\n                Console.WriteLine($\"FindAsync->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{item?.Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                item = await context.Users.Cacheable(debugInfo, serviceProvider).FindAsync(1);\n                Console.WriteLine($\"FindAsync->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{item?.Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                var items = await context.Users.Where(x => x.Name == \"user-1\").Cacheable(debugInfo, serviceProvider).ToListAsync();\n                Console.WriteLine($\"ToListAsync->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{items[0].Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                items = await context.Users.Where(x => x.Name == \"user-1\").Cacheable(debugInfo, serviceProvider).ToListAsync();\n                Console.WriteLine($\"ToListAsync->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{items[0].Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                items = context.Users.Where(x => x.Name == \"user-2\").Cacheable(debugInfo, serviceProvider).ToList();\n                Console.WriteLine($\"ToList->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{items[0].Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                items = context.Users.Where(x => x.Name == \"user-2\").Cacheable(debugInfo, serviceProvider).ToList();\n                Console.WriteLine($\"ToList->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{items[0].Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                item = context.Users.Where(x => x.Name == \"user-3\").Cacheable(debugInfo, serviceProvider).FirstOrDefault();\n                Console.WriteLine($\"FirstOrDefault->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{item.Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                item = context.Users.Where(x => x.Name == \"user-3\").Cacheable(debugInfo, serviceProvider).FirstOrDefault();\n                Console.WriteLine($\"FirstOrDefault->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{item.Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                var count = context.Users.Where(x => x.Name == \"user-3\").Cacheable(debugInfo, serviceProvider).Count();\n                Console.WriteLine($\"Count->IsCacheHit: {debugInfo.IsCacheHit}, count:{count}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                count = context.Users.Where(x => x.Name == \"user-3\").Cacheable(debugInfo, serviceProvider).Count();\n                Console.WriteLine($\"Count->IsCacheHit: {debugInfo.IsCacheHit}, count:{count}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                item = await context.Users.Where(x => x.Name == \"user-1\").Cacheable(debugInfo, serviceProvider).SingleOrDefaultAsync();\n                Console.WriteLine($\"SingleOrDefaultAsync->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{item.Name}\");\n\n                debugInfo = new EFCacheDebugInfo();\n                item = await context.Users.Where(x => x.Name == \"user-1\").Cacheable(debugInfo, serviceProvider).SingleOrDefaultAsync();\n                Console.WriteLine($\"SingleOrDefaultAsync->IsCacheHit: {debugInfo.IsCacheHit}, items[0]:{item.Name}\");\n            }\n        }\n\n        private static void SetupDatabase()\n        {\n            using (var db = new SampleContext())\n            {\n                if (db.Database.EnsureCreated())\n                {\n                    var item1 = new User { Name = \"user-1\" };\n                    db.Users.Add(item1);\n\n                    var item2 = new User { Name = \"user-2\" };\n                    db.Users.Add(item2);\n\n                    var item3 = new User { Name = \"user-3\" };\n                    db.Users.Add(item3);\n\n                    db.SaveChanges();\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/Tests/Issues/Issue43/_0-restore.bat",
    "content": "rmdir /S /Q bin\nrmdir /S /Q obj\ndotnet restore\npause"
  },
  {
    "path": "src/Tests/Issues/Issue43/_1-dotnet_run.bat",
    "content": "dotnet watch run"
  },
  {
    "path": "src/Tests/Issues/Issue43/app_data/.git.keep",
    "content": ""
  },
  {
    "path": "tag-it.bat",
    "content": "git tag -a 2.9.0 -m \"Published 2.9.0 to nuget.org\"\ngit push --follow-tags\npause"
  },
  {
    "path": "update-dependencies.bat",
    "content": "dotnet tool update -g dotnet-outdated\ndotnet outdated -u\ndotnet restore\npause"
  }
]