@malamute/ai-rules 1.0.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +272 -121
- package/bin/cli.js +5 -2
- package/configs/_shared/CLAUDE.md +52 -149
- package/configs/_shared/rules/conventions/documentation.md +324 -0
- package/configs/_shared/rules/conventions/git.md +265 -0
- package/configs/_shared/rules/conventions/npm.md +80 -0
- package/configs/_shared/{.claude/rules → rules/conventions}/performance.md +1 -1
- package/configs/_shared/rules/conventions/principles.md +334 -0
- package/configs/_shared/rules/devops/ci-cd.md +262 -0
- package/configs/_shared/rules/devops/docker.md +275 -0
- package/configs/_shared/rules/devops/nx.md +194 -0
- package/configs/_shared/rules/domain/backend/api-design.md +203 -0
- package/configs/_shared/rules/lang/csharp/async.md +220 -0
- package/configs/_shared/rules/lang/csharp/csharp.md +314 -0
- package/configs/_shared/rules/lang/csharp/linq.md +210 -0
- package/configs/_shared/rules/lang/python/async.md +337 -0
- package/configs/_shared/rules/lang/python/celery.md +476 -0
- package/configs/_shared/rules/lang/python/config.md +339 -0
- package/configs/{python/.claude/rules → _shared/rules/lang/python}/database/sqlalchemy.md +6 -1
- package/configs/_shared/rules/lang/python/deployment.md +523 -0
- package/configs/_shared/rules/lang/python/error-handling.md +330 -0
- package/configs/_shared/rules/lang/python/migrations.md +421 -0
- package/configs/_shared/rules/lang/python/python.md +172 -0
- package/configs/_shared/rules/lang/python/repository.md +383 -0
- package/configs/{python/.claude/rules → _shared/rules/lang/python}/testing.md +2 -69
- package/configs/_shared/rules/lang/typescript/async.md +447 -0
- package/configs/_shared/rules/lang/typescript/generics.md +356 -0
- package/configs/_shared/rules/lang/typescript/typescript.md +212 -0
- package/configs/_shared/rules/quality/error-handling.md +48 -0
- package/configs/_shared/rules/quality/logging.md +45 -0
- package/configs/_shared/rules/quality/observability.md +240 -0
- package/configs/_shared/rules/quality/testing-patterns.md +65 -0
- package/configs/_shared/rules/security/secrets-management.md +222 -0
- package/configs/_shared/skills/analysis/explore/SKILL.md +257 -0
- package/configs/_shared/skills/analysis/security-audit/SKILL.md +184 -0
- package/configs/_shared/skills/dev/api-endpoint/SKILL.md +126 -0
- package/configs/_shared/{.claude/commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
- package/configs/_shared/{.claude/commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
- package/configs/_shared/{.claude/commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
- package/configs/_shared/skills/infra/deploy/SKILL.md +139 -0
- package/configs/_shared/skills/infra/docker/SKILL.md +95 -0
- package/configs/_shared/skills/infra/migration/SKILL.md +158 -0
- package/configs/_shared/skills/nx/nx-affected/SKILL.md +72 -0
- package/configs/_shared/skills/nx/nx-lib/SKILL.md +375 -0
- package/configs/angular/CLAUDE.md +24 -216
- package/configs/angular/{.claude/rules → rules/core}/components.md +69 -15
- package/configs/angular/rules/core/resource.md +285 -0
- package/configs/angular/rules/core/signals.md +323 -0
- package/configs/angular/rules/http.md +338 -0
- package/configs/angular/rules/routing.md +291 -0
- package/configs/angular/rules/ssr.md +312 -0
- package/configs/angular/rules/state/signal-store.md +408 -0
- package/configs/angular/{.claude/rules → rules/state}/state.md +2 -2
- package/configs/angular/{.claude/rules → rules}/testing.md +7 -7
- package/configs/angular/rules/ui/aria.md +422 -0
- package/configs/angular/rules/ui/forms.md +424 -0
- package/configs/angular/rules/ui/pipes-directives.md +335 -0
- package/configs/angular/{.claude/settings.json → settings.json} +3 -0
- package/configs/dotnet/CLAUDE.md +53 -286
- package/configs/dotnet/rules/background-services.md +552 -0
- package/configs/dotnet/rules/configuration.md +426 -0
- package/configs/dotnet/rules/ddd.md +447 -0
- package/configs/dotnet/rules/dependency-injection.md +343 -0
- package/configs/dotnet/rules/mediatr.md +320 -0
- package/configs/dotnet/rules/middleware.md +489 -0
- package/configs/dotnet/rules/result-pattern.md +363 -0
- package/configs/dotnet/rules/validation.md +388 -0
- package/configs/dotnet/settings.json +29 -0
- package/configs/fastapi/CLAUDE.md +144 -0
- package/configs/fastapi/rules/background-tasks.md +254 -0
- package/configs/fastapi/rules/dependencies.md +170 -0
- package/configs/{python/.claude → fastapi}/rules/fastapi.md +61 -1
- package/configs/fastapi/rules/lifespan.md +274 -0
- package/configs/fastapi/rules/middleware.md +229 -0
- package/configs/fastapi/rules/pydantic.md +433 -0
- package/configs/fastapi/rules/responses.md +251 -0
- package/configs/fastapi/rules/routers.md +202 -0
- package/configs/fastapi/rules/security.md +222 -0
- package/configs/fastapi/rules/testing.md +251 -0
- package/configs/fastapi/rules/websockets.md +298 -0
- package/configs/fastapi/settings.json +35 -0
- package/configs/flask/CLAUDE.md +166 -0
- package/configs/flask/rules/blueprints.md +208 -0
- package/configs/flask/rules/cli.md +285 -0
- package/configs/flask/rules/configuration.md +281 -0
- package/configs/flask/rules/context.md +238 -0
- package/configs/flask/rules/error-handlers.md +278 -0
- package/configs/flask/rules/extensions.md +278 -0
- package/configs/flask/rules/flask.md +171 -0
- package/configs/flask/rules/marshmallow.md +206 -0
- package/configs/flask/rules/security.md +267 -0
- package/configs/flask/rules/testing.md +284 -0
- package/configs/flask/settings.json +35 -0
- package/configs/nestjs/CLAUDE.md +57 -215
- package/configs/nestjs/rules/common-patterns.md +300 -0
- package/configs/nestjs/rules/filters.md +376 -0
- package/configs/nestjs/rules/interceptors.md +317 -0
- package/configs/nestjs/rules/middleware.md +321 -0
- package/configs/nestjs/{.claude/rules → rules}/modules.md +26 -0
- package/configs/nestjs/rules/pipes.md +351 -0
- package/configs/nestjs/rules/websockets.md +451 -0
- package/configs/nestjs/settings.json +31 -0
- package/configs/nextjs/CLAUDE.md +69 -331
- package/configs/nextjs/rules/api-routes.md +358 -0
- package/configs/nextjs/rules/authentication.md +355 -0
- package/configs/nextjs/{.claude/rules → rules}/components.md +52 -0
- package/configs/nextjs/rules/data-fetching.md +249 -0
- package/configs/nextjs/rules/database.md +400 -0
- package/configs/nextjs/rules/middleware.md +303 -0
- package/configs/nextjs/rules/routing.md +324 -0
- package/configs/nextjs/rules/seo.md +350 -0
- package/configs/nextjs/rules/server-actions.md +353 -0
- package/configs/nextjs/{.claude/rules → rules}/state/zustand.md +6 -6
- package/configs/nextjs/{.claude/settings.json → settings.json} +7 -0
- package/package.json +24 -9
- package/src/cli.js +218 -0
- package/src/config.js +63 -0
- package/src/index.js +4 -0
- package/src/installer.js +414 -0
- package/src/merge.js +109 -0
- package/src/tech-config.json +45 -0
- package/src/utils.js +88 -0
- package/configs/dotnet/.claude/settings.json +0 -9
- package/configs/nestjs/.claude/settings.json +0 -15
- package/configs/python/.claude/rules/flask.md +0 -332
- package/configs/python/.claude/settings.json +0 -18
- package/configs/python/CLAUDE.md +0 -273
- package/src/install.js +0 -315
- /package/configs/_shared/{.claude/rules → rules/domain/frontend}/accessibility.md +0 -0
- /package/configs/_shared/{.claude/rules → rules/security}/security.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/dev}/debug/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/dev}/learning/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/dev}/spec/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/git}/review/SKILL.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/api.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/architecture.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/database/efcore.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/testing.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/auth.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/database/prisma.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/database/typeorm.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/testing.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/validation.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/state/redux-toolkit.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/testing.md +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.cs"
|
|
4
|
+
- "**/*.csproj"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# C# Code Style Rules
|
|
8
|
+
|
|
9
|
+
## Nullability
|
|
10
|
+
|
|
11
|
+
Enable nullable reference types in all projects:
|
|
12
|
+
|
|
13
|
+
```xml
|
|
14
|
+
<PropertyGroup>
|
|
15
|
+
<Nullable>enable</Nullable>
|
|
16
|
+
</PropertyGroup>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```csharp
|
|
20
|
+
// GOOD - explicit nullability
|
|
21
|
+
public string Name { get; set; } = string.Empty;
|
|
22
|
+
public string? MiddleName { get; set; }
|
|
23
|
+
|
|
24
|
+
// BAD - ambiguous
|
|
25
|
+
public string Name { get; set; } // Warning: non-nullable not initialized
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Naming Conventions
|
|
29
|
+
|
|
30
|
+
| Element | Convention | Example |
|
|
31
|
+
|---------|------------|---------|
|
|
32
|
+
| Classes | PascalCase | `UserService` |
|
|
33
|
+
| Interfaces | IPascalCase | `IUserRepository` |
|
|
34
|
+
| Methods | PascalCase | `GetUserById` |
|
|
35
|
+
| Properties | PascalCase | `FirstName` |
|
|
36
|
+
| Private fields | _camelCase | `_userRepository` |
|
|
37
|
+
| Local variables | camelCase | `currentUser` |
|
|
38
|
+
| Constants | PascalCase | `MaxRetryCount` |
|
|
39
|
+
| Async methods | Suffix `Async` | `GetUserAsync` |
|
|
40
|
+
|
|
41
|
+
```csharp
|
|
42
|
+
// GOOD
|
|
43
|
+
private readonly IUserRepository _userRepository;
|
|
44
|
+
public async Task<User?> GetUserByIdAsync(int id) { }
|
|
45
|
+
|
|
46
|
+
// BAD
|
|
47
|
+
private IUserRepository userRepository; // Missing underscore
|
|
48
|
+
public async Task<User?> GetUserById(int id) { } // Missing Async suffix
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Type Guidelines
|
|
52
|
+
|
|
53
|
+
### Use `var` When Type Is Obvious
|
|
54
|
+
|
|
55
|
+
```csharp
|
|
56
|
+
// GOOD - type is obvious
|
|
57
|
+
var user = new User();
|
|
58
|
+
var users = await _repository.GetAllAsync();
|
|
59
|
+
var count = items.Count;
|
|
60
|
+
|
|
61
|
+
// GOOD - type not obvious, be explicit
|
|
62
|
+
User user = GetUser();
|
|
63
|
+
IEnumerable<string> names = GetNames();
|
|
64
|
+
|
|
65
|
+
// BAD - redundant
|
|
66
|
+
User user = new User();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Prefer Collection Expressions (C# 12+)
|
|
70
|
+
|
|
71
|
+
```csharp
|
|
72
|
+
// GOOD - collection expressions
|
|
73
|
+
int[] numbers = [1, 2, 3];
|
|
74
|
+
List<string> names = ["Alice", "Bob"];
|
|
75
|
+
Dictionary<string, int> ages = new() { ["Alice"] = 30 };
|
|
76
|
+
|
|
77
|
+
// BAD - verbose
|
|
78
|
+
int[] numbers = new int[] { 1, 2, 3 };
|
|
79
|
+
List<string> names = new List<string> { "Alice", "Bob" };
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Use Pattern Matching
|
|
83
|
+
|
|
84
|
+
```csharp
|
|
85
|
+
// GOOD - pattern matching
|
|
86
|
+
if (user is { IsActive: true, Role: "Admin" })
|
|
87
|
+
{
|
|
88
|
+
// ...
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return status switch
|
|
92
|
+
{
|
|
93
|
+
OrderStatus.Pending => "Awaiting payment",
|
|
94
|
+
OrderStatus.Shipped => "On the way",
|
|
95
|
+
OrderStatus.Delivered => "Completed",
|
|
96
|
+
_ => "Unknown"
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// BAD - verbose checks
|
|
100
|
+
if (user != null && user.IsActive && user.Role == "Admin")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Async/Await
|
|
104
|
+
|
|
105
|
+
### Always Use Async All The Way
|
|
106
|
+
|
|
107
|
+
```csharp
|
|
108
|
+
// GOOD - async all the way
|
|
109
|
+
public async Task<User> GetUserAsync(int id)
|
|
110
|
+
{
|
|
111
|
+
return await _repository.GetByIdAsync(id);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// BAD - blocking on async
|
|
115
|
+
public User GetUser(int id)
|
|
116
|
+
{
|
|
117
|
+
return _repository.GetByIdAsync(id).Result; // Deadlock risk!
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// BAD - async void (except event handlers)
|
|
121
|
+
public async void ProcessData() // Can't await, exceptions lost
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### ConfigureAwait in Libraries
|
|
125
|
+
|
|
126
|
+
```csharp
|
|
127
|
+
// In library code (not ASP.NET Core)
|
|
128
|
+
await SomeOperationAsync().ConfigureAwait(false);
|
|
129
|
+
|
|
130
|
+
// In ASP.NET Core controllers - not needed
|
|
131
|
+
await SomeOperationAsync();
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Cancellation Tokens
|
|
135
|
+
|
|
136
|
+
```csharp
|
|
137
|
+
// GOOD - propagate cancellation
|
|
138
|
+
public async Task<List<User>> GetUsersAsync(CancellationToken cancellationToken = default)
|
|
139
|
+
{
|
|
140
|
+
return await _context.Users
|
|
141
|
+
.Where(u => u.IsActive)
|
|
142
|
+
.ToListAsync(cancellationToken);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// BAD - ignoring cancellation
|
|
146
|
+
public async Task<List<User>> GetUsersAsync()
|
|
147
|
+
{
|
|
148
|
+
return await _context.Users.ToListAsync();
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## LINQ Best Practices
|
|
153
|
+
|
|
154
|
+
```csharp
|
|
155
|
+
// GOOD - method syntax for complex queries
|
|
156
|
+
var activeAdmins = users
|
|
157
|
+
.Where(u => u.IsActive)
|
|
158
|
+
.Where(u => u.Role == Role.Admin)
|
|
159
|
+
.OrderBy(u => u.Name)
|
|
160
|
+
.Select(u => new UserDto(u.Id, u.Name))
|
|
161
|
+
.ToList();
|
|
162
|
+
|
|
163
|
+
// GOOD - query syntax for joins
|
|
164
|
+
var result = from order in orders
|
|
165
|
+
join customer in customers on order.CustomerId equals customer.Id
|
|
166
|
+
select new { order.Id, customer.Name };
|
|
167
|
+
|
|
168
|
+
// BAD - multiple enumerations
|
|
169
|
+
var users = GetUsers();
|
|
170
|
+
var count = users.Count(); // First enumeration
|
|
171
|
+
var first = users.First(); // Second enumeration
|
|
172
|
+
|
|
173
|
+
// GOOD - materialize once
|
|
174
|
+
var users = GetUsers().ToList();
|
|
175
|
+
var count = users.Count;
|
|
176
|
+
var first = users[0];
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Records and Immutability
|
|
180
|
+
|
|
181
|
+
```csharp
|
|
182
|
+
// GOOD - immutable DTOs with records
|
|
183
|
+
public record UserDto(int Id, string Name, string Email);
|
|
184
|
+
|
|
185
|
+
public record CreateUserCommand(string Name, string Email)
|
|
186
|
+
{
|
|
187
|
+
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// GOOD - with-expressions for modifications
|
|
191
|
+
var updated = user with { Name = "New Name" };
|
|
192
|
+
|
|
193
|
+
// BAD - mutable DTOs
|
|
194
|
+
public class UserDto
|
|
195
|
+
{
|
|
196
|
+
public int Id { get; set; }
|
|
197
|
+
public string Name { get; set; }
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Error Handling
|
|
202
|
+
|
|
203
|
+
### Specific Exceptions
|
|
204
|
+
|
|
205
|
+
```csharp
|
|
206
|
+
// GOOD - specific exceptions
|
|
207
|
+
public User GetUser(int id)
|
|
208
|
+
{
|
|
209
|
+
return _repository.GetById(id)
|
|
210
|
+
?? throw new NotFoundException($"User {id} not found");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// BAD - generic exception
|
|
214
|
+
throw new Exception("User not found");
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Never Swallow Exceptions
|
|
218
|
+
|
|
219
|
+
```csharp
|
|
220
|
+
// BAD - silent failure
|
|
221
|
+
try
|
|
222
|
+
{
|
|
223
|
+
await _service.ProcessAsync();
|
|
224
|
+
}
|
|
225
|
+
catch (Exception)
|
|
226
|
+
{
|
|
227
|
+
// Swallowed
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// GOOD - log and handle
|
|
231
|
+
try
|
|
232
|
+
{
|
|
233
|
+
await _service.ProcessAsync();
|
|
234
|
+
}
|
|
235
|
+
catch (Exception ex)
|
|
236
|
+
{
|
|
237
|
+
_logger.LogError(ex, "Failed to process");
|
|
238
|
+
throw;
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Disposables
|
|
243
|
+
|
|
244
|
+
```csharp
|
|
245
|
+
// GOOD - using declaration (C# 8+)
|
|
246
|
+
await using var connection = new SqlConnection(connectionString);
|
|
247
|
+
await connection.OpenAsync();
|
|
248
|
+
|
|
249
|
+
// GOOD - using statement for limited scope
|
|
250
|
+
using (var stream = File.OpenRead(path))
|
|
251
|
+
{
|
|
252
|
+
// ...
|
|
253
|
+
}
|
|
254
|
+
// Stream disposed here
|
|
255
|
+
|
|
256
|
+
// BAD - manual disposal
|
|
257
|
+
var connection = new SqlConnection(connectionString);
|
|
258
|
+
try
|
|
259
|
+
{
|
|
260
|
+
await connection.OpenAsync();
|
|
261
|
+
}
|
|
262
|
+
finally
|
|
263
|
+
{
|
|
264
|
+
connection.Dispose();
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## File-Scoped Namespaces
|
|
269
|
+
|
|
270
|
+
```csharp
|
|
271
|
+
// GOOD - file-scoped (C# 10+)
|
|
272
|
+
namespace MyApp.Services;
|
|
273
|
+
|
|
274
|
+
public class UserService
|
|
275
|
+
{
|
|
276
|
+
// ...
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// BAD - block-scoped (unnecessary nesting)
|
|
280
|
+
namespace MyApp.Services
|
|
281
|
+
{
|
|
282
|
+
public class UserService
|
|
283
|
+
{
|
|
284
|
+
// ...
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Primary Constructors (C# 12+)
|
|
290
|
+
|
|
291
|
+
```csharp
|
|
292
|
+
// GOOD - primary constructor
|
|
293
|
+
public class UserService(IUserRepository repository, ILogger<UserService> logger)
|
|
294
|
+
{
|
|
295
|
+
public async Task<User?> GetUserAsync(int id)
|
|
296
|
+
{
|
|
297
|
+
logger.LogInformation("Getting user {Id}", id);
|
|
298
|
+
return await repository.GetByIdAsync(id);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Also valid - traditional constructor with readonly fields
|
|
303
|
+
public class UserService
|
|
304
|
+
{
|
|
305
|
+
private readonly IUserRepository _repository;
|
|
306
|
+
private readonly ILogger<UserService> _logger;
|
|
307
|
+
|
|
308
|
+
public UserService(IUserRepository repository, ILogger<UserService> logger)
|
|
309
|
+
{
|
|
310
|
+
_repository = repository;
|
|
311
|
+
_logger = logger;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.cs"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# LINQ Best Practices
|
|
7
|
+
|
|
8
|
+
## Method vs Query Syntax
|
|
9
|
+
|
|
10
|
+
```csharp
|
|
11
|
+
// Method syntax - preferred for simple queries
|
|
12
|
+
var activeUsers = users
|
|
13
|
+
.Where(u => u.IsActive)
|
|
14
|
+
.OrderBy(u => u.Name)
|
|
15
|
+
.ToList();
|
|
16
|
+
|
|
17
|
+
// Query syntax - better for joins and complex queries
|
|
18
|
+
var orderDetails = from order in orders
|
|
19
|
+
join customer in customers
|
|
20
|
+
on order.CustomerId equals customer.Id
|
|
21
|
+
join product in products
|
|
22
|
+
on order.ProductId equals product.Id
|
|
23
|
+
where order.Status == OrderStatus.Completed
|
|
24
|
+
select new
|
|
25
|
+
{
|
|
26
|
+
OrderId = order.Id,
|
|
27
|
+
CustomerName = customer.Name,
|
|
28
|
+
ProductName = product.Name
|
|
29
|
+
};
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Deferred vs Immediate Execution
|
|
33
|
+
|
|
34
|
+
```csharp
|
|
35
|
+
// Deferred - query not executed until enumerated
|
|
36
|
+
var query = users.Where(u => u.IsActive); // No DB call yet
|
|
37
|
+
|
|
38
|
+
// Immediate - forces execution
|
|
39
|
+
var list = users.Where(u => u.IsActive).ToList(); // Executes now
|
|
40
|
+
var array = users.Where(u => u.IsActive).ToArray(); // Executes now
|
|
41
|
+
var first = users.First(u => u.IsActive); // Executes now
|
|
42
|
+
var count = users.Count(u => u.IsActive); // Executes now
|
|
43
|
+
|
|
44
|
+
// BAD - multiple enumeration
|
|
45
|
+
IEnumerable<User> users = GetUsers();
|
|
46
|
+
if (users.Any()) // First enumeration
|
|
47
|
+
{
|
|
48
|
+
var first = users.First(); // Second enumeration - might get different data!
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// GOOD - materialize once
|
|
52
|
+
var users = GetUsers().ToList();
|
|
53
|
+
if (users.Count > 0)
|
|
54
|
+
{
|
|
55
|
+
var first = users[0];
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Filtering
|
|
60
|
+
|
|
61
|
+
```csharp
|
|
62
|
+
// Chain Where for readability
|
|
63
|
+
var results = items
|
|
64
|
+
.Where(x => x.IsActive)
|
|
65
|
+
.Where(x => x.Category == "Electronics")
|
|
66
|
+
.Where(x => x.Price > 100);
|
|
67
|
+
|
|
68
|
+
// Use Any/All for existence checks
|
|
69
|
+
if (users.Any(u => u.Role == Role.Admin)) // GOOD
|
|
70
|
+
if (users.Where(u => u.Role == Role.Admin).Count() > 0) // BAD
|
|
71
|
+
|
|
72
|
+
// FirstOrDefault with predicate
|
|
73
|
+
var admin = users.FirstOrDefault(u => u.Role == Role.Admin);
|
|
74
|
+
|
|
75
|
+
// SingleOrDefault when expecting 0 or 1
|
|
76
|
+
var user = users.SingleOrDefault(u => u.Email == email);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Projection
|
|
80
|
+
|
|
81
|
+
```csharp
|
|
82
|
+
// Select for transformation
|
|
83
|
+
var dtos = users.Select(u => new UserDto(u.Id, u.Name));
|
|
84
|
+
|
|
85
|
+
// SelectMany to flatten
|
|
86
|
+
var allOrders = customers.SelectMany(c => c.Orders);
|
|
87
|
+
|
|
88
|
+
// Anonymous types for intermediate results
|
|
89
|
+
var intermediate = users
|
|
90
|
+
.Select(u => new { u.Id, FullName = $"{u.FirstName} {u.LastName}" })
|
|
91
|
+
.Where(x => x.FullName.StartsWith("A"));
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Grouping
|
|
95
|
+
|
|
96
|
+
```csharp
|
|
97
|
+
// GroupBy
|
|
98
|
+
var usersByRole = users
|
|
99
|
+
.GroupBy(u => u.Role)
|
|
100
|
+
.Select(g => new
|
|
101
|
+
{
|
|
102
|
+
Role = g.Key,
|
|
103
|
+
Count = g.Count(),
|
|
104
|
+
Users = g.ToList()
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// ToLookup - immediate execution, allows multiple enumeration
|
|
108
|
+
var lookup = users.ToLookup(u => u.Role);
|
|
109
|
+
var admins = lookup[Role.Admin];
|
|
110
|
+
var managers = lookup[Role.Manager];
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Aggregation
|
|
114
|
+
|
|
115
|
+
```csharp
|
|
116
|
+
// Aggregate functions
|
|
117
|
+
var total = orders.Sum(o => o.Amount);
|
|
118
|
+
var average = orders.Average(o => o.Amount);
|
|
119
|
+
var max = orders.Max(o => o.Amount);
|
|
120
|
+
var min = orders.Min(o => o.Amount);
|
|
121
|
+
|
|
122
|
+
// Aggregate for custom accumulation
|
|
123
|
+
var concatenated = names.Aggregate((a, b) => $"{a}, {b}");
|
|
124
|
+
|
|
125
|
+
// With seed value
|
|
126
|
+
var total = items.Aggregate(
|
|
127
|
+
seed: 0m,
|
|
128
|
+
func: (sum, item) => sum + item.Price * item.Quantity);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Set Operations
|
|
132
|
+
|
|
133
|
+
```csharp
|
|
134
|
+
// Distinct
|
|
135
|
+
var uniqueCategories = products.Select(p => p.Category).Distinct();
|
|
136
|
+
|
|
137
|
+
// DistinctBy (C# 10+)
|
|
138
|
+
var uniqueByName = products.DistinctBy(p => p.Name);
|
|
139
|
+
|
|
140
|
+
// Union, Intersect, Except
|
|
141
|
+
var allIds = list1.Select(x => x.Id).Union(list2.Select(x => x.Id));
|
|
142
|
+
var commonIds = list1.Select(x => x.Id).Intersect(list2.Select(x => x.Id));
|
|
143
|
+
var onlyInFirst = list1.Select(x => x.Id).Except(list2.Select(x => x.Id));
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Ordering
|
|
147
|
+
|
|
148
|
+
```csharp
|
|
149
|
+
// Multiple sort criteria
|
|
150
|
+
var sorted = users
|
|
151
|
+
.OrderBy(u => u.LastName)
|
|
152
|
+
.ThenBy(u => u.FirstName)
|
|
153
|
+
.ThenByDescending(u => u.CreatedAt);
|
|
154
|
+
|
|
155
|
+
// Order vs OrderBy (C# 11+)
|
|
156
|
+
var ordered = items.Order(); // Uses default comparer
|
|
157
|
+
var orderedDesc = items.OrderDescending();
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Pagination
|
|
161
|
+
|
|
162
|
+
```csharp
|
|
163
|
+
// Skip/Take for pagination
|
|
164
|
+
var page = users
|
|
165
|
+
.OrderBy(u => u.Id)
|
|
166
|
+
.Skip((pageNumber - 1) * pageSize)
|
|
167
|
+
.Take(pageSize)
|
|
168
|
+
.ToList();
|
|
169
|
+
|
|
170
|
+
// Chunk for batching (C# 10+)
|
|
171
|
+
var batches = items.Chunk(100);
|
|
172
|
+
foreach (var batch in batches)
|
|
173
|
+
{
|
|
174
|
+
await ProcessBatchAsync(batch);
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## EF Core Specific
|
|
179
|
+
|
|
180
|
+
```csharp
|
|
181
|
+
// GOOD - let EF translate to SQL
|
|
182
|
+
var users = await _context.Users
|
|
183
|
+
.Where(u => u.IsActive)
|
|
184
|
+
.OrderBy(u => u.Name)
|
|
185
|
+
.ToListAsync();
|
|
186
|
+
|
|
187
|
+
// BAD - client-side evaluation (throws in EF Core 3+)
|
|
188
|
+
var users = await _context.Users
|
|
189
|
+
.Where(u => SomeLocalMethod(u)) // Can't translate
|
|
190
|
+
.ToListAsync();
|
|
191
|
+
|
|
192
|
+
// GOOD - explicit client evaluation when needed
|
|
193
|
+
var users = await _context.Users
|
|
194
|
+
.Where(u => u.IsActive)
|
|
195
|
+
.AsEnumerable() // Switch to client
|
|
196
|
+
.Where(u => SomeLocalMethod(u))
|
|
197
|
+
.ToList();
|
|
198
|
+
|
|
199
|
+
// Use AsNoTracking for read-only queries
|
|
200
|
+
var users = await _context.Users
|
|
201
|
+
.AsNoTracking()
|
|
202
|
+
.Where(u => u.IsActive)
|
|
203
|
+
.ToListAsync();
|
|
204
|
+
|
|
205
|
+
// Projection to avoid over-fetching
|
|
206
|
+
var dtos = await _context.Users
|
|
207
|
+
.Where(u => u.IsActive)
|
|
208
|
+
.Select(u => new UserDto(u.Id, u.Name, u.Email))
|
|
209
|
+
.ToListAsync();
|
|
210
|
+
```
|