diff --git a/CSInn.sln b/CSInn.sln
index 4506e73..ab87340 100644
--- a/CSInn.sln
+++ b/CSInn.sln
@@ -1,95 +1,102 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.29306.81
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{186B0C3C-02E4-416C-9CD1-067E7662A53E}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{399E33DB-3837-48F0-BFFA-B756D995BD2D}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.UI", "src\CSInn.UI\CSInn.UI.csproj", "{B6E0D216-88C8-4BA1-A71C-7C6BDB4247BB}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Discord.Authentication", "src\CSInn.Discord.Authentication\CSInn.Discord.Authentication.csproj", "{4BA8C0D8-A2CA-4E84-B018-644C2859F5A5}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Application.Tests", "tests\CSInn.Application.Tests\CSInn.Application.Tests.csproj", "{B92834CE-81D2-4E82-86CA-4940CFB8A102}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Domain.Repositories", "src\CSInn.Domain.Repositories\CSInn.Domain.Repositories.csproj", "{C0BCA68B-7CD7-4037-8DEF-3C1DFA0FFA64}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Infrastructure.Repositories", "src\CSInn.Infrastructure.Repositories\CSInn.Infrastructure.Repositories.csproj", "{68360ABC-FFEC-4A14-94EC-DCA6B96C044E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Application", "src\CSInn.Application\CSInn.Application.csproj", "{9FC69081-2A6A-4990-ACC3-4D1A1D08AA73}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Discord.Authentication.Tests", "tests\CSInn.Discord.Authentication.Tests\CSInn.Discord.Authentication.Tests.csproj", "{989E16D6-A255-4AC5-BAE1-3D78B38EE258}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.UI.Tests", "tests\CSInn.UI.Tests\CSInn.UI.Tests.csproj", "{2159A971-3858-48F0-99B4-6F40C4F50863}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Infrastructure.Repositories.Tests", "tests\CSInn.Infrastructure.Repositories.Tests\CSInn.Infrastructure.Repositories.Tests.csproj", "{1B2CDA69-B45A-4F95-B5C9-7AC3F834A7B7}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Models", "src\CSInn.Models\CSInn.Models.csproj", "{A4D369D6-4937-4BB4-8F90-89DBCADE1481}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {B6E0D216-88C8-4BA1-A71C-7C6BDB4247BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B6E0D216-88C8-4BA1-A71C-7C6BDB4247BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B6E0D216-88C8-4BA1-A71C-7C6BDB4247BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B6E0D216-88C8-4BA1-A71C-7C6BDB4247BB}.Release|Any CPU.Build.0 = Release|Any CPU
- {4BA8C0D8-A2CA-4E84-B018-644C2859F5A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4BA8C0D8-A2CA-4E84-B018-644C2859F5A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4BA8C0D8-A2CA-4E84-B018-644C2859F5A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4BA8C0D8-A2CA-4E84-B018-644C2859F5A5}.Release|Any CPU.Build.0 = Release|Any CPU
- {B92834CE-81D2-4E82-86CA-4940CFB8A102}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B92834CE-81D2-4E82-86CA-4940CFB8A102}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B92834CE-81D2-4E82-86CA-4940CFB8A102}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B92834CE-81D2-4E82-86CA-4940CFB8A102}.Release|Any CPU.Build.0 = Release|Any CPU
- {C0BCA68B-7CD7-4037-8DEF-3C1DFA0FFA64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C0BCA68B-7CD7-4037-8DEF-3C1DFA0FFA64}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C0BCA68B-7CD7-4037-8DEF-3C1DFA0FFA64}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C0BCA68B-7CD7-4037-8DEF-3C1DFA0FFA64}.Release|Any CPU.Build.0 = Release|Any CPU
- {68360ABC-FFEC-4A14-94EC-DCA6B96C044E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {68360ABC-FFEC-4A14-94EC-DCA6B96C044E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {68360ABC-FFEC-4A14-94EC-DCA6B96C044E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {68360ABC-FFEC-4A14-94EC-DCA6B96C044E}.Release|Any CPU.Build.0 = Release|Any CPU
- {9FC69081-2A6A-4990-ACC3-4D1A1D08AA73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9FC69081-2A6A-4990-ACC3-4D1A1D08AA73}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9FC69081-2A6A-4990-ACC3-4D1A1D08AA73}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {9FC69081-2A6A-4990-ACC3-4D1A1D08AA73}.Release|Any CPU.Build.0 = Release|Any CPU
- {989E16D6-A255-4AC5-BAE1-3D78B38EE258}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {989E16D6-A255-4AC5-BAE1-3D78B38EE258}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {989E16D6-A255-4AC5-BAE1-3D78B38EE258}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {989E16D6-A255-4AC5-BAE1-3D78B38EE258}.Release|Any CPU.Build.0 = Release|Any CPU
- {2159A971-3858-48F0-99B4-6F40C4F50863}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2159A971-3858-48F0-99B4-6F40C4F50863}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2159A971-3858-48F0-99B4-6F40C4F50863}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2159A971-3858-48F0-99B4-6F40C4F50863}.Release|Any CPU.Build.0 = Release|Any CPU
- {1B2CDA69-B45A-4F95-B5C9-7AC3F834A7B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {1B2CDA69-B45A-4F95-B5C9-7AC3F834A7B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1B2CDA69-B45A-4F95-B5C9-7AC3F834A7B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {1B2CDA69-B45A-4F95-B5C9-7AC3F834A7B7}.Release|Any CPU.Build.0 = Release|Any CPU
- {A4D369D6-4937-4BB4-8F90-89DBCADE1481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A4D369D6-4937-4BB4-8F90-89DBCADE1481}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A4D369D6-4937-4BB4-8F90-89DBCADE1481}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A4D369D6-4937-4BB4-8F90-89DBCADE1481}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {B6E0D216-88C8-4BA1-A71C-7C6BDB4247BB} = {186B0C3C-02E4-416C-9CD1-067E7662A53E}
- {4BA8C0D8-A2CA-4E84-B018-644C2859F5A5} = {186B0C3C-02E4-416C-9CD1-067E7662A53E}
- {B92834CE-81D2-4E82-86CA-4940CFB8A102} = {399E33DB-3837-48F0-BFFA-B756D995BD2D}
- {C0BCA68B-7CD7-4037-8DEF-3C1DFA0FFA64} = {186B0C3C-02E4-416C-9CD1-067E7662A53E}
- {68360ABC-FFEC-4A14-94EC-DCA6B96C044E} = {186B0C3C-02E4-416C-9CD1-067E7662A53E}
- {9FC69081-2A6A-4990-ACC3-4D1A1D08AA73} = {186B0C3C-02E4-416C-9CD1-067E7662A53E}
- {989E16D6-A255-4AC5-BAE1-3D78B38EE258} = {399E33DB-3837-48F0-BFFA-B756D995BD2D}
- {2159A971-3858-48F0-99B4-6F40C4F50863} = {399E33DB-3837-48F0-BFFA-B756D995BD2D}
- {1B2CDA69-B45A-4F95-B5C9-7AC3F834A7B7} = {399E33DB-3837-48F0-BFFA-B756D995BD2D}
- {A4D369D6-4937-4BB4-8F90-89DBCADE1481} = {186B0C3C-02E4-416C-9CD1-067E7662A53E}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {6BBE2057-901B-474F-9140-0D045DF18046}
- EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29306.81
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{186B0C3C-02E4-416C-9CD1-067E7662A53E}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{399E33DB-3837-48F0-BFFA-B756D995BD2D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.UI", "src\CSInn.UI\CSInn.UI.csproj", "{B6E0D216-88C8-4BA1-A71C-7C6BDB4247BB}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Discord.Authentication", "src\CSInn.Discord.Authentication\CSInn.Discord.Authentication.csproj", "{4BA8C0D8-A2CA-4E84-B018-644C2859F5A5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Application.Tests", "tests\CSInn.Application.Tests\CSInn.Application.Tests.csproj", "{B92834CE-81D2-4E82-86CA-4940CFB8A102}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Domain.Repositories", "src\CSInn.Domain.Repositories\CSInn.Domain.Repositories.csproj", "{C0BCA68B-7CD7-4037-8DEF-3C1DFA0FFA64}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Infrastructure.Repositories", "src\CSInn.Infrastructure.Repositories\CSInn.Infrastructure.Repositories.csproj", "{68360ABC-FFEC-4A14-94EC-DCA6B96C044E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Application", "src\CSInn.Application\CSInn.Application.csproj", "{9FC69081-2A6A-4990-ACC3-4D1A1D08AA73}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Discord.Authentication.Tests", "tests\CSInn.Discord.Authentication.Tests\CSInn.Discord.Authentication.Tests.csproj", "{989E16D6-A255-4AC5-BAE1-3D78B38EE258}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.UI.Tests", "tests\CSInn.UI.Tests\CSInn.UI.Tests.csproj", "{2159A971-3858-48F0-99B4-6F40C4F50863}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Infrastructure.Repositories.Tests", "tests\CSInn.Infrastructure.Repositories.Tests\CSInn.Infrastructure.Repositories.Tests.csproj", "{1B2CDA69-B45A-4F95-B5C9-7AC3F834A7B7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSInn.Models", "src\CSInn.Models\CSInn.Models.csproj", "{A4D369D6-4937-4BB4-8F90-89DBCADE1481}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp1", "ConsoleApp1\ConsoleApp1.csproj", "{EBBFB2B3-35F1-4B87-B9D1-7260A3825CDC}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B6E0D216-88C8-4BA1-A71C-7C6BDB4247BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B6E0D216-88C8-4BA1-A71C-7C6BDB4247BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B6E0D216-88C8-4BA1-A71C-7C6BDB4247BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B6E0D216-88C8-4BA1-A71C-7C6BDB4247BB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4BA8C0D8-A2CA-4E84-B018-644C2859F5A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4BA8C0D8-A2CA-4E84-B018-644C2859F5A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4BA8C0D8-A2CA-4E84-B018-644C2859F5A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4BA8C0D8-A2CA-4E84-B018-644C2859F5A5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B92834CE-81D2-4E82-86CA-4940CFB8A102}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B92834CE-81D2-4E82-86CA-4940CFB8A102}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B92834CE-81D2-4E82-86CA-4940CFB8A102}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B92834CE-81D2-4E82-86CA-4940CFB8A102}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C0BCA68B-7CD7-4037-8DEF-3C1DFA0FFA64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C0BCA68B-7CD7-4037-8DEF-3C1DFA0FFA64}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C0BCA68B-7CD7-4037-8DEF-3C1DFA0FFA64}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C0BCA68B-7CD7-4037-8DEF-3C1DFA0FFA64}.Release|Any CPU.Build.0 = Release|Any CPU
+ {68360ABC-FFEC-4A14-94EC-DCA6B96C044E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {68360ABC-FFEC-4A14-94EC-DCA6B96C044E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {68360ABC-FFEC-4A14-94EC-DCA6B96C044E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {68360ABC-FFEC-4A14-94EC-DCA6B96C044E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9FC69081-2A6A-4990-ACC3-4D1A1D08AA73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9FC69081-2A6A-4990-ACC3-4D1A1D08AA73}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9FC69081-2A6A-4990-ACC3-4D1A1D08AA73}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9FC69081-2A6A-4990-ACC3-4D1A1D08AA73}.Release|Any CPU.Build.0 = Release|Any CPU
+ {989E16D6-A255-4AC5-BAE1-3D78B38EE258}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {989E16D6-A255-4AC5-BAE1-3D78B38EE258}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {989E16D6-A255-4AC5-BAE1-3D78B38EE258}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {989E16D6-A255-4AC5-BAE1-3D78B38EE258}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2159A971-3858-48F0-99B4-6F40C4F50863}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2159A971-3858-48F0-99B4-6F40C4F50863}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2159A971-3858-48F0-99B4-6F40C4F50863}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2159A971-3858-48F0-99B4-6F40C4F50863}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1B2CDA69-B45A-4F95-B5C9-7AC3F834A7B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1B2CDA69-B45A-4F95-B5C9-7AC3F834A7B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1B2CDA69-B45A-4F95-B5C9-7AC3F834A7B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1B2CDA69-B45A-4F95-B5C9-7AC3F834A7B7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A4D369D6-4937-4BB4-8F90-89DBCADE1481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A4D369D6-4937-4BB4-8F90-89DBCADE1481}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A4D369D6-4937-4BB4-8F90-89DBCADE1481}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A4D369D6-4937-4BB4-8F90-89DBCADE1481}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EBBFB2B3-35F1-4B87-B9D1-7260A3825CDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EBBFB2B3-35F1-4B87-B9D1-7260A3825CDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EBBFB2B3-35F1-4B87-B9D1-7260A3825CDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EBBFB2B3-35F1-4B87-B9D1-7260A3825CDC}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {B6E0D216-88C8-4BA1-A71C-7C6BDB4247BB} = {186B0C3C-02E4-416C-9CD1-067E7662A53E}
+ {4BA8C0D8-A2CA-4E84-B018-644C2859F5A5} = {186B0C3C-02E4-416C-9CD1-067E7662A53E}
+ {B92834CE-81D2-4E82-86CA-4940CFB8A102} = {399E33DB-3837-48F0-BFFA-B756D995BD2D}
+ {C0BCA68B-7CD7-4037-8DEF-3C1DFA0FFA64} = {186B0C3C-02E4-416C-9CD1-067E7662A53E}
+ {68360ABC-FFEC-4A14-94EC-DCA6B96C044E} = {186B0C3C-02E4-416C-9CD1-067E7662A53E}
+ {9FC69081-2A6A-4990-ACC3-4D1A1D08AA73} = {186B0C3C-02E4-416C-9CD1-067E7662A53E}
+ {989E16D6-A255-4AC5-BAE1-3D78B38EE258} = {399E33DB-3837-48F0-BFFA-B756D995BD2D}
+ {2159A971-3858-48F0-99B4-6F40C4F50863} = {399E33DB-3837-48F0-BFFA-B756D995BD2D}
+ {1B2CDA69-B45A-4F95-B5C9-7AC3F834A7B7} = {399E33DB-3837-48F0-BFFA-B756D995BD2D}
+ {A4D369D6-4937-4BB4-8F90-89DBCADE1481} = {186B0C3C-02E4-416C-9CD1-067E7662A53E}
+ {EBBFB2B3-35F1-4B87-B9D1-7260A3825CDC} = {186B0C3C-02E4-416C-9CD1-067E7662A53E}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {6BBE2057-901B-474F-9140-0D045DF18046}
+ EndGlobalSection
+EndGlobal
diff --git a/ConsoleApp1/ConsoleApp1.csproj b/ConsoleApp1/ConsoleApp1.csproj
new file mode 100644
index 0000000..fb4ca05
--- /dev/null
+++ b/ConsoleApp1/ConsoleApp1.csproj
@@ -0,0 +1,41 @@
+
+
+
+ Exe
+ netcoreapp3.0
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+ true
+ PreserveNewest
+
+
+ PreserveNewest
+ true
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ConsoleApp1/Program - Copy.cs b/ConsoleApp1/Program - Copy.cs
new file mode 100644
index 0000000..c3032dc
--- /dev/null
+++ b/ConsoleApp1/Program - Copy.cs
@@ -0,0 +1,34 @@
+using CSInn.Infrastructure.Repositories.Entities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ConsoleApp1
+{
+ class Program_old
+ {
+ static void Main_old(string[] args)
+ {
+ /*
+ List test_list = new List()
+ {
+ new LessonEntity() { entity_test = "1"},
+ new LessonEntity() { entity_test = "2"},
+ new LessonEntity() { entity_test = "3"},
+ new LessonEntity() { entity_test = "4"},
+ new LessonEntity() { entity_test = "5"},
+ };
+
+
+ IQueryable EntitySet = (from p in test_list select p).AsQueryable();
+
+ var dao = new LessonDao(EntitySet);
+ var dto = new LessonDto(dao);
+
+ var result = dto.findByTest("2");
+ Console.WriteLine($"cout: {result.Count}");
+ Console.WriteLine($"model_test: {result[0].model_test}");
+ */
+ }
+ }
+}
diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs
new file mode 100644
index 0000000..aac2b17
--- /dev/null
+++ b/ConsoleApp1/Program.cs
@@ -0,0 +1,58 @@
+using CSInn.Infrastructure.Repositories.Context;
+using CSInn.Infrastructure.Repositories.Entities;
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using CSInn.Infrastructure.Repositories.UoW;
+using CSInn.Models;
+using Microsoft.Extensions.Configuration;
+using System.Configuration;
+using System.IO;
+
+namespace ConsoleApp1
+{
+ class Program
+ {
+
+ static void Main(string[] args)
+ {
+ var builder = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
+
+ IConfigurationRoot configuration = builder.Build();
+
+ var options = new DbContextOptionsBuilder()
+ .UseSqlServer(configuration.GetConnectionString("CSINN"))
+ .Options;
+
+ // .UseInMemoryDatabase(databaseName: "database_name")
+ // .Options;
+ //
+
+ var context = new CSInnDbContext(options);
+ var dbSet = context.Set();
+
+ if (dbSet.Any())
+ Console.WriteLine("has stuff");
+ else
+ {
+ for (int i = 0; i < 5; ++i)
+ dbSet.Add(new LessonEntity() { entity_test = "" + i });
+
+ context.SaveChanges();
+ }
+
+ IQueryable EntitySet = dbSet;
+ var dao = new LessonDao(EntitySet);
+ var dto = new LessonDto(dao);
+
+ var result = dto.findByTest("2", "tmp");
+
+ Console.WriteLine($"cout: {result.Count}");
+ foreach(var r in result)
+ Console.WriteLine($"model_test: {r.model_test}");
+ }
+ }
+}
diff --git a/ConsoleApp1/allthestuff.cs b/ConsoleApp1/allthestuff.cs
new file mode 100644
index 0000000..0dd038d
--- /dev/null
+++ b/ConsoleApp1/allthestuff.cs
@@ -0,0 +1,238 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using AutoMapper;
+using CSInn.Models;
+using CSInn.Infrastructure.Repositories.Entities;
+
+namespace ConsoleApp1
+{
+ public static class Mapper
+ {
+ #region basic
+ public static readonly Dictionary> classMemberMapping = new Dictionary>();
+ private static IMapper mapper;
+
+ static Mapper()
+ {
+ var config = new AutoMapper.MapperConfiguration(cfg => {
+ map(cfg)
+ .bind(src => src.entity_test, dest => dest.model_test);
+ });
+
+ mapper = config.CreateMapper();
+ }
+
+ private class Mapping
+ {
+ public AutoMapper.IMappingExpression map1;
+ public AutoMapper.IMappingExpression map2;
+
+ private string getMemberName(Expression> ex)
+ {
+ if (ex.Body is MemberExpression)
+ return (ex.Body as MemberExpression).Member.Name;
+ else if (ex.Body is UnaryExpression)
+ return ((ex.Body as UnaryExpression).Operand as MemberExpression).Member.Name;
+ else
+ return null;
+ }
+ public Mapping bind(Expression> left, Expression> right)
+ {
+ string leftMember = getMemberName(left);
+ string rightMember = getMemberName(right);
+ Dictionary memberMapping = classMemberMapping[typeof(TSource).Name];
+ memberMapping[leftMember] = rightMember;
+
+ map2.ForMember(left, opt => opt.MapFrom(right));
+ map1.ForMember(right, opt => opt.MapFrom(left));
+ return this;
+ }
+ }
+
+ private static Mapping map(AutoMapper.IMapperConfigurationExpression cfg)
+ {
+ classMemberMapping[typeof(TSource).Name] = new Dictionary();
+
+ return new Mapping()
+ {
+ map1 = cfg.CreateMap(),
+ map2 = cfg.CreateMap()
+ };
+ }
+ #endregion
+
+
+ public static TModel mapEntityToModel(TEntity entity) where TModel : new()
+ {
+ TModel model = new TModel();
+ mapper.Map(entity, model);
+ return model;
+ }
+
+ public static List mapListEntityToModel(List listEntities) where TModel : new()
+ {
+ List listModel = new List();
+ foreach (TEntity entity in listEntities)
+ {
+ TModel model = mapEntityToModel(entity);
+ listModel.Add(model);
+ }
+ return listModel;
+ }
+
+ public static TEntity mapModelToEntity(TModel model) where TEntity : new()
+ {
+ TEntity entity = new TEntity();
+ mapper.Map(model, entity);
+ return entity;
+ }
+ }
+
+ public static class DaoLambdaExtension
+ {
+ public static string GetCacheKey()
+ {
+ return string.Concat(typeof(TSource).FullName, typeof(TDest).FullName);
+ }
+
+ public static IEnumerable createMemberBinding(ParameterExpression parameterExpression)
+ {
+ string[] SourceExcludedMembers = { "Tags", "Authors" };
+ string[] DestExcludedMembers = { "Tags", "Authors" };
+ var sourceProperties = typeof(TSource).GetProperties().Where(src => !SourceExcludedMembers.Contains(src.Name));
+ var destinationProperties = typeof(TDest).GetProperties().Where(dest => dest.CanWrite & !DestExcludedMembers.Contains(dest.Name));
+ Dictionary memberMapping = Mapper.classMemberMapping[typeof(TSource).Name];
+
+ var bindings = destinationProperties
+ .Select(destinationProperty => BuildBinding(memberMapping, parameterExpression, destinationProperty, sourceProperties))
+ .Where(d => d != null);
+
+ return bindings;
+ }
+
+ private static bool propertiesEqual(Dictionary memberMapping, string dest, string src)
+ {
+ string mapp;
+ if (!memberMapping.TryGetValue(src, out mapp))
+ return false;
+ return dest == mapp;
+ }
+
+ private static MemberAssignment BuildBinding(Dictionary memberMapping,
+ Expression parameterExpression, MemberInfo destinationProperty, IEnumerable sourceProperties)
+ {
+ var sourceProperty = sourceProperties.FirstOrDefault(src => src.Name.ToLower() == destinationProperty.Name.ToLower()
+ || (propertiesEqual(memberMapping, destinationProperty.Name, src.Name)));
+
+ if (sourceProperty == null)
+ return null;
+
+ return Expression.Bind(destinationProperty, Expression.Property(parameterExpression, sourceProperty));
+ }
+ }
+
+
+ public class ProjectionExpression
+ {
+ private readonly IQueryable _source;
+
+ private static readonly Dictionary ExpressionCache = new Dictionary();
+
+ public ProjectionExpression(IQueryable source)
+ {
+ _source = source;
+ }
+
+ public IQueryable To()
+ {
+ var queryExpression = GetCachedExpression() ?? BuildExpression();
+
+ return _source.Select(queryExpression);
+ }
+
+ private static Expression> GetCachedExpression()
+ {
+ var key = DaoLambdaExtension.GetCacheKey();
+
+ return ExpressionCache.ContainsKey(key) ? ExpressionCache[key] as Expression> : null;
+ }
+
+ private static Expression> BuildExpression()
+ {
+ var parameterExpression = Expression.Parameter(typeof(TSource), "src");
+ var bindings = DaoLambdaExtension.createMemberBinding(parameterExpression);
+
+ var expression = Expression.Lambda>(Expression.MemberInit(Expression.New(typeof(TDest)), bindings), parameterExpression);
+
+ var key = DaoLambdaExtension.GetCacheKey();
+ ExpressionCache.Add(key, expression);
+
+ return expression;
+ }
+ }
+
+ public static class ObjectSetExtensions
+ {
+ public static ProjectionExpression Project(
+ this IQueryable source)
+ {
+ return new ProjectionExpression(source);
+ }
+ }
+
+ public abstract class BasicDao
+ {
+ protected string entitySetName;
+ public IQueryable EntitySet;
+ }
+
+ public class LessonDao : BasicDao
+ {
+ public LessonDao(IQueryable EntitySet)
+ {
+ this.EntitySet = EntitySet;
+ }
+ }
+
+
+ public abstract class BasicDto
+ where TEntity : new()
+ where TModel : new()
+ {
+ protected BasicDao dao;
+
+ public BasicDto(BasicDao dao) { this.dao = dao; }
+
+ public IQueryable get()
+ {
+ return dao.EntitySet.Project().To();
+ }
+ }
+
+ public class LessonDto : BasicDto
+ {
+ public LessonDto(BasicDao dao)
+ : base(dao)
+ { }
+
+ public List findByTest(string test, string desc)
+ {
+ var lessonModelList =
+ /*
+ (from p in get()
+ where !p.model_test.Equals(test)
+ select p)
+ */
+ get()
+ .Where(p => !p.model_test.Equals(test))
+ .Where(p => p.Description == desc)
+ .ToList();
+
+
+ return lessonModelList;
+ }
+ }
+}
diff --git a/ConsoleApp1/appsettings.Development.json b/ConsoleApp1/appsettings.Development.json
new file mode 100644
index 0000000..e203e94
--- /dev/null
+++ b/ConsoleApp1/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ }
+}
diff --git a/ConsoleApp1/appsettings.json b/ConsoleApp1/appsettings.json
new file mode 100644
index 0000000..3267a7c
--- /dev/null
+++ b/ConsoleApp1/appsettings.json
@@ -0,0 +1,13 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*",
+ "ConnectionStrings": {
+ "CSINN": "Server=.;Database=CSINN;Trusted_Connection=True;MultipleActiveResultSets=true"
+ }
+}
diff --git a/src/CSInn.Domain.Repositories/Extensions/SpecificationExtensions.cs b/src/CSInn.Domain.Repositories/Extensions/SpecificationExtensions.cs
new file mode 100644
index 0000000..89172d2
--- /dev/null
+++ b/src/CSInn.Domain.Repositories/Extensions/SpecificationExtensions.cs
@@ -0,0 +1,25 @@
+using CSInn.Domain.Repositories.Specifications.Base;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CSInn.Domain.Repositories.Extensions
+{
+ public static class SpecificationExtensions
+ {
+ public static ISpecification And(this ISpecification left, ISpecification right) where TVisitor : ISpecificationVisitor
+ {
+ return new AndSpecification(left, right);
+ }
+
+ public static ISpecification Or(this ISpecification left, ISpecification right) where TVisitor : ISpecificationVisitor
+ {
+ return new OrSpecification(left, right);
+ }
+
+ public static ISpecification Not(this ISpecification specification) where TVisitor : ISpecificationVisitor
+ {
+ return new NotSpecification(specification);
+ }
+ }
+}
diff --git a/src/CSInn.Domain.Repositories/ILessonsRepository.cs b/src/CSInn.Domain.Repositories/ILessonsRepository.cs
deleted file mode 100644
index 874bdc2..0000000
--- a/src/CSInn.Domain.Repositories/ILessonsRepository.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using CSInn.Domain.Models.Content;
-using System;
-using System.Collections.Generic;
-
-namespace CSInn.Domain.Repositories
-{
- public interface ILessonsRepository : IRepository
- {
- // TODO: Specification pattern so we can mix criterias.
- IEnumerable GetByTags(params string[] tags);
- IEnumerable GetByName(string name);
- IEnumerable GetByAuthors(params string[] authors);
- IEnumerable GetByDate(DateTime from, DateTime to);
- }
-}
diff --git a/src/CSInn.Domain.Repositories/IRepository.cs b/src/CSInn.Domain.Repositories/IRepository.cs
deleted file mode 100644
index f4f6192..0000000
--- a/src/CSInn.Domain.Repositories/IRepository.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace CSInn.Domain.Repositories
-{
- public interface IRepository where TModel : class
- {
- void Create(TModel model);
- void Update(TModel model);
- void Delete(int key);
- IEnumerable Get();
- TModel Get(int id);
- }
-}
diff --git a/src/CSInn.Domain.Repositories/Repositories/ILessonsRepository.cs b/src/CSInn.Domain.Repositories/Repositories/ILessonsRepository.cs
new file mode 100644
index 0000000..3cd8e13
--- /dev/null
+++ b/src/CSInn.Domain.Repositories/Repositories/ILessonsRepository.cs
@@ -0,0 +1,16 @@
+using CSInn.Domain.Repositories.Specifications.Lesson;
+using CSInn.Models;
+
+namespace CSInn.Domain.Repositories.Repositories
+{
+ public interface ILessonsRepository : IRepository,
+ IRepositoryAsync
+ {
+ //// TODO: Specification pattern so we can mix criterias.
+ //IEnumerable GetByTags(params string[] tags);
+ //IEnumerable GetByName(string name);
+ //IEnumerable GetByAuthors(params string[] authors);
+ //IEnumerable GetByDate(DateTime from, DateTime to);
+ //IEnumerable Get(Specification specification);
+ }
+}
diff --git a/src/CSInn.Domain.Repositories/Repositories/IRepository.cs b/src/CSInn.Domain.Repositories/Repositories/IRepository.cs
new file mode 100644
index 0000000..0a310de
--- /dev/null
+++ b/src/CSInn.Domain.Repositories/Repositories/IRepository.cs
@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+using CSInn.Domain.Repositories.Specifications.Base;
+
+namespace CSInn.Domain.Repositories.Repositories
+{
+ public interface IRepository
+ where TModel : class
+ where TVisitor : ISpecificationVisitor
+ {
+ void Create(TModel model);
+ void Update(TModel model);
+ IEnumerable Get();
+ IEnumerable Get(ISpecification specification);
+ TModel Find(ISpecification specification);
+ }
+}
diff --git a/src/CSInn.Domain.Repositories/Repositories/IRepositoryAsync.cs b/src/CSInn.Domain.Repositories/Repositories/IRepositoryAsync.cs
new file mode 100644
index 0000000..97120d4
--- /dev/null
+++ b/src/CSInn.Domain.Repositories/Repositories/IRepositoryAsync.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using CSInn.Domain.Repositories.Specifications.Base;
+
+namespace CSInn.Domain.Repositories.Repositories
+{
+ public interface IRepositoryAsync
+ where TModel : class
+ where TVisitor : ISpecificationVisitor
+ {
+ Task CreateAsync(TModel model, CancellationToken token = default);
+ Task UpdateAsync(TModel model);
+ Task DeleteAsync(TModel key);
+ Task> GetAsync(CancellationToken token = default);
+ Task> GetAsync(ISpecification specification, CancellationToken token = default);
+ Task FindAsync(ISpecification specification, CancellationToken token = default);
+ }
+}
\ No newline at end of file
diff --git a/src/CSInn.Domain.Repositories/Specifications/Base/AndSpecification.cs b/src/CSInn.Domain.Repositories/Specifications/Base/AndSpecification.cs
new file mode 100644
index 0000000..8b40e05
--- /dev/null
+++ b/src/CSInn.Domain.Repositories/Specifications/Base/AndSpecification.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CSInn.Domain.Repositories.Specifications.Base
+{
+ public class AndSpecification : ISpecification
+ where TVisitor : ISpecificationVisitor
+ {
+ public ISpecification Left { get; }
+ public ISpecification Right { get; }
+
+ public AndSpecification(ISpecification left, ISpecification right)
+ {
+ Left = left;
+ Right = right;
+ }
+
+ public void Accept(TVisitor visitor) => visitor.Visit(this);
+ public bool IsSatisfiedBy(T obj) => Left.IsSatisfiedBy(obj) && Right.IsSatisfiedBy(obj);
+ }
+}
diff --git a/src/CSInn.Domain.Repositories/Specifications/Base/ISpecification.cs b/src/CSInn.Domain.Repositories/Specifications/Base/ISpecification.cs
new file mode 100644
index 0000000..0360ec1
--- /dev/null
+++ b/src/CSInn.Domain.Repositories/Specifications/Base/ISpecification.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CSInn.Domain.Repositories.Specifications.Base
+{
+ public interface ISpecification where TVisitor : ISpecificationVisitor
+ {
+ ///
+ /// Does the item meet specification?
+ ///
+ bool IsSatisfiedBy(T item);
+ void Accept(TVisitor visitor);
+ }
+}
diff --git a/src/CSInn.Domain.Repositories/Specifications/Base/ISpecificationVisitor.cs b/src/CSInn.Domain.Repositories/Specifications/Base/ISpecificationVisitor.cs
new file mode 100644
index 0000000..3193e3c
--- /dev/null
+++ b/src/CSInn.Domain.Repositories/Specifications/Base/ISpecificationVisitor.cs
@@ -0,0 +1,9 @@
+namespace CSInn.Domain.Repositories.Specifications.Base
+{
+ public interface ISpecificationVisitor where TVisitor : ISpecificationVisitor
+ {
+ void Visit(AndSpecification spec);
+ void Visit(OrSpecification spec);
+ void Visit(NotSpecification spec);
+ }
+}
\ No newline at end of file
diff --git a/src/CSInn.Domain.Repositories/Specifications/Base/NotSpecification.cs b/src/CSInn.Domain.Repositories/Specifications/Base/NotSpecification.cs
new file mode 100644
index 0000000..3029fbe
--- /dev/null
+++ b/src/CSInn.Domain.Repositories/Specifications/Base/NotSpecification.cs
@@ -0,0 +1,16 @@
+namespace CSInn.Domain.Repositories.Specifications.Base
+{
+ public class NotSpecification : ISpecification
+ where TVisitor : ISpecificationVisitor
+ {
+ public ISpecification Specification { get; }
+
+ public NotSpecification(ISpecification specification)
+ {
+ Specification = specification;
+ }
+
+ public void Accept(TVisitor visitor) => visitor.Visit(this);
+ public bool IsSatisfiedBy(T item) => !Specification.IsSatisfiedBy(item);
+ }
+}
\ No newline at end of file
diff --git a/src/CSInn.Domain.Repositories/Specifications/Base/OrSpecification.cs b/src/CSInn.Domain.Repositories/Specifications/Base/OrSpecification.cs
new file mode 100644
index 0000000..cf626fe
--- /dev/null
+++ b/src/CSInn.Domain.Repositories/Specifications/Base/OrSpecification.cs
@@ -0,0 +1,17 @@
+namespace CSInn.Domain.Repositories.Specifications.Base
+{
+ public class OrSpecification : ISpecification
+ where TVisitor : ISpecificationVisitor
+ {
+ public ISpecification Left { get;}
+ public ISpecification Right { get;}
+ public OrSpecification(ISpecification left, ISpecification right)
+ {
+ Left = left;
+ Right = right;
+ }
+
+ public void Accept(TVisitor visitor) => visitor.Visit(this);
+ public bool IsSatisfiedBy(T item) => Left.IsSatisfiedBy(item) || Right.IsSatisfiedBy(item);
+ }
+}
\ No newline at end of file
diff --git a/src/CSInn.Domain.Repositories/Specifications/Lesson/AuthorLike.cs b/src/CSInn.Domain.Repositories/Specifications/Lesson/AuthorLike.cs
new file mode 100644
index 0000000..a6694c1
--- /dev/null
+++ b/src/CSInn.Domain.Repositories/Specifications/Lesson/AuthorLike.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using CSInn.Domain.Repositories.Specifications.Base;
+using CSInn.Models;
+
+namespace CSInn.Domain.Repositories.Specifications.Lesson
+{
+ public class AuthorLike : ISpecification
+ {
+ public string Author { get; }
+
+ public AuthorLike(string author)
+ {
+ Author = author;
+ }
+
+ public void Accept(ILessonSpecificationVisitor visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public bool IsSatisfiedBy(CSInn.Models.Lesson lesson) => lesson.Authors.Any(a => a.Contains(Author));
+ }
+}
diff --git a/src/CSInn.Domain.Repositories/Specifications/Lesson/ILessonSpecificationVisitor.cs b/src/CSInn.Domain.Repositories/Specifications/Lesson/ILessonSpecificationVisitor.cs
new file mode 100644
index 0000000..36aadd7
--- /dev/null
+++ b/src/CSInn.Domain.Repositories/Specifications/Lesson/ILessonSpecificationVisitor.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using CSInn.Domain.Repositories.Specifications.Base;
+
+namespace CSInn.Domain.Repositories.Specifications.Lesson
+{
+ public interface ILessonSpecificationVisitor: ISpecificationVisitor
+ {
+ void Visit(TitleLike spec);
+ void Visit(TagMatches spec);
+ void Visit(AuthorLike authorLike);
+ }
+}
diff --git a/src/CSInn.Domain.Repositories/Specifications/Lesson/TagMatches.cs b/src/CSInn.Domain.Repositories/Specifications/Lesson/TagMatches.cs
new file mode 100644
index 0000000..5eee667
--- /dev/null
+++ b/src/CSInn.Domain.Repositories/Specifications/Lesson/TagMatches.cs
@@ -0,0 +1,21 @@
+using CSInn.Domain.Repositories.Specifications.Base;
+
+namespace CSInn.Domain.Repositories.Specifications.Lesson
+{
+ public class TagMatches : ISpecification
+ {
+ public string Tag { get; }
+
+ public TagMatches(string tag)
+ {
+ Tag = tag;
+ }
+
+ public void Accept(ILessonSpecificationVisitor visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public bool IsSatisfiedBy(CSInn.Models.Lesson lesson) => lesson.Tags.Contains(Tag);
+ }
+}
\ No newline at end of file
diff --git a/src/CSInn.Domain.Repositories/Specifications/Lesson/TitleLike.cs b/src/CSInn.Domain.Repositories/Specifications/Lesson/TitleLike.cs
new file mode 100644
index 0000000..9075efb
--- /dev/null
+++ b/src/CSInn.Domain.Repositories/Specifications/Lesson/TitleLike.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Linq.Expressions;
+using CSInn.Domain.Repositories.Specifications.Base;
+
+namespace CSInn.Domain.Repositories.Specifications.Lesson
+{
+ public class TitleLike : ISpecification
+ {
+ public string Title { get; }
+
+ public TitleLike(string title)
+ {
+ Title = title;
+ }
+
+ public bool IsSatisfiedBy(CSInn.Models.Lesson lesson) => lesson.Title.Contains(Title);
+
+ public void Accept(ILessonSpecificationVisitor visitor)
+ {
+ visitor.Visit(this);
+ }
+ }
+}
diff --git a/src/CSInn.Domain.Repositories/UnitOfWork/CSInnUnitOfWork.cs b/src/CSInn.Domain.Repositories/UnitOfWork/CSInnUnitOfWork.cs
new file mode 100644
index 0000000..076f899
--- /dev/null
+++ b/src/CSInn.Domain.Repositories/UnitOfWork/CSInnUnitOfWork.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using CSInn.Domain.Repositories.Repositories;
+
+namespace CSInn.Domain.Repositories.UnitOfWork
+{
+ public interface ICSInnUnitOfWork: IDisposable
+ {
+ ILessonsRepository Lessons { get; }
+
+ void Save();
+ Task SaveAsync(CancellationToken token = default);
+ }
+}
diff --git a/src/CSInn.Infrastructure.Repositories/CSInn.Infrastructure.Repositories.csproj b/src/CSInn.Infrastructure.Repositories/CSInn.Infrastructure.Repositories.csproj
index 11f86a1..577b046 100644
--- a/src/CSInn.Infrastructure.Repositories/CSInn.Infrastructure.Repositories.csproj
+++ b/src/CSInn.Infrastructure.Repositories/CSInn.Infrastructure.Repositories.csproj
@@ -1,11 +1,20 @@
- netcoreapp2.2
+ netcoreapp3.0
+
+
+
+
+
+
+
+
+
diff --git a/src/CSInn.Infrastructure.Repositories/Entities/LessonEntity.cs b/src/CSInn.Infrastructure.Repositories/Entities/LessonEntity.cs
new file mode 100644
index 0000000..17d06a4
--- /dev/null
+++ b/src/CSInn.Infrastructure.Repositories/Entities/LessonEntity.cs
@@ -0,0 +1,17 @@
+namespace CSInn.Infrastructure.Repositories.Entities
+{
+ public class LessonEntity
+ {
+
+ public int Id { get; set; }
+ public string Title { get; set; }
+ public string Description { get; set; }
+ public string Video { get; set; }
+ public string Slides { get; set; }
+ // EF doesn't like a list- fair point, but how do we solve this?
+ public string Tags { get; set; }
+ public string Authors { get; set; }
+
+ public string entity_test { get; set; }
+ }
+}
diff --git a/src/CSInn.Infrastructure.Repositories/Expressions/ExpressionExtensions.cs b/src/CSInn.Infrastructure.Repositories/Expressions/ExpressionExtensions.cs
new file mode 100644
index 0000000..75e19fd
--- /dev/null
+++ b/src/CSInn.Infrastructure.Repositories/Expressions/ExpressionExtensions.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Linq.Expressions;
+using CSInn.Domain.Repositories.ExpressionVisitors;
+
+namespace CSInn.Infrastructure.Repositories.Expressions
+{
+ internal static class ExpressionExtensions
+ {
+ public static Expression> AndAlso(
+ this Expression> expr1,
+ Expression> expr2)
+ {
+ var parameter = Expression.Parameter(typeof(T));
+
+ var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
+ var left = leftVisitor.Visit(expr1.Body);
+
+ var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
+ var right = rightVisitor.Visit(expr2.Body);
+
+ return Expression.Lambda>(
+ Expression.AndAlso(left, right), parameter);
+ }
+
+ public static Expression> OrElse(
+ this Expression> expr1,
+ Expression> expr2)
+ {
+ var parameter = Expression.Parameter(typeof(T));
+
+ var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
+ var left = leftVisitor.Visit(expr1.Body);
+
+ var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
+ var right = rightVisitor.Visit(expr2.Body);
+
+ return Expression.Lambda>(
+ Expression.OrElse(left, right), parameter);
+ }
+
+ public static Expression> Not(
+ this Expression> epxr)
+ {
+ var exprBody = System.Linq.Expressions.Expression.Not(epxr.Body);
+ return Expression.Lambda>(exprBody, epxr.Parameters);
+ }
+ }
+}
diff --git a/src/CSInn.Infrastructure.Repositories/Expressions/ExpressionVisitor.cs b/src/CSInn.Infrastructure.Repositories/Expressions/ExpressionVisitor.cs
new file mode 100644
index 0000000..06d00ac
--- /dev/null
+++ b/src/CSInn.Infrastructure.Repositories/Expressions/ExpressionVisitor.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Linq.Expressions;
+using CSInn.Domain.Repositories.Specifications.Base;
+
+// TODO: References should be internals visible to this one.
+namespace CSInn.Infrastructure.Repositories.Expressions
+{
+ public abstract class ExpressionVisitor
+ where TVisitor : ISpecificationVisitor
+ {
+ public Expression> Expression { get; protected set; }
+
+ public abstract Expression> ConvertSpecToExpression(ISpecification spec);
+
+ public void Visit(AndSpecification spec)
+ {
+ var left = ConvertSpecToExpression(spec.Left);
+ var right = ConvertSpecToExpression(spec.Right);
+
+ Expression = left.AndAlso(right);
+ }
+
+ public void Visit(OrSpecification spec)
+ {
+ var left = ConvertSpecToExpression(spec.Left);
+ var right = ConvertSpecToExpression(spec.Right);
+
+ Expression = left.OrElse(right);
+ }
+
+ public void Visit(NotSpecification spec)
+ {
+ var specExpr = ConvertSpecToExpression(spec.Specification);
+ Expression = specExpr.Not();
+ }
+ }
+}
+
diff --git a/src/CSInn.Infrastructure.Repositories/Expressions/LessonExpressionVisitor.cs b/src/CSInn.Infrastructure.Repositories/Expressions/LessonExpressionVisitor.cs
new file mode 100644
index 0000000..5e69ac9
--- /dev/null
+++ b/src/CSInn.Infrastructure.Repositories/Expressions/LessonExpressionVisitor.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Linq.Expressions;
+using CSInn.Domain.Repositories.Specifications.Base;
+using CSInn.Domain.Repositories.Specifications.Lesson;
+using CSInn.Infrastructure.Repositories.Entities;
+using CSInn.Models;
+
+namespace CSInn.Infrastructure.Repositories.Expressions
+{
+ public class LessonEntityExpressionVisitor : ExpressionVisitor, ILessonSpecificationVisitor
+ {
+ public override Expression> ConvertSpecToExpression(ISpecification spec)
+ {
+ var visitor = new LessonEntityExpressionVisitor();
+ spec.Accept(visitor);
+ return visitor.Expression;
+ }
+
+ public void Visit(TitleLike spec) => Expression = e => e.Title.Contains(spec.Title);
+ public void Visit(TagMatches spec) => Expression = e => e.Tags.Contains(spec.Tag);
+ public void Visit(AuthorLike authorLike) => Expression = e => e.Authors.Contains(authorLike.Author);
+ }
+}
diff --git a/src/CSInn.Infrastructure.Repositories/Expressions/ReplaceExpressionVisitor.cs b/src/CSInn.Infrastructure.Repositories/Expressions/ReplaceExpressionVisitor.cs
new file mode 100644
index 0000000..2f5d6ac
--- /dev/null
+++ b/src/CSInn.Infrastructure.Repositories/Expressions/ReplaceExpressionVisitor.cs
@@ -0,0 +1,22 @@
+using System.Linq.Expressions;
+
+namespace CSInn.Domain.Repositories.ExpressionVisitors
+{
+ internal class ReplaceExpressionVisitor
+ : ExpressionVisitor
+ {
+ private readonly Expression _oldValue;
+ private readonly Expression _newValue;
+
+ public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
+ {
+ _oldValue = oldValue;
+ _newValue = newValue;
+ }
+
+ public override Expression Visit(Expression node)
+ {
+ return node == _oldValue ? _newValue : base.Visit(node);
+ }
+ }
+}
diff --git a/src/CSInn.Infrastructure.Repositories/Extensions/LessonExtensions.cs b/src/CSInn.Infrastructure.Repositories/Extensions/LessonExtensions.cs
new file mode 100644
index 0000000..1ddf9f4
--- /dev/null
+++ b/src/CSInn.Infrastructure.Repositories/Extensions/LessonExtensions.cs
@@ -0,0 +1,45 @@
+using System.Collections.Generic;
+using System.Linq;
+using CSInn.Infrastructure.Repositories.Entities;
+using CSInn.Models;
+
+namespace CSInn.Infrastructure.Repositories.Extensions
+{
+ public static class LessonExtensions
+ {
+ public static Lesson ToModel(this LessonEntity entity)
+ {
+ // call to automapper
+ var lesson = new Lesson(entity.Title, entity.Description, entity.Tags.Split(","), entity.Authors.Split(","))
+ {
+ Id = entity.Id,
+ };
+
+ return lesson;
+ }
+
+ public static IEnumerable ToModels(this IEnumerable entities)
+ {
+ // call to automapper
+ var experiments = entities.Select(e => e.ToModel());
+
+ return experiments;
+ }
+
+ public static LessonEntity ToEntity(this Lesson entity)
+ {
+ // call to automapper
+ var experiment = new LessonEntity()
+ {
+ Id = entity.Id,
+ Title = entity.Title,
+ Authors = string.Join(',', entity.Authors),
+ Description = entity.Description,
+ Slides = entity.Slides,
+ Tags = string.Join(',',entity.Tags)
+ };
+
+ return experiment;
+ }
+ }
+}
diff --git a/src/CSInn.Infrastructure.Repositories/Repositories/LessonsRepository.cs b/src/CSInn.Infrastructure.Repositories/Repositories/LessonsRepository.cs
new file mode 100644
index 0000000..afb4b3c
--- /dev/null
+++ b/src/CSInn.Infrastructure.Repositories/Repositories/LessonsRepository.cs
@@ -0,0 +1,50 @@
+using CSInn.Domain.Repositories.Repositories;
+using CSInn.Domain.Repositories.Specifications.Lesson;
+using CSInn.Infrastructure.Repositories.Context;
+using CSInn.Infrastructure.Repositories.Entities;
+using CSInn.Infrastructure.Repositories.Expressions;
+using CSInn.Models;
+
+namespace CSInn.Infrastructure.Repositories.Repositories
+{
+ namespace CSInn.Experimental.EF.Repositories
+ {
+ public class LessonsRepository : Repository,
+ ILessonsRepository
+ {
+ public LessonsRepository(CSInnDbContext context) : base(context)
+ {
+ }
+
+ protected override Lesson ToModel(LessonEntity entity)
+ {
+ // call to automapper
+ var lesson = new Lesson(entity.Title, entity.Description, entity.Tags.Split(","), entity.Authors.Split(","))
+ {
+ Id = entity.Id,
+ };
+
+ return lesson;
+ }
+
+ protected override LessonEntity ToEntity(Lesson model)
+ {
+ // call to automapper
+ var lesson = new LessonEntity()
+ {
+ Id = model.Id,
+ Title = model.Title,
+ Authors = string.Join(',', model.Authors),
+ Description = model.Description,
+ Slides = model.Slides,
+ Tags = string.Join(',', model.Tags)
+ };
+
+ return lesson;
+ }
+
+ protected override int ExtractKey(Lesson model) => model.Id;
+ }
+ }
+}
+
diff --git a/src/CSInn.Infrastructure.Repositories/Repositories/Repository.cs b/src/CSInn.Infrastructure.Repositories/Repositories/Repository.cs
new file mode 100644
index 0000000..02bb220
--- /dev/null
+++ b/src/CSInn.Infrastructure.Repositories/Repositories/Repository.cs
@@ -0,0 +1,111 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using CSInn.Domain.Repositories.Repositories;
+using CSInn.Domain.Repositories.Specifications.Base;
+using CSInn.Infrastructure.Repositories.Context;
+using CSInn.Infrastructure.Repositories.Expressions;
+using Microsoft.EntityFrameworkCore;
+
+namespace CSInn.Infrastructure.Repositories.Repositories
+{
+ public abstract class Repository : IRepository, IRepositoryAsync
+ where TModel : class
+ where TEntity : class
+ where TDomainVisitor : ISpecificationVisitor
+ where TEFVisitor : ExpressionVisitor, new()
+ {
+ private readonly TEFVisitor _visitor;
+
+ private readonly CSInnDbContext _context;
+ private readonly DbSet _dbSet;
+
+ protected Repository(CSInnDbContext context)
+ {
+ _context = context;
+ _visitor = new TEFVisitor();
+ _dbSet = _context.Set();
+ }
+
+ protected abstract TModel ToModel(TEntity entity);
+ protected abstract TEntity ToEntity(TModel model);
+ protected abstract int ExtractKey(TModel model);
+
+ public IEnumerable Get() => _dbSet.Select(e => ToModel(e));
+ public void Create(TModel model) => _dbSet.Add(ToEntity(model));
+
+ public void Update(TModel model)
+ {
+ var entity = _dbSet.Find(ExtractKey(model));
+
+ if (entity == null)
+ {
+ return;
+ }
+
+ _context.Entry(entity).CurrentValues.SetValues(model);
+ }
+
+ public void Delete(TModel model)
+ {
+ var entity = _dbSet.Find(ExtractKey(model));
+ _dbSet.Remove(entity);
+ }
+
+ public TModel Find(ISpecification specification)
+ {
+ var expression = _visitor.ConvertSpecToExpression(specification);
+ return ToModel(_dbSet.FirstOrDefault(expression));
+ }
+
+ public IEnumerable Get(ISpecification specification)
+ {
+ var expression = _visitor.ConvertSpecToExpression(specification);
+ return _dbSet.Where(expression).Select(e => ToModel(e));
+ }
+
+ public async Task CreateAsync(TModel model, CancellationToken token = default) => await _dbSet.AddAsync(ToEntity(model), token);
+
+ public async Task UpdateAsync(TModel model)
+ {
+ var entity = await _dbSet.FindAsync(ExtractKey(model));
+
+ if (entity == null)
+ {
+ return;
+ }
+
+ _context.Entry(entity).CurrentValues.SetValues(model);
+ }
+
+ public async Task DeleteAsync(TModel model)
+ {
+ var entity = await _dbSet.FindAsync(ExtractKey(model));
+ _dbSet.Remove(entity);
+ }
+
+ public async Task> GetAsync(CancellationToken token = default)
+ {
+ var entities = await _dbSet.ToListAsync(token);
+ return entities.Select(ToModel);
+ }
+
+ public async Task> GetAsync(ISpecification specification, CancellationToken token = default)
+ {
+ var expression = _visitor.ConvertSpecToExpression(specification);
+ var entities = await _dbSet.Where(expression).ToListAsync(token);
+
+ return entities.Select(ToModel);
+ }
+
+ public async Task FindAsync(ISpecification specification, CancellationToken token = default)
+ {
+ var expression = _visitor.ConvertSpecToExpression(specification);
+ var entity = await _dbSet.FirstOrDefaultAsync(expression, token);
+
+ return ToModel(entity);
+ }
+
+ }
+}
diff --git a/src/CSInn.Infrastructure.Repositories/UoW/CSInnDbContext.cs b/src/CSInn.Infrastructure.Repositories/UoW/CSInnDbContext.cs
new file mode 100644
index 0000000..8daa0fb
--- /dev/null
+++ b/src/CSInn.Infrastructure.Repositories/UoW/CSInnDbContext.cs
@@ -0,0 +1,18 @@
+using CSInn.Infrastructure.Repositories.Entities;
+using Microsoft.EntityFrameworkCore;
+
+namespace CSInn.Infrastructure.Repositories.Context
+{
+ public class CSInnDbContext : DbContext
+ {
+ public CSInnDbContext()
+ {
+ }
+
+ public CSInnDbContext(DbContextOptions options)
+ : base(options)
+ {
+ }
+ public virtual DbSet Lessons { get; set; }
+ }
+}
diff --git a/src/CSInn.Infrastructure.Repositories/UoW/CSInnUnitOfWork.cs b/src/CSInn.Infrastructure.Repositories/UoW/CSInnUnitOfWork.cs
new file mode 100644
index 0000000..a8d2fd2
--- /dev/null
+++ b/src/CSInn.Infrastructure.Repositories/UoW/CSInnUnitOfWork.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using CSInn.Domain.Repositories.Repositories;
+using CSInn.Domain.Repositories.UnitOfWork;
+using CSInn.Infrastructure.Repositories.Context;
+using CSInn.Infrastructure.Repositories.Repositories.CSInn.Experimental.EF.Repositories;
+
+namespace CSInn.Infrastructure.Repositories.UoW
+{
+ public class CSInnUnitOfWork: ICSInnUnitOfWork
+ {
+ private readonly CSInnDbContext _context;
+
+ private ILessonsRepository _lessons;
+
+ public ILessonsRepository Lessons
+ {
+ get
+ {
+ if (_lessons == null)
+ {
+ _lessons = new LessonsRepository(_context);
+ }
+
+ return _lessons;
+ }
+ private set => _lessons = value;
+ }
+
+ public CSInnUnitOfWork(CSInnDbContext context)
+ {
+ _context = context;
+ }
+
+ public void Save() => _context.SaveChanges();
+
+ public Task SaveAsync(CancellationToken token = default) =>_context.SaveChangesAsync();
+
+ #region IDisposable pattern
+ private bool disposed;
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposed)
+ {
+ if (disposing)
+ {
+ _context.Dispose();
+ }
+ }
+ disposed = true;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ #endregion
+ }
+}
diff --git a/src/CSInn.Models/Exceptions/InvalidLessonException.cs b/src/CSInn.Models/Exceptions/InvalidLessonException.cs
index a215143..ff41c40 100644
--- a/src/CSInn.Models/Exceptions/InvalidLessonException.cs
+++ b/src/CSInn.Models/Exceptions/InvalidLessonException.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Text;
namespace CSInn.Domain.Models.Content.Exceptions
{
diff --git a/src/CSInn.Models/Lesson.cs b/src/CSInn.Models/Lesson.cs
index 6f5bea6..4967c7b 100644
--- a/src/CSInn.Models/Lesson.cs
+++ b/src/CSInn.Models/Lesson.cs
@@ -1,19 +1,22 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Linq;
-using System.Text;
+using System.Runtime.CompilerServices;
using CSInn.Domain.Models.Content.Exceptions;
-namespace CSInn.Domain.Models.Content
+[assembly: InternalsVisibleTo("CSInn.Infrastructure.Repositories")]
+namespace CSInn.Models
{
public class Lesson
{
- public string Title { get; }
+ internal int Id { get; set; }
+ public string Title { get; set; }
public string Description { get; set; }
public string Video { get; set; }
public string Slides { get; set; }
public IList Tags { get; set; }
- public IList Authors { get; }
+ public IList Authors { get; set; }
+
+ public string model_test { get; set; }
public Lesson(string title, string description, IEnumerable tags, IEnumerable authors)
{
@@ -25,7 +28,7 @@ public Lesson(string title, string description, IEnumerable tags, IEnume
var isTitleEmpty = string.IsNullOrEmpty(title);
var isDescriptionEmpty = string.IsNullOrEmpty(description);
var areNoTags = Tags == null || !Tags.Any();
- var areNoAuthors = Authors == null || Authors.Any();
+ var areNoAuthors = Authors == null || !Authors.Any();
if (isTitleEmpty || isDescriptionEmpty || areNoTags || areNoAuthors)
{
@@ -33,6 +36,8 @@ public Lesson(string title, string description, IEnumerable tags, IEnume
}
}
+
+ public Lesson() { }
}
}
diff --git a/tests/CSInn.Application.Tests/CSInn.Application.Tests.csproj b/tests/CSInn.Application.Tests/CSInn.Application.Tests.csproj
index ff862c8..8938f3d 100644
--- a/tests/CSInn.Application.Tests/CSInn.Application.Tests.csproj
+++ b/tests/CSInn.Application.Tests/CSInn.Application.Tests.csproj
@@ -1,16 +1,19 @@
-
+
- netcoreapp2.1
+ netcoreapp2.2
false
-
-
-
-
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/tests/CSInn.Discord.Authentication.Tests/CSInn.Discord.Authentication.Tests.csproj b/tests/CSInn.Discord.Authentication.Tests/CSInn.Discord.Authentication.Tests.csproj
index 9116020..1bf18b5 100644
--- a/tests/CSInn.Discord.Authentication.Tests/CSInn.Discord.Authentication.Tests.csproj
+++ b/tests/CSInn.Discord.Authentication.Tests/CSInn.Discord.Authentication.Tests.csproj
@@ -7,10 +7,13 @@
-
-
-
-
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/tests/CSInn.Infrastructure.Repositories.Tests/CSInn.Infrastructure.Repositories.Tests.csproj b/tests/CSInn.Infrastructure.Repositories.Tests/CSInn.Infrastructure.Repositories.Tests.csproj
index 7bb3c40..a793555 100644
--- a/tests/CSInn.Infrastructure.Repositories.Tests/CSInn.Infrastructure.Repositories.Tests.csproj
+++ b/tests/CSInn.Infrastructure.Repositories.Tests/CSInn.Infrastructure.Repositories.Tests.csproj
@@ -1,18 +1,23 @@
-
+
- netcoreapp2.2
+ netcoreapp3.0
false
-
-
-
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
diff --git a/tests/CSInn.Infrastructure.Repositories.Tests/Input/LessonsFixture.cs b/tests/CSInn.Infrastructure.Repositories.Tests/Input/LessonsFixture.cs
new file mode 100644
index 0000000..5a99c45
--- /dev/null
+++ b/tests/CSInn.Infrastructure.Repositories.Tests/Input/LessonsFixture.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.Linq;
+using CSInn.Models;
+
+namespace CSInn.Infrastructure.Repositories.Tests.Input
+{
+ public class LessonsFixture
+ {
+ public readonly IQueryable Lessons;
+
+ public LessonsFixture()
+ {
+ var lessons = new[]
+ {
+ new Lesson("Lesson 1: Class", "What is a class, object", new List() {"OOP"},
+ new List() {"Almantas Karpavičius"}),
+ new Lesson("Lesson 2: SOLID", "SOLID", new List() {"OOP"}, new List() {"Kaisinel"}),
+ new Lesson("Lesson 3: Delegates", "Delegate vs callback", new List() {"FP"},
+ new List() {"Lethern"})
+ };
+ Lessons = new EnumerableQuery(lessons);
+ }
+ }
+}
diff --git a/tests/CSInn.Infrastructure.Repositories.Tests/Input/UoWFixture.cs b/tests/CSInn.Infrastructure.Repositories.Tests/Input/UoWFixture.cs
new file mode 100644
index 0000000..e0a849e
--- /dev/null
+++ b/tests/CSInn.Infrastructure.Repositories.Tests/Input/UoWFixture.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using CSInn.Infrastructure.Repositories.Context;
+using CSInn.Infrastructure.Repositories.UoW;
+using CSInn.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace CSInn.Infrastructure.Repositories.Tests.Input
+{
+ public class UoWFixture
+ {
+ public CSInnUnitOfWork UoW { get; set; }
+ public UoWFixture()
+ {
+ var options = new DbContextOptionsBuilder()
+ .UseInMemoryDatabase(databaseName: "database_name")
+ .Options;
+
+ var context = new CSInnDbContext(options);
+ UoW = new CSInnUnitOfWork(context);
+
+ UoW.Lessons.Create(new Lesson("Lesson 2: SOLID", "SOLID", new List() { "OOP" },
+ new List() { "Kaisinel" }));
+
+ UoW.Lessons.Create(
+ new Lesson("Lesson 3: Delegates", "Delegate vs callback", new List() { "FP" },
+ new List() { "Lethern" }));
+
+ UoW.Save();
+ }
+ }
+}
diff --git a/tests/CSInn.Infrastructure.Repositories.Tests/LessonSpecificationTests.cs b/tests/CSInn.Infrastructure.Repositories.Tests/LessonSpecificationTests.cs
new file mode 100644
index 0000000..be31442
--- /dev/null
+++ b/tests/CSInn.Infrastructure.Repositories.Tests/LessonSpecificationTests.cs
@@ -0,0 +1,43 @@
+using System.Linq;
+using System.Threading.Tasks;
+using CSInn.Domain.Repositories.Extensions;
+using CSInn.Domain.Repositories.Specifications.Lesson;
+using CSInn.Domain.Repositories.UnitOfWork;
+using CSInn.Infrastructure.Repositories.Tests.Input;
+using CSInn.Models;
+using Xunit;
+
+namespace CSInn.Infrastructure.Repositories.Tests
+{
+ public class LessonSpecificationTests : IClassFixture
+ {
+ private readonly ICSInnUnitOfWork _uow;
+
+ public LessonSpecificationTests(UoWFixture fixture)
+ {
+ _uow = fixture.UoW;
+ }
+
+ [Fact]
+ public void Composite_Specification_Ok()
+ {
+ var filter = new TitleLike("Lesson 2")
+ .Or(new TitleLike("Lesson 1")).Not();
+
+ var result = _uow.Lessons.Get(filter);
+
+ Assert.Single(result);
+ }
+
+ [Fact]
+ public async Task Composite_Specification_Async_Ok()
+ {
+ var filter = new TitleLike("Lesson 2")
+ .Or(new TitleLike("Lesson 1")).Not();
+
+ var result = await _uow.Lessons.GetAsync(filter);
+
+ Assert.Single(result);
+ }
+ }
+}