Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions docs/client-api/csharp-code-analyzers/RVN001.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
title: "RVN001 — Index Map or Reduce assigned outside constructor"
sidebar_label: RVN001
sidebar_position: 1
---

# RVN001 — Index Map or Reduce assigned outside constructor

| Property | Value |
|----------|-------|
| **ID** | RVN001 |
| **Title** | Index Map or Reduce assigned outside constructor |
| **Severity** | Info (default) |
| **Category** | Usage |
| **Code fix** | No |

## Description

RavenDB reads the `Map` and `Reduce` expression trees during index registration, which happens when the index class constructor runs. Assigning `Map` or `Reduce` in a regular method, even one called from the constructor, means the assignment may never execute (e.g., when the method is only called conditionally), resulting in the index being deployed without a definition.

## Trigger condition

The analyzer fires when an assignment to `Map` or `Reduce` (the properties defined on `AbstractIndexCreationTask` and its variants) appears inside a regular `MethodDeclarationSyntax` rather than inside a constructor body. The check is semantic: it confirms the target identifier resolves to the actual `Map`/`Reduce` field or property on an index base class before reporting.

## What to do

Move the `Map` and `Reduce` for map-reduce indexes assignment into the constructor body.

### Before (triggers RVN001)

```csharp
public class OrdersByCompany : AbstractIndexCreationTask<Order>
{
public void Configure()
{
Map = orders => from o in orders
select new { o.Company, o.Employee };
}

public OrdersByCompany()
{
Configure(); // assignment happens in a method, not directly in ctor
}
}
```

### After (correct)

```csharp
public class OrdersByCompany : AbstractIndexCreationTask<Order>
{
public OrdersByCompany()
{
Map = orders => from o in orders
select new { o.Company, o.Employee };
}
}
```

## See also

- [RVN004](./RVN004.mdx) — fires when no constructor assigns `Map` at all.
- [RVN005](./RVN005.mdx) — same concept for multi-map indexes missing `AddMap`.
53 changes: 53 additions & 0 deletions docs/client-api/csharp-code-analyzers/RVN002.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
title: "RVN002 — RavenDB query operator after projection"
sidebar_label: RVN002
sidebar_position: 2
---

# RVN002 — RavenDB query operator after projection

| Property | Value |
|----------|-------|
| **ID** | RVN002 |
| **Title** | RavenDB query operator after projection |
| **Severity** | Info (default) |
| **Category** | Usage |
| **Code fix** | No |

## Description

RavenDB LINQ query operators such as `Where`, `OrderBy`, `Search`, `Spatial`, `VectorSearch`, `Filter`, `GroupBy`, and their variants bind against the **source document or index shape**. When `.ProjectInto<T>()` or `.Select(x => new { … })` appears earlier in the chain, it changes the element type. Any operator placed *after* the projection then operates on the projected type instead of the source type, silently producing incorrect results or throwing at runtime.

## Operators covered

The following methods are flagged when they appear after a projection: `Where`, `OrderBy`, `OrderByDescending`, `ThenBy`, `ThenByDescending`, `GroupBy`, `Search`, `Spatial`, `OrderByDistance`, `OrderByDistanceDescending`, `OrderByScore`, `OrderByScoreDescending`, `ThenByScore`, `ThenByScoreDescending`, `MoreLikeThis`, `VectorSearch`, `Filter`, `GroupByArrayValues`, `GroupByArrayContent`.

## Trigger condition

The analyzer fires when the immediate receiver of a forbidden method is an `IRavenQueryable<T>`, and walking back through the invocation chain finds a `.ProjectInto` or `.Select` call whose own receiver is also an `IRavenQueryable<T>`.

## What to do

Move the projection call to the **end** of the query chain, after all filtering and ordering.

### Before (triggers RVN002)

```csharp
var results = session.Query<Order, Orders_ByCompany>()
.ProjectInto<OrderDto>()
.Where(x => x.Company == "companies/1-A") // RVN002: Where after ProjectInto
.ToList();
```

### After (correct)

```csharp
var results = session.Query<Order, Orders_ByCompany>()
.Where(x => x.Company == "companies/1-A")
.ProjectInto<OrderDto>()
.ToList();
```

## See also

- [RVN003](./RVN003.mdx) — fires when `.ProjectInto` is called twice on the same chain.
48 changes: 48 additions & 0 deletions docs/client-api/csharp-code-analyzers/RVN003.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
title: "RVN003 — ProjectInto called more than once in a query chain"
sidebar_label: RVN003
sidebar_position: 3
---

