├── .gitignore
├── LICENSE
├── README.md
└── src
├── FluentSqlKata.EFCore
├── DbContextHelper.cs
└── FluentSqlKata.EFCore.csproj
├── FluentSqlKata.Tests
├── Entities
│ ├── BaseEntity.cs
│ ├── BirdWithSchema.cs
│ ├── Contact.cs
│ └── Customer.cs
├── FluentQueryTests.cs
├── FluentSqlKata.Tests.csproj
├── Helpers
│ └── AssertHelper.cs
└── Models
│ ├── CustomerModel.cs
│ └── NestedContactModel.cs
├── FluentSqlKata.sln
└── FluentSqlKata
├── FluentQuery.cs
├── FluentQueryWrapper.cs
└── FluentSqlKata.csproj
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 fairking
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FluentSqlKata
2 |
3 | Simple and very concrete, fluent way to build sql queries.
4 |
5 | ## Features
6 |
7 | - Fluent queries (you have more freedom than with `Linq`, `HQL` or `QueryOver`)
8 | - OrderByAlias (Dynamic way to order by columns in runtime)
9 | - Can be easily used along with Entity Framework Core without huge code changes (see [FluentSqlKata.EFCore](https://github.com/fairking/FluentSqlKata/tree/main/src/FluentSqlKata.EFCore/DbContextHelper.cs))
10 | - All standart SqlKata features remain without changes
11 |
12 | ## Installation
13 |
14 | Using dotnet cli
15 | ```
16 | $ dotnet add package FluentSqlKata
17 | ```
18 | Using Nuget Package Manager
19 | ```
20 | PM> Install-Package FluentSqlKata
21 | ```
22 |
23 | ## How to use
24 |
25 | ``` c#
26 | using FluentSqlKata;
27 |
28 | [Fact]
29 | public void Basic()
30 | {
31 | Customer myCust = null;
32 | (string CustomerId, string CustomerName) result = default;
33 |
34 | var query = FluentQuery.Query()
35 | .From(() => myCust)
36 | .Select(() => result.CustomerId, () => myCust.Id)
37 | .Select(() => result.CustomerName, () => myCust.Name)
38 | ;
39 |
40 | var query_str = new SqlServerCompiler().Compile(query).ToString();
41 |
42 | Assert.NotNull(query_str);
43 | Assert.Equal("SELECT [myCust].[Id] AS [Item1], [myCust].[Name] AS [Item2] FROM [Customer] AS [myCust]", query_str);
44 | }
45 |
46 | [Fact]
47 | public void Where()
48 | {
49 | Customer myCust = null;
50 | (string CustomerId, string CustomerName) result = default;
51 |
52 | var query = FluentQuery.Query()
53 | .From(() => myCust)
54 | .Select(() => result.CustomerId, () => myCust.Id)
55 | .Select(() => result.CustomerName, () => myCust.Name)
56 | .Where((q) => q.Where(() => myCust.Name, "John").OrWhereContains(() => myCust.Name, "oh"))
57 | ;
58 |
59 | var query_str = new SqlServerCompiler().Compile(query).ToString();
60 |
61 | Assert.NotNull(query_str);
62 | Assert.Equal("SELECT [myCust].[Id] AS [Item1], [myCust].[Name] AS [Item2] FROM [Customer] AS [myCust] WHERE ([myCust].[Name] = 'John' OR LOWER([myCust].[Name]) like '%oh%')", query_str);
63 | }
64 |
65 | [Fact]
66 | public void Join()
67 | {
68 | Contact myCont = null;
69 | Customer myCust = null;
70 | (string FirstName, string LastName, string CustomerId, string CustomerName) result = default;
71 |
72 | var query = FluentQuery.Query()
73 | .From(() => myCont)
74 | .Join(() => myCust, () => myCust.Id, () => myCont.CustomerId)
75 | .Select(() => result.FirstName, () => myCont.FirstName)
76 | .Select(() => result.LastName, () => myCont.LastName)
77 | .Select(() => result.CustomerId, () => myCont.CustomerId)
78 | .Select(() => result.CustomerName, () => myCust.Name)
79 | ;
80 |
81 | var query_str = new SqlServerCompiler().Compile(query).ToString();
82 |
83 | Assert.NotNull(query_str);
84 | Assert.Equal("SELECT [myCont].[FirstName] AS [Item1], [myCont].[LastName] AS [Item2], [myCont].[contact_customer_id] AS [Item3], [myCust].[Name] AS [Item4] FROM [Contacts] AS [myCont] \nINNER JOIN [Customer] AS [myCust] ON [myCust].[Id] = [myCont].[contact_customer_id]", query_str);
85 | }
86 |
87 | [Fact]
88 | public void JoinBuilder()
89 | {
90 | Contact myCont = null;
91 | Customer myCust = null;
92 | (string FirstName, string LastName, string CustomerId) result = default;
93 |
94 | var query = FluentQuery.Query()
95 | .From(() => myCont)
96 | .Join(() => myCust, (join) => join.WhereColumns(() => myCust.Id, () => myCont.CustomerId))
97 | .Select(() => result.FirstName, () => myCont.FirstName)
98 | .Select(() => result.LastName, () => myCont.LastName)
99 | .Select(() => result.CustomerId, () => myCust.Id)
100 | ;
101 |
102 | var query_str = new SqlServerCompiler().Compile(query).ToString();
103 |
104 | Assert.NotNull(query_str);
105 | Assert.Equal("SELECT [myCont].[FirstName] AS [Item1], [myCont].[LastName] AS [Item2], [myCust].[Id] AS [Item3] FROM [Contacts] AS [myCont] \nINNER JOIN [Customer] AS [myCust] ON ([myCust].[Id] = [myCont].[contact_customer_id])", query_str);
106 | }
107 |
108 | [Fact]
109 | public void GroupBy()
110 | {
111 | Customer myCust = null;
112 | Contact myCont = null;
113 |
114 | (string CustomerName, int ContactCount) result = default;
115 |
116 | var query = FluentQuery.Query()
117 | .From(() => myCont)
118 | .Join(() => myCust, (join) => join.WhereColumns(() => myCust.Id, () => myCont.CustomerId))
119 | .SelectCount(() => result.ContactCount, () => myCont.Id)
120 | .Select(() => result.CustomerName, () => myCust.Name)
121 | .GroupBy(() => myCust.Name)
122 | ;
123 |
124 | var query_str = new SqlServerCompiler().Compile(query).ToString();
125 |
126 | Assert.NotNull(query_str);
127 | Assert.Equal("SELECT COUNT(myCont.Id) AS Item2, [myCust].[Name] AS [Item1] FROM [Contacts] AS [myCont] \nINNER JOIN [Customer] AS [myCust] ON ([myCust].[Id] = [myCont].[contact_customer_id]) GROUP BY [myCust].[Name]", query_str);
128 | }
129 |
130 | [Fact]
131 | public void OrderBy()
132 | {
133 | Customer myCust = null;
134 |
135 | var query = FluentQuery.Query()
136 | .SelectAll(() => myCust)
137 | .OrderByColumn(() => myCust.Name)
138 | ;
139 |
140 | var query_str = new SqlServerCompiler().Compile(query).ToString();
141 |
142 | Assert.NotNull(query_str);
143 | Assert.Equal("SELECT [myCust].[Name] AS [Name], [myCust].[Id] AS [Id] FROM [Customer] AS [myCust] ORDER BY [myCust].[Name]", query_str);
144 | }
145 |
146 | [Fact]
147 | public void OrderByAlias()
148 | {
149 | Customer myCust = null;
150 | CustomerModel model = null;
151 |
152 | var query = FluentQuery.Query()
153 | .From(() => myCust)
154 | .SelectRaw(() => model.Name, "ISNULL({0}, 'Uknown')", () => myCust.Name)
155 | .OrderByAlias(() => model.Name)
156 | ;
157 |
158 | var query_str = new SqlServerCompiler().Compile(query).ToString();
159 |
160 | Assert.NotNull(query_str);
161 | Assert.Equal("SELECT ISNULL(myCust.Name, 'Uknown') AS Name FROM [Customer] AS [myCust] ORDER BY ISNULL(myCust.Name, 'Uknown')", query_str);
162 | }
163 | ```
164 |
165 | ## Nuget
166 |
167 | [FluentSqlKata](https://www.nuget.org/packages/FluentSqlKata/)
168 |
169 | ## How to contribute
170 |
171 | If you have any issues please provide us with Unit Test Example.
172 |
173 | To become a contributer please create an issue ticket with such enqury.
174 |
175 | ## Donations
176 |
177 | Donate with [Ӿ nano crypto (XNO)](https://nano.org).
178 |
179 | [](https://nanocrawler.cc/explorer/account/nano_1sygjbkepdcu5diiekf15ar6m6utfgf9rr9tkd6zi8mkq7yza34kiyjpgt9g)
180 |
181 | Thank you!
--------------------------------------------------------------------------------
/src/FluentSqlKata.EFCore/DbContextHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Microsoft.EntityFrameworkCore.Storage;
3 | using SqlKata;
4 | using SqlKata.Compilers;
5 | using System.Collections;
6 | using System.ComponentModel.DataAnnotations.Schema;
7 | using System.Data;
8 | using System.Data.Common;
9 | using System.Linq.Expressions;
10 | using System.Reflection;
11 |
12 | namespace FluentSqlKata.EFCore6
13 | {
14 | public static class DbContextHelper
15 | {
16 | #region Queries
17 |
18 | public static Query Query(this DbContext dbContext)
19 | {
20 | return FluentQuery.Query();
21 | }
22 |
23 | public static Query Query(this DbContext dbContext)
24 | {
25 | return FluentQuery.Query();
26 | }
27 |
28 | public static Query Query(this DbContext dbContext, Expression> alias)
29 | {
30 | return FluentQuery.Query(alias);
31 | }
32 |
33 | #endregion Queries
34 |
35 | #region Execution
36 |
37 | public static async Task ToScalarAsync(this DbContext dbContext, Query query, CancellationToken cancellationToken = default)
38 | {
39 | T result;
40 |
41 | var compiler = new SqlServerCompiler() { UseLegacyPagination = true };
42 |
43 | // Compile to sql query
44 | var sql = compiler.Compile(query);
45 |
46 | #if DEBUG
47 | Debug.WriteLine(sql);
48 | #endif
49 |
50 | using (var cmd = dbContext.Database.GetDbConnection().CreateCommand())
51 | {
52 | if (cmd.Connection.State == ConnectionState.Closed)
53 | await dbContext.Database.OpenConnectionAsync(cancellationToken);
54 |
55 | cmd.Transaction = dbContext.Database.CurrentTransaction?.GetDbTransaction();
56 |
57 | cmd.CommandText = sql.Sql;
58 |
59 | // Add query parameters
60 | foreach (var bind in sql.NamedBindings)
61 | {
62 | var p = cmd.CreateParameter();
63 | p.ParameterName = bind.Key;
64 | p.Value = bind.Value;
65 | cmd.Parameters.Add(p);
66 | }
67 |
68 | var sqlResult = await cmd.ExecuteScalarAsync(cancellationToken);
69 |
70 | if (sqlResult == null || sqlResult == DBNull.Value)
71 | result = default;
72 | else
73 | result = (T)sqlResult;
74 | }
75 |
76 | return result;
77 | }
78 |
79 | public static async Task> ToListAsync(this DbContext dbContext, Query query, CancellationToken cancellationToken = default) where T : class, new()
80 | {
81 | IEnumerable result;
82 |
83 | var compiler = new SqlServerCompiler() { UseLegacyPagination = true };
84 |
85 | // Compile to sql query
86 | var sql = compiler.Compile(query);
87 |
88 | #if DEBUG
89 | Debug.WriteLine(sql);
90 | #endif
91 |
92 | using (var cmd = dbContext.CreateCommand())
93 | {
94 | if (cmd.Connection.State == ConnectionState.Closed)
95 | await dbContext.Database.OpenConnectionAsync(cancellationToken);
96 |
97 | cmd.CommandText = sql.Sql;
98 |
99 | // Add query parameters
100 | foreach (var bind in sql.NamedBindings)
101 | {
102 | var p = cmd.CreateParameter();
103 | p.ParameterName = bind.Key;
104 | p.Value = bind.Value;
105 | cmd.Parameters.Add(p);
106 | }
107 |
108 | // Read rows
109 | result = await cmd.ReadObjectsAsync(cancellationToken);
110 | }
111 |
112 | return result;
113 | }
114 |
115 | #endregion Execution
116 |
117 | #region Misc
118 |
119 | public static void AutoIncrementOff(this DbContext dbContext, Type entity)
120 | {
121 | using (var cmd = dbContext.CreateCommand())
122 | {
123 | cmd.CommandText = $"SET IDENTITY_INSERT {dbContext.GetTableName(entity)} ON";
124 |
125 | cmd.ExecuteNonQuery();
126 | }
127 | }
128 |
129 | public static void AutoIncrementOn(this DbContext dbContext, Type entity)
130 | {
131 | using (var cmd = dbContext.CreateCommand())
132 | {
133 | cmd.CommandText = $"SET IDENTITY_INSERT {dbContext.GetTableName(entity)} OFF";
134 |
135 | cmd.ExecuteNonQuery();
136 | }
137 | }
138 |
139 | public static void LockTable(this DbContext dbContext, Type entity)
140 | {
141 | using (var cmd = dbContext.CreateCommand())
142 | {
143 | cmd.CommandText = $"SELECT TOP 0 NULL FROM {dbContext.GetTableName(entity)} WITH (TABLOCKX)";
144 |
145 | cmd.ExecuteNonQuery();
146 | }
147 | }
148 |
149 | public static string GetTableName(this DbContext dbContext, Type entity)
150 | {
151 | if (entity == null)
152 | throw new ArgumentNullException(nameof(entity));
153 |
154 | var tableAttr = entity.GetCustomAttribute();
155 | if (tableAttr == null)
156 | throw new ArgumentException($"The entity type '{entity.Name}' is not a table.", nameof(entity));
157 |
158 | if (!string.IsNullOrWhiteSpace(tableAttr.Schema))
159 | {
160 | return $"{tableAttr.Schema}.dbo.{tableAttr.Name}";
161 | }
162 | else
163 | {
164 | return $"{tableAttr.Name}";
165 | }
166 | }
167 |
168 | public static string GetDbName(this DbContext dbContext)
169 | {
170 | // We can also use this
171 | // return new SqlConnectionStringBuilder(dbContext.Database.GetConnectionString()).InitialCatalog;
172 |
173 | return dbContext.Database.GetDbConnection().Database;
174 | }
175 |
176 | ///
177 | /// Creates an empty DbCommand
178 | /// Please remember to dispose DbCommand and DbDataReader afterwards
179 | ///
180 | public static DbCommand CreateCommand(this DbContext dbContext)
181 | {
182 | var connection = dbContext.GetOpenConnection();
183 | var cmd = connection.CreateCommand();
184 |
185 | cmd.Transaction = dbContext.Database.CurrentTransaction?.GetDbTransaction();
186 |
187 | return cmd;
188 | }
189 |
190 | ///
191 | /// Creates a DbCommand containing the
192 | /// Please remember to dispose DbCommand and DbDataReader afterwards
193 | ///
194 | public static async Task CreateDbCommand(this DbContext dbContext, Query query, CancellationToken cancellationToken = default)
195 | {
196 | Compiler compiler;
197 |
198 | // Create a compiler
199 | if (dbContext.Database.IsSqlServer())
200 | compiler = new SqlServerCompiler();
201 | else if (dbContext.Database.IsSqlite())
202 | compiler = new SqliteCompiler();
203 | else
204 | throw new NotSupportedException($"The provided database context '{dbContext.GetType()}' is not supported to create a SqlKata compiler.");
205 |
206 | // Compile to sql query
207 | var sql = compiler.Compile(query);
208 |
209 | #if DEBUG
210 | Debug.WriteLine(sql);
211 | #endif
212 |
213 | var cmd = dbContext.Database.GetDbConnection().CreateCommand();
214 |
215 | if (cmd.Connection.State == ConnectionState.Closed)
216 | await cmd.Connection.OpenAsync(cancellationToken);
217 |
218 | cmd.CommandText = sql.Sql;
219 |
220 | // Add query parameters
221 | foreach (var bind in sql.NamedBindings)
222 | {
223 | var p = cmd.CreateParameter();
224 | p.ParameterName = bind.Key;
225 | p.Value = bind.Value;
226 | cmd.Parameters.Add(p);
227 | }
228 |
229 | return cmd;
230 | }
231 |
232 | #endregion Misc
233 |
234 | #region Private Methods
235 |
236 | private static async Task> ReadObjectsAsync(this DbCommand cmd, CancellationToken cancellationToken = default) where T : class, new()
237 | {
238 | // Execute query
239 | using (var reader = await cmd.ExecuteReaderAsync(cancellationToken))
240 | {
241 | if (!reader.HasRows)
242 | return new T[0];
243 |
244 | var properties = GetPropertiesOfModel(typeof(T));
245 |
246 | var result = new List();
247 |
248 | // Read rows
249 | while (await reader.ReadAsync(cancellationToken))
250 | {
251 | T item;
252 |
253 | // Read columns
254 | if (properties.Count == 0) // Single column struct like ToObjectAsync() (eg. int, string or enum)
255 | {
256 | item = (T)ParseDbField(reader[0], typeof(T));
257 | }
258 | else
259 | {
260 | item = (T)Activator.CreateInstance(typeof(T), true);
261 |
262 | for (int i = 0; i < reader.FieldCount; ++i)
263 | {
264 | string columnName = reader.GetName(i);
265 |
266 | if (properties.TryGetValue(columnName, out var mappedProperty))
267 | {
268 | var value = reader[columnName];
269 | var propertyType = mappedProperty.Member.GetMemberType();
270 |
271 | try
272 | {
273 | value = ParseDbField(value, propertyType);
274 |
275 | var propertyItem = GetMemberItem(item, mappedProperty, properties);
276 |
277 | if (mappedProperty.Member.MemberType == MemberTypes.Property)
278 | {
279 | (mappedProperty.Member as PropertyInfo).SetValue(propertyItem, value);
280 | }
281 | else
282 | {
283 | // https://social.msdn.microsoft.com/Forums/vstudio/en-US/33284e33-d004-4b76-bc0f-50100ec46bf1/fieldinfosetvalue-dont-work-in-struct?forum=csharpgeneral
284 | object obj_ref = propertyItem;
285 | (mappedProperty.Member as FieldInfo).SetValue(obj_ref, value);
286 | propertyItem = (T)obj_ref;
287 | if (mappedProperty.Parent == null)
288 | item = (T)obj_ref;
289 | }
290 | }
291 | catch (Exception exc)
292 | {
293 | throw new InvalidCastException($"Could not cast database type {value?.GetType().FullName} into the property type {propertyType.FullName} for property name {typeof(T).Name}.{mappedProperty.Member.Name}", exc);
294 | }
295 | }
296 | }
297 | }
298 |
299 | result.Add(item);
300 | }
301 |
302 | return result.ToArray();
303 | }
304 | }
305 |
306 | private static Type GetMemberType(this MemberInfo member)
307 | {
308 | return member.MemberType == MemberTypes.Property
309 | ? (member as PropertyInfo).PropertyType
310 | : (member as FieldInfo).FieldType;
311 | }
312 |
313 | ///
314 | /// Gets the current instance of the given member of the root item
315 | ///
316 | private static object GetMemberItem(object rootItem, (MemberInfo Member, MemberInfo Parent) member, IDictionary allProperties)
317 | {
318 | if (member.Member == null)
319 | throw new ArgumentNullException();
320 |
321 | // If the parent member doesn't exist, then the current instance is the root item
322 | if (member.Parent == null)
323 | {
324 | return rootItem
325 | ?? Activator.CreateInstance(member.Member.GetMemberType(), true);
326 | }
327 | else
328 | {
329 | // Find parent member
330 | var parentMember = allProperties.Single(x => x.Value.Member == member.Parent);
331 |
332 | // Currently only one level is available. TODO: Write Unit test and make it work for more than one level
333 | var item = parentMember.Value.Member is PropertyInfo
334 | ? (parentMember.Value.Member as PropertyInfo).GetValue(rootItem)
335 | : (parentMember.Value.Member as FieldInfo).GetValue(rootItem);
336 |
337 | // Create an instance of the parent's member if it is null
338 | if (item == null)
339 | {
340 | if (parentMember.Value.Member.MemberType == MemberTypes.Property)
341 | {
342 | var parentMemberType = (parentMember.Value.Member as PropertyInfo).PropertyType;
343 | item = Activator.CreateInstance(parentMemberType, true);
344 | (parentMember.Value.Member as PropertyInfo).SetValue(rootItem, item);
345 | }
346 | else // Tuple
347 | {
348 | var parentMemberType = (parentMember.Value.Member as FieldInfo).FieldType;
349 | item = Activator.CreateInstance(parentMemberType, true);
350 | (parentMember.Value.Member as FieldInfo).SetValue(rootItem, item);
351 | }
352 | }
353 |
354 | // Return the current instance of the parent member
355 | if (parentMember.Value.Parent == null)
356 | {
357 | return item;
358 | }
359 | else
360 | {
361 | // If there are other parents down to the hierarhy, make sure all of them are not null.
362 | GetMemberItem(item, parentMember.Value, allProperties);
363 | return item;
364 | }
365 | }
366 | }
367 |
368 | ///
369 | /// Gets all available properties of the model including child classes and their properties
370 | ///
371 | ///
372 | /// IDictionary<"Full_Member_Path", (MemberInfo Member, MemberInfo Parent)>
373 | ///
374 | private static IDictionary GetPropertiesOfModel(Type modelType)
375 | {
376 | if (modelType == null)
377 | throw new ArgumentNullException(nameof(modelType));
378 |
379 | IDictionary result = null;
380 |
381 | if (modelType.Name.StartsWith("ValueTuple`"))
382 | {
383 | result = modelType.GetFields().ToDictionary(x => x.Name, x => (Member: (MemberInfo)x, Parent: (MemberInfo)null), StringComparer.InvariantCultureIgnoreCase);
384 | }
385 | else
386 | {
387 | result = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
388 | foreach (var property in modelType.GetWritableProperties())
389 | {
390 | result.Add(property.Name, (Member: (MemberInfo)property, Parent: (MemberInfo)null));
391 |
392 | if (property.PropertyType.Name.StartsWith("ValueTuple`") ||
393 | (property.PropertyType.IsClass && property.PropertyType != typeof(string) && !typeof(IEnumerable).IsAssignableFrom(property.PropertyType)))
394 | {
395 | var children = GetPropertiesOfModel(property.PropertyType);
396 | foreach (var child in children)
397 | {
398 | result.Add(property.Name + "_" + child.Key, (Member: child.Value.Member, Parent: (MemberInfo)property));
399 | }
400 | }
401 | }
402 | }
403 |
404 | return result;
405 | }
406 |
407 | private static object ParseDbField(object value, Type toType)
408 | {
409 | if (value is DBNull)
410 | value = GetDefaultValue(toType);
411 |
412 | if (value != null)
413 | {
414 | if (toType.IsEnum || toType.GetNullableUnderlyingType().IsEnum)
415 | {
416 | value = Enum.Parse(toType.IsEnum ? toType : toType.GetNullableUnderlyingType(), value.ToString(), true);
417 | }
418 | else
419 | {
420 | value = value.ConvertType(toType);
421 | }
422 | }
423 |
424 | return value;
425 | }
426 |
427 | private static object ConvertType(this object obj, Type to)
428 | {
429 | var from = obj.GetType().GetNullableUnderlyingType();
430 |
431 | var u_to = to.GetNullableUnderlyingType();
432 |
433 | if (from == u_to)
434 | return obj;
435 |
436 | // Sqlite has dates in string format
437 | if (u_to == typeof(DateTime) && from == typeof(string))
438 | return Convert.ToDateTime(obj);
439 |
440 | return Convert.ChangeType(obj, u_to);
441 | }
442 |
443 | private static PropertyInfo[] GetWritableProperties(this Type type)
444 | {
445 | return type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
446 | }
447 |
448 | private static Type GetNullableUnderlyingType(this Type type)
449 | {
450 | if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
451 | return Nullable.GetUnderlyingType(type);
452 | else
453 | return type;
454 | }
455 |
456 | private static object GetDefaultValue(this Type t)
457 | {
458 | if (t.IsValueType && Nullable.GetUnderlyingType(t) == null)
459 | return Activator.CreateInstance(t);
460 | else
461 | return null;
462 | }
463 |
464 | private static DbConnection GetOpenConnection(this DbContext dbContext)
465 | {
466 | var connection = dbContext.Database.GetDbConnection();
467 |
468 | if (connection.State != ConnectionState.Open)
469 | connection.Open();
470 |
471 | return connection;
472 | }
473 |
474 | #endregion Private Methods
475 | }
476 | }
--------------------------------------------------------------------------------
/src/FluentSqlKata.EFCore/FluentSqlKata.EFCore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | disable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/FluentSqlKata.Tests/Entities/BaseEntity.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace FluentSqlKata.Tests.Entities
4 | {
5 | public class BaseEntity
6 | {
7 | protected BaseEntity()
8 | {
9 |
10 | }
11 |
12 | [Key]
13 | public virtual int Id { get; protected set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/FluentSqlKata.Tests/Entities/BirdWithSchema.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations.Schema;
2 |
3 | namespace FluentSqlKata.Tests.Entities
4 | {
5 | [Table("Birds", Schema = "OtherDatabase.dbo")]
6 | public class BirdWithSchema : BaseEntity
7 | {
8 | protected BirdWithSchema() { }
9 |
10 | public BirdWithSchema(string name)
11 | {
12 | SetName(name);
13 | }
14 |
15 | #region Properties
16 |
17 | public virtual string Name { get; protected set; }
18 |
19 | #endregion Properties
20 |
21 | #region Methods
22 |
23 | public virtual void SetName(string name)
24 | {
25 | if (string.IsNullOrWhiteSpace(name))
26 | throw new ArgumentNullException(nameof(name));
27 |
28 | Name = name;
29 | }
30 |
31 | #endregion Methods
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/FluentSqlKata.Tests/Entities/Contact.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations.Schema;
2 |
3 | namespace FluentSqlKata.Tests.Entities
4 | {
5 | [Table("Contacts")]
6 | public class Contact : BaseEntity
7 | {
8 | protected Contact() : base()
9 | {
10 |
11 | }
12 |
13 | public Contact(string firstName, string lastName) : this()
14 | {
15 | SetName(firstName, lastName);
16 | }
17 |
18 | #region Properties
19 |
20 | public virtual string FirstName { get; protected set; }
21 |
22 | public virtual string LastName { get; protected set; }
23 |
24 | public virtual int Age { get; set; }
25 |
26 | [Column("contact_customer_id")]
27 | public virtual int CustomerId { get; set; }
28 |
29 | #endregion Properties
30 |
31 | #region Public Methods
32 |
33 | public virtual void SetName(string firstName, string lastName)
34 | {
35 | if (string.IsNullOrWhiteSpace(firstName))
36 | throw new ArgumentNullException(nameof(firstName));
37 |
38 | FirstName = firstName;
39 |
40 | if (string.IsNullOrWhiteSpace(firstName))
41 | throw new ArgumentNullException(nameof(firstName));
42 |
43 | LastName = lastName;
44 | }
45 |
46 | #endregion Public Methods
47 |
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/FluentSqlKata.Tests/Entities/Customer.cs:
--------------------------------------------------------------------------------
1 | namespace FluentSqlKata.Tests.Entities
2 | {
3 | public class Customer : BaseEntity
4 | {
5 | protected Customer() : base()
6 | {
7 |
8 | }
9 |
10 | public Customer(string name) : this()
11 | {
12 | SetName(name);
13 | }
14 |
15 | #region Properties
16 |
17 | public virtual string Name { get; protected set; }
18 |
19 | #endregion Properties
20 |
21 | #region Public Methods
22 |
23 | public virtual void SetName(string name)
24 | {
25 | if (string.IsNullOrWhiteSpace(name))
26 | throw new ArgumentNullException(nameof(name));
27 |
28 | Name = name;
29 | }
30 |
31 | #endregion Public Methods
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/FluentSqlKata.Tests/FluentQueryTests.cs:
--------------------------------------------------------------------------------
1 | using FluentSqlKata.Tests.Entities;
2 | using FluentSqlKata.Tests.Models;
3 | using SqlKata.Compilers;
4 |
5 | namespace FluentSqlKata.Tests
6 | {
7 | public class FluentQueryTests
8 | {
9 | [Fact]
10 | public void T01_Basic()
11 | {
12 | // Table from class
13 | {
14 | Customer myCust = null;
15 | (string CustomerId, string CustomerName) result = default;
16 |
17 | var query = FluentQuery.Query()
18 | .From(() => myCust)
19 | .Select(() => result.CustomerId, () => myCust.Id)
20 | .Select(() => result.CustomerName, () => myCust.Name)
21 | ;
22 |
23 | var query_str = new SqlServerCompiler().Compile(query).ToString();
24 |
25 | Assert.NotNull(query_str);
26 | Assert.Equal("SELECT [myCust].[Id] AS [Item1], [myCust].[Name] AS [Item2] FROM [Customer] AS [myCust]", query_str);
27 | }
28 | // Custom table name
29 | {
30 | Customer myCust = null;
31 | (string CustomerId, string CustomerName) result = default;
32 |
33 | var query = FluentQuery.Query()
34 | .From("MyCustomers", () => myCust)
35 | .Select(() => result.CustomerId, () => myCust.Id)
36 | .Select(() => result.CustomerName, () => myCust.Name)
37 | ;
38 |
39 | var query_str = new SqlServerCompiler().Compile(query).ToString();
40 |
41 | Assert.NotNull(query_str);
42 | Assert.Equal("SELECT [myCust].[Id] AS [Item1], [myCust].[Name] AS [Item2] FROM [MyCustomers] AS [myCust]", query_str);
43 | }
44 | }
45 |
46 | [Fact]
47 | public void T02_Where()
48 | {
49 | Customer myCust = null;
50 | (string CustomerId, string CustomerName) result = default;
51 |
52 | var query = FluentQuery.Query()
53 | .From(() => myCust)
54 | .Select(() => result.CustomerId, () => myCust.Id)
55 | .Select(() => result.CustomerName, () => myCust.Name)
56 | .Where((q) => q.Where(() => myCust.Name, "John").OrWhereContains(() => myCust.Name, "oh"))
57 | ;
58 |
59 | var query_str = new SqlServerCompiler().Compile(query).ToString();
60 |
61 | Assert.NotNull(query_str);
62 | Assert.Equal("SELECT [myCust].[Id] AS [Item1], [myCust].[Name] AS [Item2] FROM [Customer] AS [myCust] WHERE ([myCust].[Name] = 'John' OR LOWER([myCust].[Name]) like '%oh%')", query_str);
63 | }
64 |
65 | [Fact]
66 | public void T03_Join()
67 | {
68 | // Table name from class
69 | {
70 | Contact myCont = null;
71 | Customer myCust = null;
72 | (string FirstName, string LastName, string CustomerId, string CustomerName) result = default;
73 |
74 | var query = FluentQuery.Query()
75 | .From(() => myCont)
76 | .Join(() => myCust, () => myCust.Id, () => myCont.CustomerId)
77 | .Select(() => result.FirstName, () => myCont.FirstName)
78 | .Select(() => result.LastName, () => myCont.LastName)
79 | .Select(() => result.CustomerId, () => myCont.CustomerId)
80 | .Select(() => result.CustomerName, () => myCust.Name)
81 | ;
82 |
83 | var query_str = new SqlServerCompiler().Compile(query).ToString();
84 |
85 | Assert.NotNull(query_str);
86 | Assert.Equal("SELECT [myCont].[FirstName] AS [Item1], [myCont].[LastName] AS [Item2], [myCont].[contact_customer_id] AS [Item3], [myCust].[Name] AS [Item4] FROM [Contacts] AS [myCont] \nINNER JOIN [Customer] AS [myCust] ON [myCust].[Id] = [myCont].[contact_customer_id]", query_str);
87 | }
88 | // Custom table name
89 | {
90 | Contact myCont = null;
91 | Customer myCust = null;
92 | (string FirstName, string LastName, string CustomerId, string CustomerName) result = default;
93 |
94 | var query = FluentQuery.Query()
95 | .From(() => myCont)
96 | .Join("MyCustomer", () => myCust, () => myCust.Id, () => myCont.CustomerId)
97 | .Select(() => result.FirstName, () => myCont.FirstName)
98 | .Select(() => result.LastName, () => myCont.LastName)
99 | .Select(() => result.CustomerId, () => myCont.CustomerId)
100 | .Select(() => result.CustomerName, () => myCust.Name)
101 | ;
102 |
103 | var query_str = new SqlServerCompiler().Compile(query).ToString();
104 |
105 | Assert.NotNull(query_str);
106 | Assert.Equal("SELECT [myCont].[FirstName] AS [Item1], [myCont].[LastName] AS [Item2], [myCont].[contact_customer_id] AS [Item3], [myCust].[Name] AS [Item4] FROM [Contacts] AS [myCont] \nINNER JOIN [MyCustomer] AS [myCust] ON [myCust].[Id] = [myCont].[contact_customer_id]", query_str);
107 | }
108 | }
109 |
110 | [Fact]
111 | public void T04_JoinBuilder()
112 | {
113 | Contact myCont = null;
114 | Customer myCust = null;
115 | (string FirstName, string LastName, string CustomerId) result = default;
116 |
117 | var query = FluentQuery.Query()
118 | .From(() => myCont)
119 | .Join(() => myCust, (join) => join.WhereColumns(() => myCust.Id, () => myCont.CustomerId))
120 | .Select(() => result.FirstName, () => myCont.FirstName)
121 | .Select(() => result.LastName, () => myCont.LastName)
122 | .Select(() => result.CustomerId, () => myCust.Id)
123 | ;
124 |
125 | var query_str = new SqlServerCompiler().Compile(query).ToString();
126 |
127 | Assert.NotNull(query_str);
128 | Assert.Equal("SELECT [myCont].[FirstName] AS [Item1], [myCont].[LastName] AS [Item2], [myCust].[Id] AS [Item3] FROM [Contacts] AS [myCont] \nINNER JOIN [Customer] AS [myCust] ON ([myCust].[Id] = [myCont].[contact_customer_id])", query_str);
129 | }
130 |
131 | [Fact]
132 | public void T05_GroupBy()
133 | {
134 | Customer myCust = null;
135 | Contact myCont = null;
136 |
137 | (string CustomerName, int ContactCount) result = default;
138 |
139 | var query = FluentQuery.Query()
140 | .From(() => myCont)
141 | .Join(() => myCust, (join) => join.WhereColumns(() => myCust.Id, () => myCont.CustomerId))
142 | .SelectCount(() => result.ContactCount, () => myCont.Id)
143 | .Select(() => result.CustomerName, () => myCust.Name)
144 | .GroupBy(() => myCust.Name)
145 | ;
146 |
147 | var query_str = new SqlServerCompiler().Compile(query).ToString();
148 |
149 | Assert.NotNull(query_str);
150 | Assert.Equal("SELECT COUNT(myCont.Id) AS Item2, [myCust].[Name] AS [Item1] FROM [Contacts] AS [myCont] \nINNER JOIN [Customer] AS [myCust] ON ([myCust].[Id] = [myCont].[contact_customer_id]) GROUP BY [myCust].[Name]", query_str);
151 | }
152 |
153 | [Fact]
154 | public void T06_OrderBy()
155 | {
156 | Customer myCust = null;
157 |
158 | var query = FluentQuery.Query()
159 | .SelectAll(() => myCust)
160 | .OrderByColumn(() => myCust.Name)
161 | ;
162 |
163 | var query_str = new SqlServerCompiler().Compile(query).ToString();
164 |
165 | Assert.NotNull(query_str);
166 | Assert.Equal("SELECT [myCust].[Name] AS [Name], [myCust].[Id] AS [Id] FROM [Customer] AS [myCust] ORDER BY [myCust].[Name]", query_str);
167 | }
168 |
169 | [Fact]
170 | public void T07_OrderByAlias()
171 | {
172 | Customer myCust = null;
173 | CustomerModel model = null;
174 |
175 | var query = FluentQuery.Query()
176 | .From(() => myCust)
177 | .SelectRawFormat(() => model.Name, "ISNULL({0}, 'Uknown')", () => myCust.Name)
178 | .OrderByAlias(() => model.Name)
179 | ;
180 |
181 | var query_str = new SqlServerCompiler().Compile(query).ToString();
182 |
183 | Assert.NotNull(query_str);
184 | Assert.Equal("SELECT ISNULL(myCust.Name, 'Uknown') AS Name FROM [Customer] AS [myCust] ORDER BY ISNULL(myCust.Name, 'Uknown')", query_str);
185 | }
186 |
187 | [Fact]
188 | public void T08_SkipTake()
189 | {
190 | Customer myCust = null;
191 |
192 | var query = FluentQuery.Query()
193 | .Distinct()
194 | .SelectAll(() => myCust)
195 | .Skip(10)
196 | .Take(20)
197 | ;
198 |
199 | var query_str = new SqlServerCompiler().Compile(query).ToString();
200 |
201 | Assert.NotNull(query_str);
202 | // TODO: https://github.com/sqlkata/querybuilder/issues/643#issuecomment-1709879159
203 | Assert.Equal("SELECT DISTINCT [myCust].[Name] AS [Name], [myCust].[Id] AS [Id] FROM [Customer] AS [myCust] ORDER BY (SELECT 0) OFFSET 10 ROWS FETCH NEXT 20 ROWS ONLY", query_str);
204 | }
205 |
206 | [Fact]
207 | public void T09_PerPage()
208 | {
209 | Customer myCust = null;
210 |
211 | var query = FluentQuery.Query()
212 | .Distinct()
213 | .SelectAll(() => myCust)
214 | .ForPage(2, 10)
215 | ;
216 |
217 | var query_str = new SqlServerCompiler().Compile(query).ToString();
218 |
219 | Assert.NotNull(query_str);
220 | // TODO: https://github.com/sqlkata/querybuilder/issues/643#issuecomment-1709879159
221 | Assert.Equal("SELECT DISTINCT [myCust].[Name] AS [Name], [myCust].[Id] AS [Id] FROM [Customer] AS [myCust] ORDER BY (SELECT 0) OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY", query_str);
222 | }
223 |
224 | [Fact]
225 | public void T10_Schema()
226 | {
227 | BirdWithSchema bird = null;
228 |
229 | var query = FluentQuery.Query()
230 | .SelectAll(() => bird)
231 | ;
232 |
233 | var query_str = new SqlServerCompiler().Compile(query).ToString();
234 |
235 | Assert.NotNull(query_str);
236 | Assert.Equal("SELECT [bird].[Name] AS [Name], [bird].[Id] AS [Id] FROM [OtherDatabase].[dbo].[Birds] AS [bird]", query_str);
237 | }
238 |
239 | [Fact]
240 | public void T11_NestedFields()
241 | {
242 | Contact cnt = null;
243 |
244 | NestedContactModel model = null;
245 |
246 | var query = FluentQuery.Query(() => cnt)
247 | .Select(() => model.FirstName, () => cnt.FirstName)
248 | .Select(() => model.LastName, () => cnt.LastName)
249 | .Select(() => model.Initials.FirstName, () => cnt.FirstName)
250 | .Select(() => model.Initials.LastName, () => cnt.LastName)
251 | ;
252 |
253 | var query_str = new SqlServerCompiler().Compile(query).ToString();
254 |
255 | Assert.NotNull(query_str);
256 | Assert.Equal("SELECT [cnt].[FirstName] AS [FirstName], [cnt].[LastName] AS [LastName], [cnt].[FirstName] AS [Initials_FirstName], [cnt].[LastName] AS [Initials_LastName] FROM [Contacts] AS [cnt]", query_str);
257 | }
258 |
259 | [Fact]
260 | public void T12_FormattedRawQuery()
261 | {
262 | Contact cnt = null;
263 |
264 | var query = FluentQuery.Query(() => cnt)
265 | .SelectRawFormat("FullName", queryFormat: "ISNULL({0}, ?) + ' ' + ISNULL({1}, ?)",
266 | new[] { FluentQuery.Expression(() => cnt.FirstName), FluentQuery.Expression(() => cnt.LastName) },
267 | new[] { "John", "Smith" })
268 | .SelectRawFormat("Age", queryFormat: "{0} + 'y'", () => cnt.Age)
269 | .WhereRawFormat("{0} LIKE ?", columns: new[] { FluentQuery.Expression(() => cnt.FirstName) }, bindings: new[] { "John" })
270 | .If(true,
271 | q => q.Where(() => cnt.Age, 12, ">"),
272 | q => q.Where(() => cnt.Age, 16, ">"))
273 | ;
274 |
275 | var query_str = new SqlServerCompiler().Compile(query).ToString();
276 |
277 | Assert.NotNull(query_str);
278 | Assert.Equal("SELECT ISNULL(cnt.FirstName, 'John') + ' ' + ISNULL(cnt.LastName, 'Smith') AS FullName, cnt.Age + 'y' AS Age FROM [Contacts] AS [cnt] WHERE cnt.FirstName LIKE 'John' AND [cnt].[Age] > 12", query_str);
279 | }
280 |
281 | [Fact]
282 | public void T13_ConditionalQuery()
283 | {
284 | Contact cnt = null;
285 |
286 | var queryBuilder = (bool ageCheck) =>
287 | {
288 | return FluentQuery.Query(() => cnt)
289 | .Select(() => cnt.FirstName)
290 | .Select(() => cnt.LastName)
291 | .If(ageCheck,
292 | q => q.Where(() => cnt.Age, 18, ">"),
293 | q => q.Where(() => cnt.Age, 16, ">"))
294 | .If(ageCheck, q => q.Where(() => cnt.Age, 60, "<="))
295 | ;
296 | };
297 |
298 | // True
299 | {
300 | var query = queryBuilder(true);
301 |
302 | var query_str = new SqlServerCompiler().Compile(query).ToString();
303 |
304 | Assert.NotNull(query_str);
305 | Assert.Equal("SELECT cnt.FirstName, cnt.LastName FROM [Contacts] AS [cnt] WHERE [cnt].[Age] > 18 AND [cnt].[Age] <= 60", query_str);
306 | }
307 |
308 | // False
309 | {
310 | var query = queryBuilder(false);
311 |
312 | var query_str = new SqlServerCompiler().Compile(query).ToString();
313 |
314 | Assert.NotNull(query_str);
315 | Assert.Equal("SELECT cnt.FirstName, cnt.LastName FROM [Contacts] AS [cnt] WHERE [cnt].[Age] > 16", query_str);
316 | }
317 | }
318 |
319 | [Fact]
320 | public void T14_HelperFunctions()
321 | {
322 | Contact cnt = null;
323 | NestedContactModel model = null;
324 |
325 | // Column name
326 | {
327 | var result = FluentQuery.Column(() => cnt.CustomerId);
328 |
329 | Assert.Equal("contact_customer_id", result);
330 | }
331 |
332 | // Column name
333 | {
334 | var result = FluentQuery.ColumnWithAlias(() => cnt.CustomerId);
335 |
336 | Assert.Equal("cnt.contact_customer_id", result);
337 | }
338 |
339 | // Column with alias name
340 | {
341 | var result = FluentQuery.AliasFromColumn(() => cnt.CustomerId);
342 |
343 | Assert.Equal("cnt", result);
344 | }
345 |
346 | // Alias name
347 | {
348 | var result = FluentQuery.Alias(() => model.FirstName);
349 |
350 | Assert.Equal("FirstName", result);
351 | }
352 |
353 | // Table name
354 | {
355 | var result = FluentQuery.Table();
356 |
357 | Assert.Equal("Contacts", result);
358 | }
359 |
360 | // Table name
361 | {
362 | var result = FluentQuery.Table(() => cnt);
363 |
364 | Assert.Equal("Contacts", result);
365 | }
366 | }
367 | }
368 | }
--------------------------------------------------------------------------------
/src/FluentSqlKata.Tests/FluentSqlKata.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | disable
7 |
8 | false
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 | all
21 |
22 |
23 | runtime; build; native; contentfiles; analyzers; buildtransitive
24 | all
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/FluentSqlKata.Tests/Helpers/AssertHelper.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System.Collections;
3 | using System.Linq.Expressions;
4 | using Xunit.Sdk;
5 |
6 | namespace FluentSqlKata.Tests.Helpers
7 | {
8 | public static class AssertHelper
9 | {
10 | ///
11 | /// Check whether all elements are included into the collection. Order does not matter.
12 | ///
13 | public static void CollectionContainsAll(IEnumerable collectionToTest, params Expression>[] inspectors)
14 | {
15 | var expectedLength = inspectors.Count();
16 | var actualLength = collectionToTest.Count();
17 |
18 | if (expectedLength != actualLength)
19 | throw CollectionException.ForMismatchedItemCount(expectedLength, actualLength, JsonConvert.SerializeObject(collectionToTest));
20 |
21 | var all = new List(collectionToTest);
22 |
23 | foreach (var inspector in inspectors)
24 | {
25 | var foundElement = all.Where(inspector.Compile()).ToArray();
26 |
27 | if (foundElement.Length == 0)
28 | throw ContainsException.ForCollectionItemNotFound(inspector.ToStringExpression(), JsonConvert.SerializeObject(collectionToTest));
29 |
30 | all.Remove(foundElement[0]);
31 | }
32 |
33 | if (all.Any())
34 | throw ContainsException.ForCollectionFilterNotMatched(JsonConvert.SerializeObject(all));
35 | }
36 |
37 | ///
38 | /// Check whether all elements are included into the collection. Order does matter.
39 | ///
40 | public static void CollectionContainsOrdered(IEnumerable collectionToTest, params Expression>[] inspectors)
41 | {
42 | var expectedLength = inspectors.Count();
43 | var actualLength = collectionToTest.Count();
44 |
45 | if (expectedLength != actualLength)
46 | throw CollectionException.ForMismatchedItemCount(expectedLength, actualLength, JsonConvert.SerializeObject(collectionToTest));
47 |
48 | for (int i = 0; i < expectedLength; i++)
49 | {
50 | if (!collectionToTest.Skip(i).Take(1).All(inspectors[i].Compile()))
51 | throw ContainsException.ForCollectionItemNotFound(inspectors[i].ToStringExpression(), JsonConvert.SerializeObject(collectionToTest));
52 | }
53 | }
54 |
55 | ///
56 | /// Check whether any elements are contained into the collection. Order does not matter.
57 | ///
58 | public static void CollectionContainsAny(IEnumerable collectionToTest, params Expression>[] inspectors)
59 | {
60 | foreach (var inspector in inspectors)
61 | {
62 | var foundElement = collectionToTest.Where(inspector.Compile()).ToArray();
63 |
64 | if (foundElement.Length == 0)
65 | throw ContainsException.ForCollectionItemNotFound(inspector.ToStringExpression(), JsonConvert.SerializeObject(collectionToTest));
66 | }
67 | }
68 |
69 | ///
70 | /// Check whether any elements are not contained into the collection.
71 | ///
72 | public static void CollectionDoesNotContain(IEnumerable collectionToTest, params Expression>[] inspectors)
73 | {
74 | foreach (var (inspector, index) in inspectors.WithIndex())
75 | {
76 | var foundElement = collectionToTest.Where(inspector.Compile()).ToArray();
77 | if (foundElement.Length > 0)
78 | throw DoesNotContainException.ForCollectionItemFound(inspector.ToStringExpression(), index, null, JsonConvert.SerializeObject(collectionToTest));
79 | }
80 | }
81 |
82 | public static string ToStringExpression(this Expression> exp)
83 | {
84 | string expBody = ((LambdaExpression)exp).Body.ToString();
85 | // Gives: ((x.Id > 5) AndAlso (x.Warranty != False))
86 |
87 | var paramName = exp.Parameters[0].Name;
88 | var paramTypeName = exp.Parameters[0].Type.Name;
89 |
90 | // You could easily add "OrElse" and others...
91 | expBody = expBody.Replace(paramName + ".", paramTypeName + ".")
92 | .Replace("AndAlso", "&&");
93 |
94 | return expBody;
95 | }
96 |
97 | public static void NullOrEmpty(IEnumerable array)
98 | {
99 | if (array == null)
100 | Assert.Null(array);
101 | else
102 | Assert.Empty(array);
103 | }
104 |
105 | public static void NotNullAndEmpty(IEnumerable array)
106 | {
107 | Assert.NotNull(array);
108 | Assert.NotEmpty(array);
109 | }
110 |
111 | public static IEnumerable<(T item, int index)> WithIndex(this IEnumerable source)
112 | {
113 | return source.Select((item, index) => (item, index));
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/FluentSqlKata.Tests/Models/CustomerModel.cs:
--------------------------------------------------------------------------------
1 | namespace FluentSqlKata.Tests.Models
2 | {
3 | public class CustomerModel
4 | {
5 | public string Name { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/FluentSqlKata.Tests/Models/NestedContactModel.cs:
--------------------------------------------------------------------------------
1 | namespace FluentSqlKata.Tests.Models
2 | {
3 | public class NestedContactModel
4 | {
5 | public string FirstName { get; set; }
6 |
7 | public string LastName { get; set; }
8 |
9 | public Initials Initials { get; set; }
10 | }
11 |
12 | public class Initials
13 | {
14 | public string FirstName { get; set; }
15 |
16 | public string LastName { get; set; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/FluentSqlKata.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.2.32630.192
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentSqlKata", "FluentSqlKata\FluentSqlKata.csproj", "{903C89AA-9DE1-43FF-8451-2A77EE9BCD02}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentSqlKata.Tests", "FluentSqlKata.Tests\FluentSqlKata.Tests.csproj", "{CBA6BDB5-AAC8-48C2-BDF4-18254FB52D09}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentSqlKata.EFCore", "FluentSqlKata.EFCore\FluentSqlKata.EFCore.csproj", "{8A3A3FE0-F91D-415C-903E-CBB6D451C53D}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {903C89AA-9DE1-43FF-8451-2A77EE9BCD02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {903C89AA-9DE1-43FF-8451-2A77EE9BCD02}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {903C89AA-9DE1-43FF-8451-2A77EE9BCD02}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {903C89AA-9DE1-43FF-8451-2A77EE9BCD02}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {CBA6BDB5-AAC8-48C2-BDF4-18254FB52D09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {CBA6BDB5-AAC8-48C2-BDF4-18254FB52D09}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {CBA6BDB5-AAC8-48C2-BDF4-18254FB52D09}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {CBA6BDB5-AAC8-48C2-BDF4-18254FB52D09}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {8A3A3FE0-F91D-415C-903E-CBB6D451C53D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {8A3A3FE0-F91D-415C-903E-CBB6D451C53D}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {8A3A3FE0-F91D-415C-903E-CBB6D451C53D}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {8A3A3FE0-F91D-415C-903E-CBB6D451C53D}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {B892F75D-B942-4F8C-B849-734FC2A219BF}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/src/FluentSqlKata/FluentQuery.cs:
--------------------------------------------------------------------------------
1 | using SqlKata;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Linq.Expressions;
6 | using System.Reflection;
7 |
8 | namespace FluentSqlKata
9 | {
10 | public static class FluentQuery
11 | {
12 | #region Query/From
13 |
14 | public static Query Query()
15 | {
16 | return new FluentQueryWrapper();
17 | }
18 |
19 | public static Query Query()
20 | {
21 | return new FluentQueryWrapper($"{Table()}");
22 | }
23 |
24 | public static Query Query(Expression> alias)
25 | {
26 | return Query(Table(), alias);
27 | }
28 |
29 | public static Query Query(string table, Expression> alias)
30 | {
31 | return new FluentQueryWrapper($"{table} AS {Alias(alias)}");
32 | }
33 |
34 | public static Query From(this Query query)
35 | {
36 | return query.From($"{Table()}");
37 | }
38 |
39 | public static Join From(this Join query)
40 | {
41 | return query.From($"{Table()}");
42 | }
43 |
44 | public static Q From(this Q query, Expression> alias) where Q : BaseQuery
45 | {
46 | return query.From(Table(), alias);
47 | }
48 |
49 | public static Q From(this Q query, string table, Expression> alias) where Q : BaseQuery
50 | {
51 | return query.From($"{table} AS {Alias(alias)}");
52 | }
53 |
54 | public static Q FromRawFormat(this Q query, string queryFormat, params Expression>[] columns) where Q : BaseQuery
55 | {
56 | var queryRaw = FormatQueryRaw(queryFormat, columns: columns);
57 | return query.FromRaw(queryRaw);
58 | }
59 |
60 | public static Q FromRawFormat(this Q query, string queryFormat, Expression>[] columns, object[] bindings) where Q : BaseQuery
61 | {
62 | var queryRaw = FormatQueryRaw(queryFormat, columns: columns);
63 | return query.FromRaw(queryRaw, bindings: bindings);
64 | }
65 |
66 | public static Query As(this Query query, Expression> alias)
67 | {
68 | var aliasName = Alias(alias);
69 | return query.As(aliasName);
70 | }
71 |
72 | public static Query WithRawFormat(this Query query, Expression> alias, string queryFormat, params Expression>[] columns)
73 | {
74 | var queryRaw = FormatQueryRaw(queryFormat, columns: columns);
75 | return query.WithRaw(Alias(alias), queryRaw);
76 | }
77 |
78 | public static Query WithRawFormat(this Query query, Expression> alias, string queryFormat, Expression>[] columns, object[] bindings)
79 | {
80 | var queryRaw = FormatQueryRaw(queryFormat, columns: columns);
81 | return query.WithRaw(Alias(alias), queryRaw, bindings: bindings);
82 | }
83 |
84 | public static Query CombineRawFormat(this Query query, string queryFormat, params Expression>[] columns)
85 | {
86 | var queryRaw = FormatQueryRaw(queryFormat, columns: columns);
87 | return query.CombineRaw(queryRaw);
88 | }
89 |
90 | public static Query CombineRawFormat(this Query query, string queryFormat, Expression>[] columns, object[] bindings)
91 | {
92 | var queryRaw = FormatQueryRaw(queryFormat, columns: columns);
93 | return query.CombineRaw(queryRaw, bindings: bindings);
94 | }
95 |
96 | public static Query ExceptRawFormat(this Query query, string queryFormat, params Expression>[] columns)
97 | {
98 | var queryRaw = FormatQueryRaw(queryFormat, columns: columns);
99 | return query.ExceptRaw(queryRaw);
100 | }
101 |
102 | public static Query ExceptRawFormat(this Query query, string queryFormat, Expression>[] columns, object[] bindings)
103 | {
104 | var queryRaw = FormatQueryRaw(queryFormat, columns: columns);
105 | return query.ExceptRaw(queryRaw, bindings: bindings);
106 | }
107 |
108 | public static Query IntersectRawFormat(this Query query, string queryFormat, params Expression>[] columns)
109 | {
110 | var queryRaw = FormatQueryRaw(queryFormat, columns: columns);
111 | return query.IntersectRaw(queryRaw);
112 | }
113 |
114 | public static Query IntersectRawFormat(this Query query, string queryFormat, Expression>[] columns, object[] bindings)
115 | {
116 | var queryRaw = FormatQueryRaw(queryFormat, columns: columns);
117 | return query.IntersectRaw(queryRaw, bindings: bindings);
118 | }
119 |
120 | public static Query UnionRawFormat(this Query query, string queryFormat, params Expression>[] columns)
121 | {
122 | var queryRaw = FormatQueryRaw(queryFormat, columns: columns);
123 | return query.UnionRaw(queryRaw);
124 | }
125 |
126 | public static Query UnionRawFormat(this Query query, string queryFormat, Expression>[] columns, object[] bindings)
127 | {
128 | var queryRaw = FormatQueryRaw(queryFormat, columns: columns);
129 | return query.UnionRaw(queryRaw, bindings: bindings);
130 | }
131 |
132 | #endregion Query/From
133 |
134 | #region Selects
135 |
136 | public static Query Select(this Query query, Expression> alias, Query subquery)
137 | {
138 | var aliasName = Alias(alias);
139 | query.Select(subquery, aliasName);
140 | return query;
141 | }
142 |
143 | ///
144 | /// Example: SelectRawFormat(() => dto.FullName, queryFormat: "{0} + ' ' + {1}", () => cnt.FirstName, () => cnt.LastName)
145 | /// Results: SELECT FirstName + ' ' + LastName AS FullName
146 | ///
147 | public static Query SelectRawFormat(this Query query, Expression> alias, string queryFormat, params Expression>[] columns)
148 | {
149 | var aliasName = Alias(alias);
150 | return query.SelectRawFormat(aliasName, queryFormat, columns: columns);
151 | }
152 |
153 | ///
154 | /// Example: SelectRawFormat(() => dto.Name, queryFormat: "ISNULL({0}, ?)", new[] { FluentQuery.Expression(() => cnt.FirstName) }, new[] { "John" })
155 | /// Results: SELECT ISNULL(FirstName, 'John') AS Name
156 | ///
157 | public static Query SelectRawFormat(this Query query, Expression> alias, string queryFormat, Expression>[] columns, object[] bindings)
158 | {
159 | var aliasName = Alias(alias);
160 | return query.SelectRawFormat(aliasName, queryFormat, columns: columns, bindings: bindings);
161 | }
162 |
163 | ///
164 | /// Example: SelectRawFormat("FullName", queryFormat: "{0} + ' ' + {1}", () => cnt.FirstName, () => cnt.LastName)
165 | /// Results: SELECT FirstName + ' ' + LastName AS FullName
166 | ///
167 | public static Query SelectRawFormat(this Query query, string alias, string queryFormat, params Expression>[] columns)
168 | {
169 | var queryRaw = FormatQueryRaw(queryFormat, columns: columns);
170 | query.GetWrapper().SelectsRaw.Add(alias, queryRaw);
171 | query.SelectRaw($"{queryRaw} AS {alias}");
172 | return query;
173 | }
174 |
175 | ///
176 | /// Example: SelectRawFormat("Name", queryFormat: "ISNULL({0}, ?)", new[] { FluentQuery.Expression(() => cnt.FirstName) }, new[] { "John" })
177 | /// Results: SELECT ISNULL(FirstName, 'John') AS Name
178 | ///
179 | public static Query SelectRawFormat(this Query query, string alias, string queryFormat, Expression>[] columns, object[] bindings)
180 | {
181 | var queryRaw = FormatQueryRaw(queryFormat, columns: columns);
182 | query.GetWrapper().SelectsRaw.Add(alias, queryRaw);
183 | query.SelectRaw($"{queryRaw} AS {alias}", bindings: bindings);
184 | return query;
185 | }
186 |
187 | public static Query Select(this Query query, Expression> alias, Expression> column)
188 | {
189 | var aliasName = Alias(alias);
190 | return query.Select(aliasName, column);
191 | }
192 |
193 | public static Query Select(this Query query, string alias, Expression> column)
194 | {
195 | var columnName = $"{AliasFromColumn(column)}.{Property(column)}";
196 | query.GetWrapper().Selects.Add(alias, columnName);
197 | query.Select($"{columnName} AS {alias}");
198 | return query;
199 | }
200 |
201 | public static Query Select(this Query query, Expression> column)
202 | {
203 | var columnName = Property(column);
204 | var fullName = $"{AliasFromColumn(column)}.{columnName}";
205 | query.GetWrapper().Selects.Add(columnName, fullName);
206 | return query.SelectRaw(fullName);
207 | }
208 |
209 | public static Query SelectAll(this Query query)
210 | {
211 | query.From();
212 |
213 | foreach (var col in GetColumns())
214 | {
215 | var columnName = $"{col.Value}";
216 | query.GetWrapper().Selects.Add(col.Key, columnName);
217 | query.Select($"{columnName} AS {col.Key}");
218 | }
219 |
220 | return query;
221 | }
222 |
223 | public static Query SelectAll(this Query query, Expression> alias)
224 | {
225 | query.From(alias);
226 |
227 | foreach (var col in GetColumns())
228 | {
229 | var columnName = $"{Alias(alias)}.{col.Value}";
230 | query.GetWrapper().Selects.Add(col.Key, columnName);
231 | query.Select($"{columnName} AS {col.Key}");
232 | }
233 |
234 | return query;
235 | }
236 |
237 | public static Query SelectFunc(this Query query, Expression> alias, Expression> column, string func, bool aggregate = false)
238 | {
239 | var aliasName = Alias(alias);
240 | query.SelectFunc(aliasName, column, func, aggregate);
241 | return query;
242 | }
243 |
244 | public static Query SelectFunc(this Query query, string alias, Expression> column, string func, bool aggregate = false)
245 | {
246 | var columnName = $"{func}({AliasFromColumn(column)}.{Property(column)})";
247 | if (aggregate)
248 | query.GetWrapper().SelectAggrs.Add(alias, columnName);
249 | else
250 | query.GetWrapper().Selects.Add(alias, columnName);
251 | query.SelectRaw($"{columnName} AS {alias}");
252 | return query;
253 | }
254 |
255 | #endregion Selects
256 |
257 | #region Where
258 |
259 | ///
260 | /// Example: WhereRawFormat("(LEN({0}) + LEN({1})) > 0", () => cnt.FirstName, () => cnt.LastName)
261 | /// Results: WHERE (LEN(FirstName) + LEN(LastName)) > 0
262 | ///
263 | public static Q WhereRawFormat(this Q query, string queryFormat, params Expression>[] columns) where Q : BaseQuery
264 | {
265 | queryFormat = FormatQueryRaw(queryFormat, columns: columns);
266 | query.WhereRaw(queryFormat);
267 | return query;
268 | }
269 |
270 | ///
271 | /// Example: WhereRawFormat("(LEN({0}) + LEN({1})) > 0", () => cnt.FirstName, () => cnt.LastName)
272 | /// Results: WHERE (LEN(FirstName) + LEN(LastName)) > 0
273 | ///
274 | public static Q OrWhereRawFormat(this Q query, string queryFormat, params Expression>[] columns) where Q : BaseQuery
275 | {
276 | queryFormat = FormatQueryRaw(queryFormat, columns: columns);
277 | query.OrWhereRaw(queryFormat);
278 | return query;
279 | }
280 |
281 | ///
282 | /// Example: WhereRawFormat("LEN({0}) > ?", new[] { FluentQuery.Expression(() => cnt.FirstName) }, new[] { 5 })
283 | /// Results: WHERE LEN(FirstName) > 5
284 | ///
285 | public static Q WhereRawFormat(this Q query, string queryFormat, Expression>[] columns, object[] bindings) where Q : BaseQuery
286 | {
287 | queryFormat = FormatQueryRaw(queryFormat, columns: columns);
288 | query.WhereRaw(queryFormat, bindings: bindings);
289 | return query;
290 | }
291 |
292 | ///
293 | /// Example: WhereRawFormat("LEN({0}) > ?", new[] { FluentQuery.Expression(() => cnt.FirstName) }, new[] { 5 })
294 | /// Results: WHERE LEN(FirstName) > 5
295 | ///
296 | public static Q OrWhereRawFormat(this Q query, string queryFormat, Expression>[] columns, object[] bindings) where Q : BaseQuery
297 | {
298 | queryFormat = FormatQueryRaw(queryFormat, columns: columns);
299 | query.OrWhereRaw(queryFormat, bindings: bindings);
300 | return query;
301 | }
302 |
303 | public static Q Where(this Q query, Expression> column, object value, string op = "=") where Q : BaseQuery
304 | {
305 | query.Where($"{AliasFromColumn(column)}.{Property(column)}", op, value);
306 | return query;
307 | }
308 |
309 | public static Q OrWhere(this Q query, Expression> column, object value, string op = "=") where Q : BaseQuery
310 | {
311 | query.OrWhere($"{AliasFromColumn(column)}.{Property(column)}", op, value);
312 | return query;
313 | }
314 |
315 | public static Q WhereNot(this Q query, Expression> column, object value, string op = "=") where Q : BaseQuery
316 | {
317 | query.WhereNot($"{AliasFromColumn(column)}.{Property(column)}", op, value);
318 | return query;
319 | }
320 |
321 | public static Q OrWhereNot(this Q query, Expression> column, object value, string op = "=") where Q : BaseQuery
322 | {
323 | query.OrWhereNot($"{AliasFromColumn(column)}.{Property(column)}", op, value);
324 | return query;
325 | }
326 |
327 | public static Q WhereColumns(this Q query, Expression> column1, Expression> column2, string op = "=") where Q : BaseQuery
328 | {
329 | query.WhereColumns(
330 | $"{AliasFromColumn(column1)}.{Property(column1)}",
331 | op,
332 | $"{AliasFromColumn(column2)}.{Property(column2)}");
333 | return query;
334 | }
335 |
336 | public static Q WhereColumns(this Q query, Expression> column1, string second, string op = "=") where Q : BaseQuery
337 | {
338 | query.WhereColumns($"{AliasFromColumn(column1)}.{Property(column1)}", op, second);
339 | return query;
340 | }
341 |
342 | public static Q OrWhereColumns(this Q query, Expression> column1, Expression> column2, string op = "=") where Q : BaseQuery
343 | {
344 | query.OrWhereColumns(
345 | $"{AliasFromColumn(column1)}.{Property(column1)}",
346 | op,
347 | $"{AliasFromColumn(column2)}.{Property(column2)}");
348 | return query;
349 | }
350 |
351 | public static Q OrWhereColumns(this Q query, Expression> column1, string second, string op = "=") where Q : BaseQuery
352 | {
353 | query.OrWhereColumns($"{AliasFromColumn(column1)}.{Property(column1)}", op, second);
354 | return query;
355 | }
356 |
357 | public static Q WhereNull(this Q query, Expression> column) where Q : BaseQuery
358 | {
359 | query.WhereNull($"{AliasFromColumn(column)}.{Property(column)}");
360 | return query;
361 | }
362 |
363 | public static Q OrWhereNull(this Q query, Expression> column) where Q : BaseQuery
364 | {
365 | query.OrWhereNull($"{AliasFromColumn(column)}.{Property(column)}");
366 | return query;
367 | }
368 |
369 | public static Q WhereNotNull(this Q query, Expression> column) where Q : BaseQuery
370 | {
371 | query.WhereNotNull($"{AliasFromColumn(column)}.{Property(column)}");
372 | return query;
373 | }
374 |
375 | public static Q OrWhereNotNull(this Q query, Expression> column) where Q : BaseQuery
376 | {
377 | query.OrWhereNotNull($"{AliasFromColumn(column)}.{Property(column)}");
378 | return query;
379 | }
380 |
381 | public static Q WhereTrue(this Q query, Expression> column) where Q : BaseQuery
382 | {
383 | query.WhereTrue($"{AliasFromColumn(column)}.{Property(column)}");
384 | return query;
385 | }
386 |
387 | public static Q OrWhereTrue(this Q query, Expression> column) where Q : BaseQuery
388 | {
389 | query.OrWhereTrue($"{AliasFromColumn(column)}.{Property(column)}");
390 | return query;
391 | }
392 |
393 | public static Q WhereFalse(this Q query, Expression> column) where Q : BaseQuery
394 | {
395 | query.WhereFalse($"{AliasFromColumn(column)}.{Property(column)}");
396 | return query;
397 | }
398 |
399 | public static Q OrWhereFalse(this Q query, Expression> column) where Q : BaseQuery
400 | {
401 | query.OrWhereFalse($"{AliasFromColumn(column)}.{Property(column)}");
402 | return query;
403 | }
404 |
405 | public static Q WhereLike(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
406 | {
407 | query.WhereLike($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
408 | return query;
409 | }
410 |
411 | public static Q OrWhereLike(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
412 | {
413 | query.OrWhereLike($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
414 | return query;
415 | }
416 |
417 | public static Q WhereNotLike(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
418 | {
419 | query.WhereNotLike($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
420 | return query;
421 | }
422 |
423 | public static Q OrWhereNotLike(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
424 | {
425 | query.OrWhereNotLike($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
426 | return query;
427 | }
428 |
429 | public static Q WhereStarts(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
430 | {
431 | query.WhereStarts($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
432 | return query;
433 | }
434 |
435 | public static Q OrWhereStarts(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
436 | {
437 | query.OrWhereStarts($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
438 | return query;
439 | }
440 |
441 | public static Q WhereNotStarts(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
442 | {
443 | query.WhereNotStarts($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
444 | return query;
445 | }
446 |
447 | public static Q OrWhereNotStarts(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
448 | {
449 | query.OrWhereNotStarts($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
450 | return query;
451 | }
452 |
453 | public static Q WhereEnds(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
454 | {
455 | query.WhereEnds($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
456 | return query;
457 | }
458 |
459 | public static Q OrWhereEnds(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
460 | {
461 | query.OrWhereEnds($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
462 | return query;
463 | }
464 |
465 | public static Q WhereNotEnds(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
466 | {
467 | query.WhereNotEnds($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
468 | return query;
469 | }
470 |
471 | public static Q OrWhereNotEnds(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
472 | {
473 | query.OrWhereNotEnds($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
474 | return query;
475 | }
476 |
477 | public static Q WhereContains(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
478 | {
479 | query.WhereContains($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
480 | return query;
481 | }
482 |
483 | public static Q OrWhereContains(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
484 | {
485 | query.OrWhereContains($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
486 | return query;
487 | }
488 |
489 | public static Q WhereNotContains(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
490 | {
491 | query.WhereNotContains($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
492 | return query;
493 | }
494 |
495 | public static Q OrWhereNotContains(this Q query, Expression> column, object value, bool caseSensitive = false) where Q : BaseQuery
496 | {
497 | query.OrWhereNotContains($"{AliasFromColumn(column)}.{Property(column)}", value, caseSensitive: caseSensitive);
498 | return query;
499 | }
500 |
501 | public static Q WhereBetween(this Q query, Expression> column, TValue lower, TValue higher) where Q : BaseQuery
502 | {
503 | query.WhereBetween($"{AliasFromColumn(column)}.{Property(column)}", lower, higher);
504 | return query;
505 | }
506 |
507 | public static Q OrWhereBetween(this Q query, Expression> column, TValue lower, TValue higher) where Q : BaseQuery
508 | {
509 | query.OrWhereBetween($"{AliasFromColumn(column)}.{Property(column)}", lower, higher);
510 | return query;
511 | }
512 |
513 | public static Q WhereNotBetween(this Q query, Expression> column, TValue lower, TValue higher) where Q : BaseQuery
514 | {
515 | query.WhereNotBetween($"{AliasFromColumn(column)}.{Property(column)}", lower, higher);
516 | return query;
517 | }
518 |
519 | public static Q OrWhereNotBetween(this Q query, Expression> column, TValue lower, TValue higher) where Q : BaseQuery
520 | {
521 | query.OrWhereNotBetween($"{AliasFromColumn(column)}.{Property(column)}", lower, higher);
522 | return query;
523 | }
524 |
525 | public static Q WhereIn(this Q query, Expression> column, IEnumerable values) where Q : BaseQuery
526 | {
527 | query.WhereIn($"{AliasFromColumn(column)}.{Property(column)}", values);
528 | return query;
529 | }
530 |
531 | public static Q OrWhereIn(this Q query, Expression> column, IEnumerable values) where Q : BaseQuery
532 | {
533 | query.OrWhereIn($"{AliasFromColumn(column)}.{Property(column)}", values);
534 | return query;
535 | }
536 |
537 | public static Q WhereNotIn(this Q query, Expression> column, IEnumerable values) where Q : BaseQuery
538 | {
539 | query.WhereNotIn($"{AliasFromColumn(column)}.{Property(column)}", values);
540 | return query;
541 | }
542 |
543 | public static Q OrWhereNotIn(this Q query, Expression> column, IEnumerable values) where Q : BaseQuery
544 | {
545 | query.OrWhereNotIn($"{AliasFromColumn(column)}.{Property(column)}", values);
546 | return query;
547 | }
548 |
549 | public static Q WhereIn(this Q query, Expression> column, Query subquery) where Q : BaseQuery
550 | {
551 | query.WhereIn($"{AliasFromColumn(column)}.{Property(column)}", subquery);
552 | return query;
553 | }
554 |
555 | public static Q OrWhereIn(this Q query, Expression> column, Query subquery) where Q : BaseQuery
556 | {
557 | query.OrWhereIn($"{AliasFromColumn(column)}.{Property(column)}", subquery);
558 | return query;
559 | }
560 |
561 | public static Q WhereNotIn(this Q query, Expression> column, Query subquery) where Q : BaseQuery
562 | {
563 | query.WhereNotIn($"{AliasFromColumn(column)}.{Property(column)}", subquery);
564 | return query;
565 | }
566 |
567 | public static Q OrWhereNotIn(this Q query, Expression> column, Query subquery) where Q : BaseQuery
568 | {
569 | query.OrWhereNotIn($"{AliasFromColumn(column)}.{Property(column)}", subquery);
570 | return query;
571 | }
572 |
573 | public static Q WhereDatePart(this Q query, string part, Expression> column, object value, string op = "=") where Q : BaseQuery
574 | {
575 | query.WhereDatePart(part, $"{AliasFromColumn(column)}.{Property(column)}", op, value);
576 | return query;
577 | }
578 |
579 | public static Q OrWhereDatePart(this Q query, string part, Expression> column, object value, string op = "=") where Q : BaseQuery