The Entity Framework experience for Weaviate.
Define collections with attributes. Query with LINQ. Migrate schemas safely. Ship faster.
// 1. Define your model
[WeaviateCollection("Articles")]
public class Article
{
[WeaviateUUID] public Guid Id { get; set; }
[Property] public string Title { get; set; } = "";
[Property] public string Content { get; set; } = "";
[Vector<Vectorizer.Text2VecOpenAI>] public float[]? Embedding { get; set; }
}
// 2. Create a context (just like EF Core's DbContext)
public class BlogContext : WeaviateContext
{
public BlogContext(WeaviateClient client) : base(client) { }
public CollectionSet<Article> Articles { get; set; } = null!;
}
// 3. Use it
var context = new BlogContext(client);
await context.Migrate<Article>();
await context.Insert(new Article { Title = "Hello Weaviate", Content = "Vector databases are amazing." });
var results = await context.Articles.Query()
.Where(a => a.Title.Contains("Weaviate"))
.NearText("vector database tutorial")
.Limit(10);
foreach (var r in results)
Console.WriteLine($"{r.Object.Title} (score: {r.Metadata.Score})");Tip
Want low-level API access? Check out the core Weaviate C# client for direct REST/gRPC operations.
| Feature | What You Get | |
|---|---|---|
| Schema | Attribute-Driven | Define collections with [WeaviateCollection], [Property], [Vector<T>] — like EF Core |
| Queries | Type-Safe | LINQ-style .Where(), .NearText(), .Hybrid() with compile-time safety |
| Mapping | Zero Boilerplate | Automatic C# ↔ Weaviate mapping, camelCase conversion, vector handling |
| Migrations | Safe by Default | Schema evolution with breaking change detection — blocks destructive changes |
| RAG | Built-In | .Generate().SinglePrompt() for retrieval-augmented generation |
| DI | One Line | services.AddWeaviateContext<T>() for ASP.NET Core integration |
| Vectors | 47+ Vectorizers | OpenAI, Cohere, HuggingFace, Ollama, and more — configured via attributes |
| Multi-Tenancy | Native | .ForTenant("acme") scoping with immutable cloning |
dotnet add package Weaviate.Client.ManagedYour C# classes are your schema. No YAML, no JSON, no separate config files.
[WeaviateCollection(Name = "Product", Description = "Product catalog")]
public record Product
{
[WeaviateUUID]
public Guid Id { get; set; }
[Property(Description = "Product name")]
[Index(Searchable = true, Filterable = true)]
[Tokenization(PropertyTokenization.Word)]
public string Name { get; set; } = "";
[Property(DataType.Number)]
[Index(Filterable = true, RangeFilters = true)]
public decimal Price { get; set; }
[Property]
[NestedType(typeof(ProductSpecs))]
public ProductSpecs? Specs { get; set; }
[Vector<Vectorizer.Text2VecOpenAI>(VectorIndexType = typeof(VectorIndex.Hnsw))]
public float[]? Embedding { get; set; }
[Reference(Loading = Loading.Eager)]
public Category? Category { get; set; }
}Semantic search, keyword search, hybrid search, and filtering — all with IntelliSense.
// Semantic search with filters
var products = await context.Products.Query()
.Where(p => p.Price < 100 && p.Category.Name == "Electronics")
.NearText("wireless headphones")
.WithReferences()
.Limit(10);
// Hybrid search (combines BM25 + vector)
var results = await context.Products.Query()
.Hybrid("laptop", alpha: 0.7)
.OrderByScore();
// BM25 keyword search
var exact = await context.Products.Query()
.BM25(query: "USB-C charger")
.WithMetadata(MetadataOptions.Score);Return only the fields you need — with automatic metadata injection.
[QueryProjection<Product>]
public class ProductSummary
{
public string Name { get; set; } = "";
public decimal Price { get; set; }
[MetadataProperty(MetadataField.Score)]
public float? Score { get; set; }
}
var summaries = await context.Query<ProductSummary>()
.NearText("best value laptop")
.Limit(5);Define default query behavior directly on your entity or projection — no repetition at every call site.
// Entity-level defaults: apply to every query on this collection
[WeaviateCollection("Products")]
public class Product
{
[Property] public string Name { get; set; } = "";
[Property] public bool IsActive { get; set; }
// Automatically applied unless overridden
public static void ConfigureSearch(QueryConfig<Product> q) =>
q.Where(p => p.IsActive).Limit(50u);
}
// Projection-level: overrides entity defaults when this projection is used
[QueryProjection<Product>]
public class ProductSummary
{
public string Name { get; set; } = "";
public static void ConfigureSearch(QueryConfig<Product> q) =>
q.Where(p => p.IsActive).Limit(10u); // tighter limit for summaries
}
// Explicit call always wins — overrides both entity and projection hooks
var results = await context.Products.Query<ProductSummary>()
.Limit(27u) // takes precedence over ProductSummary.ConfigureSearch
.Execute();Precedence: explicit call > projection's ConfigureSearch > entity's ConfigureSearch.
Compute statistics across a collection with a typed projection — mean, sum, min, max, count, and grouping.
[QueryAggregate<Product>]
public class ProductStats
{
[Metrics(Metric.Number.Mean, Metric.Number.Sum, Metric.Number.Count, Metric.Number.Min, Metric.Number.Max)]
public Aggregate.Number Price { get; set; }
[Metrics(Metric.Integer.Mean, Metric.Integer.Sum)]
public Aggregate.Integer Stock { get; set; }
}
// Aggregate over all objects
var stats = await context.Aggregate<ProductStats>();
Console.WriteLine($"Avg price: {stats.Properties.PriceMean:C}, Total: {stats.TotalCount}");
// Filter before aggregating
var inStockStats = await context.Aggregate<ProductStats>()
.Where(p => p.InStock)
.Execute();
// Group by a property
var byCategory = await context.Aggregate<ProductStats>()
.GroupBy("category")
.Execute();
foreach (var group in byCategory.Groups)
Console.WriteLine($"{group.GroupedBy.Value}: avg {group.Properties.PriceMean:C}");You can also extract single metrics to scalar properties instead of using full Aggregate.* types:
[QueryAggregate<Product>]
public class SimpleStats
{
[Metrics("price", Metric.Number.Mean)]
public double? AveragePrice { get; set; }
[Metrics("price", Metric.Number.Count)]
public long? PriceCount { get; set; }
[Metrics("stock", Metric.Integer.Sum)]
public long? TotalStock { get; set; }
}
var stats = await context.Aggregate<SimpleStats>();
Console.WriteLine($"Average: ${stats.AveragePrice:F2}, Total stock: {stats.TotalStock}");Schema changes are analyzed before execution. Breaking changes are blocked by default.
// Single collection migration
var plan = await context.PlanMigration<Product>();
if (plan.HasBreakingChanges)
Console.WriteLine($"Breaking changes: {plan.BreakingChanges.Count}");
// Safe changes apply automatically; breaking changes require explicit opt-in
await context.Migrate<Product>(allowBreakingChanges: false);
// Migrate ALL collections in the context at once
await context.Migrate(); // Migrates all registered collections
// Check pending migrations across all collections
var pendingMigrations = await context.GetPendingMigrations();
foreach (var (collectionName, migrationPlan) in pendingMigrations)
Console.WriteLine($"{collectionName}: {migrationPlan.Changes.Count} changes");
// Advanced: migrate all + delete orphaned collections
await context.Migrate(allowBreakingChanges: false, destructive: true);First-class generative AI support with 15+ providers.
[WeaviateCollection("Documents")]
[Generative<Generative.OpenAI>]
public class Document
{
[Property] public string Content { get; set; } = "";
}
var response = await context.Documents.Query()
.NearText("climate change")
.Limit(5)
.Generate()
.SinglePrompt("Summarize in 3 sentences.")
.Execute();
Console.WriteLine(response.GeneratedText);Model relationships between collections with eager or explicit loading.
[WeaviateCollection("ProductReview")]
public record ProductReview
{
[Property] public string Title { get; set; } = "";
[Property] public double Rating { get; set; }
[Reference]
public Product? Product { get; set; }
}
// Query with references expanded
var reviews = await context.Reviews.Query()
.Where(r => r.Rating >= 4.0)
.WithReferences()
.Limit(10);
// Access the referenced product directly
var productName = reviews.First().Object.Product?.Name;Insert across multiple collections in a single unit of work.
var batch = context.Batch();
batch.Add(product1, product2, product3);
batch.Add(review1, review2);
await batch.Execute(); // Topologically sorted, cross-collectionOne-line setup for ASP.NET Core applications.
// In Program.cs
services.AddWeaviateLocal();
services.AddWeaviateContext<ProductCatalogContext>(
options => { options.AutoMigrate = true; },
eagerMigration: true
);
// In your service — just inject it
public class ProductService(ProductCatalogContext context)
{
public async Task<IEnumerable<Product>> Search(string query)
=> (await context.Products.Query().NearText(query).Limit(10)).Objects();
}Note
Ready-to-run ASP.NET Core Web API — Complete product catalog with semantic search, filtering, pagination, reviews with cross-references, and automatic data seeding.
src/Example/WebApi/ — Swagger UI, health checks, CORS for SvelteKit, and DI-wired WeaviateContext.
Additional console examples covering traditional usage, dependency injection patterns, and multi-client setups are in src/Example/.
| Managed Client (this package) | Core Client | |
|---|---|---|
| Best for | Apps with domain models | Dynamic schemas, low-level control |
| Schema | C# attributes on classes | Manual API calls |
| Queries | LINQ-style fluent builder | Direct gRPC/REST calls |
| Mapping | Automatic (both directions) | Manual serialization |
| Migrations | Built-in with safety checks | N/A |
| Learning curve | Familiar to EF Core developers | Requires Weaviate API knowledge |
Both packages can be used together — the managed client is built on top of the core client, and you can always drop down to the core API when needed.
graph TB
subgraph managed["Managed Client (this package)"]
CTX["WeaviateContext"]
ATTR["Attributes & Schema"]
QB["Query Builder"]
MAP["Object Mapper"]
MIG["Migrations"]
end
subgraph core["Core Client"]
REST["REST API"]
GRPC["gRPC API"]
end
APP["Your Application"] --> CTX
CTX --> ATTR
CTX --> QB
CTX --> MAP
CTX --> MIG
managed --> core
REST --> WV["Weaviate"]
GRPC --> WV
| Managed Client | Weaviate | .NET |
|---|---|---|
| Latest | 1.31 — 1.34+ | 8.0, 9.0 |
Tested in CI against Weaviate 1.31.20, 1.32.17, 1.33.5, and 1.34.0.
| Guide | Description | |
|---|---|---|
| 🚀 | Getting Started | 5-minute quickstart |
| 📖 | User Guide | Comprehensive usage patterns |
| 🏷️ | Attributes Reference | All 16+ attributes explained |
| 🔍 | API Reference | Complete API surface |
| 🔄 | Migrations | Schema evolution strategies |
| 🏗️ | Architecture | System design deep-dive |
| 🎯 | Advanced Patterns | Multi-tenancy, multi-vector, RAG |
| 💉 | Dependency Injection | ASP.NET Core integration |
| 🏆 | Best Practices | Production guidelines |
# Start local Weaviate (requires >= 1.31.0)
./ci/start_weaviate.sh # defaults to 1.31.0
./ci/start_weaviate.sh 1.34.0 # or specify a version
# Run all tests
dotnet test
# Managed client tests only
dotnet test src/Weaviate.Client.Managed.Tests/
# Exclude slow tests (backups, replication)
dotnet test --filter "Category!=Slow"
# Stop Weaviate
./ci/stop_weaviate.shThis project uses CSharpier for consistent formatting:
# Manual formatting
dotnet csharpier . # format all files
dotnet csharpier --check . # check only (no changes)Automatically format code before each commit using pre-commit:
# One-time setup (requires Python/pip)
pip install pre-commit # or: brew install pre-commit
# Install hooks in your local .git/hooks/
pre-commit install
# Now CSharpier runs automatically on git commit!
# Or run manually on all files:
pre-commit run --all-filesThe hook formats only staged C# files, keeping commits clean and consistent.
- Weaviate Forum — Questions and discussions
- Weaviate Slack — Live chat with the community
- GitHub Issues — Bug reports and feature requests
- Email — devex@weaviate.io
If you find this project useful, give it a ⭐ — it helps others discover it!
BSD-3-Clause — see LICENSE for details.
- Weaviate — The AI-native vector database
- Weaviate C# Client — Core REST/gRPC client
- Weaviate Documentation — Official docs
Made with ❤️ by Weaviate
Website · Documentation · Blog