# RVN003 — ProjectInto called more than once in a query chain

| Property | Value |
|----------|-------|
| **ID** | RVN003 |
| **Title** | ProjectInto called more than once in a query chain |
| **Severity** | Info (default) |
| **Category** | Usage |
| **Code fix** | No |

## Description

RavenDB's `ProjectInto<T>()` can only be called once per query chain. Calling it a second time throws `InvalidOperationException` at runtime because the projection is already registered on the query provider. This diagnostic catches the double-call at compile time.

## Trigger condition

The analyzer fires when a `.ProjectInto` invocation on an `IRavenQueryable<T>` receiver is found, and walking back through the invocation chain finds a previous `.ProjectInto` call whose own receiver is also an `IRavenQueryable<T>`.

## What to do

Remove the duplicate `.ProjectInto` call. If you need to project into a different type, restructure the query or use a single projection with a combined DTO.

### Before (triggers RVN003)

```csharp
var results = session.Query<Order>()
.ProjectInto<OrderSummaryDto>()
.ProjectInto<OrderSummaryDto>() // RVN003: second ProjectInto throws at runtime
.ToList();
```

### After (correct)

```csharp
var results = session.Query<Order>()
.ProjectInto<OrderSummaryDto>()
.ToList();
```

## See also

- [RVN002](./RVN002.mdx) — fires when a query operator appears after a projection.
54 changes: 54 additions & 0 deletions docs/client-api/csharp-code-analyzers/RVN004.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
title: "RVN004 — AbstractIndexCreationTask subclass is missing a Map assignment"
sidebar_label: RVN004
sidebar_position: 4
---

# RVN004 — AbstractIndexCreationTask subclass is missing a Map assignment

| Property | Value |
|----------|-------|
| **ID** | RVN004 |
| **Title** | AbstractIndexCreationTask subclass is missing a Map assignment |
| **Severity** | Info (default) |
| **Category** | Usage |
| **Code fix** | No |

## Description

Every class that inherits from `AbstractIndexCreationTask<TDocument>` (or `AbstractGenericIndexCreationTask`) must assign the `Map` property in its constructor. Without a `Map` expression, the index has no definition and will fail when deployed to the server.

## Trigger condition

The analyzer fires when a class has a base-type chain that includes `AbstractIndexCreationTask` or `AbstractGenericIndexCreationTask`, and none of its constructors contains an assignment to the `Map` property that resolves to the field or property defined on the index base class. JavaScript indexes (`AbstractJavaScriptIndexCreationTask`) are excluded because they express their maps differently.

## What to do

Add a constructor (or update the existing one) that assigns the `Map` property.

### Before (triggers RVN004)

```csharp
public class Orders_ByCompany : AbstractIndexCreationTask<Order>
{
// No constructor — Map is never assigned; RVN004 fires on the class name
}
```

### After (correct)

```csharp
public class Orders_ByCompany : AbstractIndexCreationTask<Order>
{
public Orders_ByCompany()
{
Map = orders => from o in orders
select new { o.Company, o.Employee };
}
}
```

## See also

- [RVN001](./RVN001.mdx) — fires when `Map` is assigned in a method rather than a constructor.
- [RVN005](./RVN005.mdx) — equivalent rule for multi-map indexes.
54 changes: 54 additions & 0 deletions docs/client-api/csharp-code-analyzers/RVN005.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
title: "RVN005 — Multi-map index has no AddMap call in any constructor"
sidebar_label: RVN005
sidebar_position: 5
---

# RVN005 — Multi-map index has no AddMap call in any constructor

| Property | Value |
|----------|-------|
| **ID** | RVN005 |
| **Title** | Multi-map index has no AddMap call in any constructor |
| **Severity** | Info (default) |
| **Category** | Usage |
| **Code fix** | No |

## Description

Every class that inherits from `AbstractMultiMapIndexCreationTask` (or the time-series and counters variants: `AbstractMultiMapTimeSeriesIndexCreationTask`, `AbstractMultiMapCountersIndexCreationTask`) must call `AddMap<TSource>(…)` at least once in its constructor. Without at least one `AddMap`, the index has no definition and will fail when deployed to the server.

## Trigger condition

The analyzer fires when a class whose base-type chain includes a multi-map index base class has no constructor that calls `AddMap` (or `AddMapForAll`) with the method resolving to the one defined on the multi-map base class.

## What to do

Add a constructor that calls `AddMap<TSource>(…)` for each document type the index should map.

### Before (triggers RVN005)

```csharp
public class Animals_ByName : AbstractMultiMapIndexCreationTask
{
// No AddMap call anywhere — RVN005 fires on the class name
}
```

