# In-Memory Repository

> **Renamed:** This project was renamed from **Deveel.Repository** to **Kista** on **May 26, 2025**. The name *Kista* is Old Norse for "chest" or "repository", better reflecting the project purpose as a data access framework.

| Feature         | Status | Notes                                        |
| --------------- | :----: | -------------------------------------------- |
| Base Repository |    ✅   |                                              |
| Filterable      |    ✅   |                                              |
| Queryable       |    ✅   | Native .NET `IQueryable`                     |
| Pageable        |    ✅   |                                              |
| Tracking        |    ❌   |                                              |
| Multi-tenant    |    ❌   |                                              |
| Thread-Safe     |    ✅   | `ReaderWriterLockSlim` for concurrent access |

The `InMemoryRepository<TEntity>` class stores entities in the memory of the running process. It is intended for **testing, prototyping, and scenarios where data persistence is not required**.

## Thread Safety

`InMemoryRepository<TEntity>` is fully thread-safe. All read operations acquire a shared read lock, and all write operations acquire an exclusive write lock via `ReaderWriterLockSlim`. Multiple threads can safely read and write to the same repository instance concurrently.

## Installation

```bash
dotnet add package Kista.InMemory
```

## Registration

Use the fluent builder API to register the In-Memory driver:

```csharp
// Program.cs
builder.Services.AddRepositoryContext()
    .UseInMemory();
```

You can also configure field mappers and initial data:

```csharp
builder.Services.AddRepositoryContext()
    .UseInMemory(b => b
        .WithFieldMapper<MyEntity, MyFieldMapper>()
        .WithInitialData(new[] { new MyEntity { /* ... */ } }));
```

### Pre-seeding Data

You can seed initial data using the `WithInitialData` method:

```csharp
var seeded = new List<MyEntity> { /* ... */ };
builder.Services.AddRepositoryContext()
    .UseInMemory(b => b.WithInitialData(seeded));
```

Or register the repository directly with pre-seeded data:

```csharp
var seeded = new List<MyEntity> { /* ... */ };
builder.Services.AddSingleton(new InMemoryRepository<MyEntity>(seeded));
```

## Lifecycle Support

The In-Memory driver provides `InMemoryRepositoryLifecycleHandler<TEntity>` for lifecycle orchestration (create, drop, seed). It is **disabled by default** and must be explicitly enabled.

### Enabling Lifecycle

```csharp
builder.Services.AddRepositoryContext()
    .UseInMemory(b => b.WithLifecycle())
    .ConfigureLifecycle(options => {
        options.SeedStrategy = SeedStrategy.Always;
    });
```

### Handler Behavior

| Operation     | Behavior                                                                                                                                                                    |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ExistsAsync` | Always returns `false` (in-memory storage is transient).                                                                                                                    |
| `CreateAsync` | No-op (no physical resource to create).                                                                                                                                     |
| `DropAsync`   | No-op (no physical resource to drop).                                                                                                                                       |
| `SeedAsync`   | Resolves the `IRepository<TEntity>` from DI and inserts data via `AddRangeAsync` / `AddAsync`. Supports `IEnumerable<TEntity>`, `IEnumerable<object>`, and single entities. |

### Seeding Examples

**Using a provider class:**

```csharp
public class ProductSeedProvider : IRepositorySeedDataProvider<Product> {
    public IEnumerable<Product> GetSeedData() {
        yield return new Product { Name = "Widget", Price = 9.99m };
        yield return new Product { Name = "Gadget", Price = 24.99m };
    }

    IEnumerable<object> IRepositorySeedDataProvider.GetSeedData()
        => GetSeedData().Cast<object>();
}

// Program.cs
builder.Services.AddRepositoryContext()
    .UseInMemory(b => b.WithLifecycle())
    .ConfigureLifecycle(options => {
        options.SeedStrategy = SeedStrategy.Always;
    })
    .WithSeedData<Product, ProductSeedProvider>();
```

**Using inline data (no provider class needed):**

```csharp
builder.Services.AddRepositoryContext()
    .UseInMemory(b => b.WithLifecycle())
    .ConfigureLifecycle(options => {
        options.SeedStrategy = SeedStrategy.Always;
    })
    .WithSeedData<Product>(new[] {
        new Product { Name = "Widget", Price = 9.99m },
        new Product { Name = "Gadget", Price = 24.99m }
    });
```

The handler resolves the provider during seeding and inserts the entities through `IRepository<Product>.AddRangeAsync`.

## Querying

`InMemoryRepository<TEntity>` implements both `IQueryableRepository<TEntity>` and `IFilterableRepository<TEntity>`.

**With LINQ (`IQueryableRepository`):**

```csharp
var items = repository.AsQueryable()
    .Where(x => x.IsActive)
    .OrderBy(x => x.Name)
    .ToList();
```

**With filter types (`IFilterableRepository`):**

The only supported filter type is `ExpressionQueryFilter<TEntity>` (backed by a lambda expression). You can pass it directly or use the convenience `Query.Where<TEntity>` factory:

```csharp
// Using ExpressionQueryFilter
var query = new Query(new ExpressionQueryFilter<MyEntity>(x => x.Name == "John"));
var items = await repository.FindAllAsync(query);

// Using lambda shorthand (extension method)
var items = await repository.FindAllAsync(x => x.Name == "John");
```

> Passing an unsupported filter type (e.g., a MongoDB-specific filter) will throw `NotSupportedException`.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://kista.deveel.org/repository-implementations/in-memory.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
