Skip to content
Open
58 changes: 58 additions & 0 deletions docs/client-api/csharp-code-analyzers/RVN001.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: "RVN001 — Index Map or Reduce assigned outside constructor"
sidebar_label: RVN001
sidebar_position: 1
---

# RVN001 — Index Map or Reduce assigned outside constructor

- **Severity**: Info (default)
- **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`.
48 changes: 48 additions & 0 deletions docs/client-api/csharp-code-analyzers/RVN002.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
title: "RVN002 — RavenDB query operator after projection"
sidebar_label: RVN002
sidebar_position: 2
---

# RVN002 — RavenDB query operator after projection

- **Severity**: Info (default)
- **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.
43 changes: 43 additions & 0 deletions docs/client-api/csharp-code-analyzers/RVN003.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
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

- **Severity**: Info (default)
- **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.
49 changes: 49 additions & 0 deletions docs/client-api/csharp-code-analyzers/RVN004.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
title: "RVN004 — AbstractIndexCreationTask subclass is missing a Map assignment"
sidebar_label: RVN004
sidebar_position: 4
---

# RVN004 — AbstractIndexCreationTask subclass is missing a Map assignment

- **Severity**: Info (default)
- **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.
49 changes: 49 additions & 0 deletions docs/client-api/csharp-code-analyzers/RVN005.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
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

- **Severity**: Info (default)
- **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.
51 changes: 51 additions & 0 deletions docs/client-api/csharp-code-analyzers/RVN006.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
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

- **Severity**: Info (default)
- **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.
59 changes: 59 additions & 0 deletions docs/client-api/csharp-code-analyzers/RVN007.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
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

- **Severity**: Info (default)
- **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