### After (correct)

```csharp
public class Animals_ByName : AbstractMultiMapIndexCreationTask
{
public Animals_ByName()
{
AddMap<Cat>(cats => from c in cats select new { c.Name });
AddMap<Dog>(dogs => from d in dogs select new { d.Name });
}
}
```

## See also

- [RVN004](./RVN004.mdx) — equivalent rule for regular single-map indexes.
- [RVN006](./RVN006.mdx) — fires when a multi-map index has only one `AddMap` call.
56 changes: 56 additions & 0 deletions docs/client-api/csharp-code-analyzers/RVN006.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
title: "RVN006 — Multi-map index uses only a single AddMap"
sidebar_label: RVN006
sidebar_position: 6
---

# RVN006 — Multi-map index uses only a single AddMap

| Property | Value |
|----------|-------|
| **ID** | RVN006 |
| **Title** | Multi-map index uses only a single AddMap |
| **Severity** | Info (default) |
| **Category** | Usage |
| **Code fix** | No |

## Description

Multi-map index base classes (`AbstractMultiMapIndexCreationTask` and its variants) are designed for indexes that map over multiple document types. When only one `AddMap` call is present across all constructors, a regular `AbstractIndexCreationTask<TDocument>` is simpler, equally expressive, and has slightly lower overhead.

## Trigger condition

The analyzer fires when the class inherits from a multi-map index base class, and the total count of `AddMap` / `AddMapForAll` calls across all constructors is exactly one.

## What to do

Replace the multi-map base class with `AbstractIndexCreationTask<TDocument>` and replace the `AddMap<T>(lambda)` call with a `Map = lambda` assignment.

### Before (triggers RVN006)

```csharp
public class Orders_ByCompany : AbstractMultiMapIndexCreationTask
{
public Orders_ByCompany()
{
AddMap<Order>(orders => from o in orders select new { o.Company });
// Only one AddMap — a regular index would suffice
}
}
```

### After (correct)

```csharp
public class Orders_ByCompany : AbstractIndexCreationTask<Order>
{
public Orders_ByCompany()
{
Map = orders => from o in orders select new { o.Company };
}
}
```

## See also

- [RVN005](./RVN005.mdx) — fires when a multi-map index has zero `AddMap` calls.
64 changes: 64 additions & 0 deletions docs/client-api/csharp-code-analyzers/RVN007.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: "RVN007 — Query field not present in the index projection"
sidebar_label: RVN007
sidebar_position: 7
---

# RVN007 — Query field not present in the index projection

| Property | Value |
|----------|-------|
| **ID** | RVN007 |
| **Title** | Query field not present in the index projection |
| **Severity** | Info (default) |
| **Category** | Usage |
| **Code fix** | No |

## Description

When querying a specific index (via `session.Query<T, TIndex>()`), all fields referenced in `Where`, `OrderBy`, and `Search` lambdas should match the fields projected by the index's `Map` expression. A field that is not part of the index projection cannot be searched or sorted by the server, and the query will return no results for that field.

## Trigger condition

The analyzer fires when a `Where`, `OrderBy`, `OrderByDescending`, `ThenBy`, `ThenByDescending`, or `Search` lambda is found on an `IRavenQueryable<T>`, the query was created with an explicit index type argument (e.g., `Query<Order, MyIndex>()`), and a member referenced in the lambda does not appear in the set of fields extracted from the index's `Map` expression. The index class must be available in the same compilation for the field extraction to succeed; if the index is in a separate assembly the check is skipped conservatively.

## What to do

Ensure the fields you query on are part of the index projection, or switch to a different index that includes those fields.

### Before (triggers RVN007)

```csharp
public class Orders_ByCompany : AbstractIndexCreationTask<Order>
{
public Orders_ByCompany()
{
Map = orders => from o in orders select new { o.Company };
// 'Employee' is not in the projection
}
}

var results = session.Query<Order, Orders_ByCompany>()
.Where(x => x.Employee == "employees/1-A") // RVN007: Employee not indexed
.ToList();
```

### After (correct)

```csharp
public class Orders_ByCompany : AbstractIndexCreationTask<Order>
{
public Orders_ByCompany()
{
Map = orders => from o in orders select new { o.Company, o.Employee };
}
}

var results = session.Query<Order, Orders_ByCompany>()
.Where(x => x.Employee == "employees/1-A")
.ToList();
```

## See also

- [RVN008](./RVN008.mdx) — fires when a projected field is not retrievable from the index or source document.
Loading
Loading