*Write projections easily with generation*.
概要
*Write projections easily with generation*.
README
Features
Linqraft is a Roslyn Source Generator for easily writing IQueryable projections.
Query-based automatic DTO generation
You can write projections with UseLinqraft().Select(...) and get DTOs generated automatically based on the shape of your selector expression.
This allows for “what you see is what you get” declarations, unifying DTO definition and query writing.
Complex queries are fully supported, including nested DTOs, collections, and calculated fields.
For example, the following declaration will automatically generate OrderDto and OrderItemDto:
// orders: List
var orders = await dbContext.Orders
.UseLinqraft()
.Select(o => new {
// CustomerName: string
CustomerName = o.Customer.Name,
// Items: IEnumerable
Items = o.OrderItems.Select(oi => new {
ProductName = oi.Product.Name,
Quantity = oi.Quantity,
// calculated field are supported !
Price = oi.UnitPrice * oi.Quantity,
}),
})
.ToListAsync();
Null-propagation operator support
C# Expression Trees do not support the null-propagation operator (?.).
Linqraft generates queries through anonymous type generation, allowing you to write queries using the null-propagation operator.
var orders = await dbContext.Orders
.UseLinqraft()
.Select(o => new {
// CustomerName: string?
CustomerName = o.Customer?.Name,
// Items: IEnumerable
ProductNames = o.OrderItems.Select(oi => oi.Product?.Name),
// > If all OrderItems.Products are null, the array will be empty.
})
.ToListAsync();
Drop-in Replacement Analyzers
Analyzers are provided to replace existing Select code with Linqraft. The replacement is completed in an instant.
// before
// order is (anonymous type)
var order = await dbContext.Orders
.Select(o => new {
CustomerName = o.Customer != null ? o.Customer.Name : null,
Items = o.OrderItems.Select(oi => new {
ProductName = oi.Product != null ? oi.Product.Name : null,
}),
})
.FirstOrDefaultAsync();
// after (apply code fix)
// order is OrderDto✨️
var order = await dbContext.Orders
.UseLinqraft().Select(o => new {
// automatically replace null checks with null-propagation operator
CustomerName = o.Customer?.Name,
Items = o.OrderItems.Select(oi => new {
ProductName = oi.Product?.Name,
}),
})
.FirstOrDefaultAsync();
Projection Helpers for simplify
Linqraft also supports explicit projection helpers for cases where you want the generator to rewrite part of a selector body:
var rows = dbContext.Orders
.UseLinqraft()
// add 2nd argument to Select, which is a helper object
// that provides methods for rewriting parts of the selector body.
.Select((o, helper) => new
{
CustomerName = helper.AsLeftJoin(o.Customer).Name,
RequiredCustomerName = helper.AsInnerJoin(o.Customer).Name,
Customer = helper.AsProjection(o.Customer),
CustomerSummary = helper.Project(o.Customer).Select(customer => new
{
customer.Id,
customer.Name,
}),
FirstLargeItem = helper.AsInline(o.FirstLargeItemProductName),
});
More details about projection helpers are available in the Projection Helpers section below.
No Dependencies
Thanks to the magic of Source Generators and Interceptors, there are no dependencies in production. The generated code consists of simple LINQ queries that work with any LINQ provider, such as EF Core or Dapper.
Installation
Linqraft is available on NuGet. Install it with the following command:
dotnet add package Linqraft
Linqraft requires C# 12.0 or later. On .NET 8 and later, it works out of the box.
AI Agent Integration (APM)
Linqraft ships an APM skill so AI coding agents can discover and use the library automatically. Install it with:
# use APM (installs only the skills/ package files via sparse checkout)
apm install arika0093/Linqraft/skills
# or install manually
curl https://raw.githubusercontent.com/arika0093/Linqraft/main/skills/.apm/skills/linqraft/SKILL.md -o your/skills/linqraft/SKILL.md
Since Linqraft’s DTO generation is an unfamiliar pattern for AI, installing the skill is recommended.
Basic Usage
Linqraft supports anonymous projections, auto-generated DTOs, pre-existing DTOs, grouped and flattened projections, and runtime DTO generation through LinqraftKit.Generate.
Explicit DTO Pattern
Use UseLinqraft().Select(...) when you want Linqraft to generate a named DTO from the selector body.
var orders = await dbContext.Orders
.UseLinqraft().Select(o => new
{
o.Id,
CustomerName = o.Customer?.Name,
CustomerCountry = o.Customer?.Address?.Country?.Name,
Items = o.OrderItems.Select(oi => new
{
ProductName = oi.Product?.Name,
oi.Quantity,
}),
})
.ToListAsync();
Linqraft generates a partial OrderDto and any required nested DTOs automatically.
public partial class OrderDto
{
public required int Id { get; set; }
public required string? CustomerName { get; set; }
public required string? CustomerCountry { get; set; }
public required IEnumerable Items { get; set; }
}
namespace LinqraftGenerated_HASH
{
public partial class ItemsDto
{
public required string? ProductName { get; set; }
public required int Quantity { get; set; }
}
}
The same pattern also works for in-memory IEnumerable pipelines:
var orders = myList
.UseLinqraft().Select(o => new
{
o.Id,
CustomerName = o.Customer?.Name,
})
.ToList();
Anonymous Pattern
Use UseLinqraft().Select(...) without generic type parameters when you only need a one-off result shape.
var orders = await dbContext.Orders
.UseLinqraft().Select(o => new
{
o.Id,
CustomerName = o.Customer?.Name,
TotalAmount = o.OrderItems.Sum(oi => oi.Quantity * oi.UnitPrice),
})
.ToListAsync();
This is ideal for quick exploration, ad-hoc queries, and prototypes.
[!NOTE] This is mostly the same as a normal
Select(new => { ... }), but with support for features like the null-propagation operator.
Pre-existing DTO Pattern
Use UseLinqraft().Select(...) with your own DTO class when the type already exists or must carry custom attributes and interfaces.
public class OrderDto
{
[JsonPropertyName("order_id")]
public int Id { get; set; }
[Required]
public string CustomerName { get; set; } = "";
public decimal TotalAmount { get; set; }
}
var orders = await dbContext.Orders
.UseLinqraft()
.Select(o => new OrderDto
{
Id = o.Id,
CustomerName = o.Customer?.Name ?? string.Empty,
TotalAmount = o.OrderItems.Sum(oi => oi.Quantity * oi.UnitPrice),
})
.ToListAsync();
This keeps Linqraft’s null-propagation support while leaving the DTO definition fully under your control.
Aggregation and Flattening Helpers
In addition to Select, the following two projection patterns are also available (same as normal LINQ):
SelectMany: flattening projections over nested collectionsGroupBy: projections over groupings
Use GroupBy(...) when the final result is a projection over an IGrouping:
var result = dbContext.HealthChecks
.UseLinqraft()
.GroupBy(
check => check.Region,
group => new
{
Region = group.Key,
AllHealthy = group.All(x => x.Status == "Healthy"),
Checks = group.Select(x => new
{
x.Id,
x.Status,
}),
})
.ToListAsync();
Use SelectMany(...) when you want the final result to be flattened:
var rows = dbContext.Orders
.UseLinqraft()
.SelectMany(order =>
order.Items.Select(item => new
{
OrderId = order.Id,
item.ProductName,
item.Quantity,
}))
.ToListAsync();
LinqraftKit.Generate
Use LinqraftKit.Generate(...) when you want Linqraft to generate a DTO from a runtime object instead of from an IEnumerable or IQueryable projection.
var id = 42;
var dto = LinqraftKit.Generate(
new {
Id = id,
Customer = new { Name = "Ada" },
ItemNames = new[] { "Keyboard", "Mouse" },
},
capture: () => id
);
This works well for combining runtime values, nested anonymous objects, arrays, lists, and values produced by other Linqraft projections.
Usage Documentation
Local Variable Capture
Pass the required local variables to capture: via a delegate (Func) that returns either a single value or an anonymous object/tuple for multiple values, like this:
var threshold = 100;
var multiplier = 2;
var suffix = " units";
var converted = dbContext.Entities
.UseLinqraft()
.Select(
x => new
{
x.Id,
IsExpensive = x.Price > threshold,
DoubledValue = x.Value * multiplier,
Description = x.Name + suffix,
},
capture: () => (threshold, multiplier, suffix)
);
Linqraft analyzers can detect missing capture values and can rewrite legacy anonymous-object captures to the delegate form automatically.
Array Nullability Removal
Linqraft can automatically remove nullability from generated collection properties when an empty collection is a better semantic result than null.
This avoids repetitive post-processing such as dto.Items ?? [].
var dto = query
.UseLinqraft()
.Select(e => new
{
// ChildNames: List
ChildNames = e.Child?.Select(c => c.Name).ToList(),
// ChildDtos: IEnumerable
ChildDtos = e.Child?.Select(c => new { c.Name, c.Description }),
});
Partial Classes
All generated DTOs are partial, so you can extend them with methods, interfaces, attributes, or predeclared properties whose accessibility you control yourself.
// can add methods, interfaces, attributes, etc.
[DebuggerDisplay("{GetDisplayName()}")]
public partial class OrderDto : ISampleInterface
{
public string GetDisplayName() => $"Order #{Id} - {CustomerName}";
public bool IsLargeOrder => TotalAmount > 1000;
}
var order = await dbContext.Orders
.UseLinqraft()
.Select(o => new
{
o.Id,
CustomerName = o.Customer?.Name,
TotalAmount = o.OrderItems.Sum(oi => oi.Quantity * oi.UnitPrice),
})
.FirstOrDefaultAsync();
// you can call the method you added to the partial class
if (order is not null)
{
Console.WriteLine($"{order.GetDisplayName()} is {(order.IsLargeOrder ? "large" : "small")}.");
}
Mapping Methods
Linqraft normally generates projection code inline through interceptors, but sometimes you want a reusable extension method that can be called from multiple places or embedded inside EF Core compiled queries through EF.CompileAsyncQuery. In that case, you can write a template method and let Linqraft generate the full mapping method based on it.
For example, you can write a template like this:
// your declaration
public static partial class OrderQueries
{
[LinqraftMapping]
internal static IQueryable ProjectToOrderDto(this LinqraftMapper source) =>
source.Select(o => new
{
o.Id,
CustomerName = o.Customer?.Name,
Items = o.Items.Select(i => new
{
i.ProductName,
i.Quantity,
}),
});
}
// generated code such as:
public static partial class OrderQueries
{
internal static IQueryable ProjectToOrderDto(this IQueryable source) =>
source.Select(o => new OrderDto { /* ... */ });
internal static IEnumerable ProjectToOrderDto(this IEnumerable source) =>
source.Select(o => new OrderDto { /* ... */ });
}
// and you can call it like this:
var orders = await dbContext.Orders
.ProjectToOrderDto()
.ToListAsync();
This is useful in cases such as:
- Reusable projections shared across multiple callers
- EF Core compiled queries through
EF.CompileAsyncQuery - EF Core precompiled queries on newer runtimes
- Dedicated query classes for complex projections
Projection Helpers
Projection helpers are exposed through the selector’s IProjectionHelper parameter.
They let you ask Linqraft to rewrite a specific fragment inside the generated projection instead of treating the selector body as a plain expression tree.
Analyzers
Linqraft ships analyzers and code fixes that can migrate ordinary Select calls to UseLinqraft().Select(...), add missing captures, simplify null checks, and highlight projection-specific issues.
See Analyzers for the full rule list and code-fix catalog.
Advanced Features
FAQ
How is the performance?
Linqraft’s performance is designed to be on par with hand-written DTO code because it generates the same code you would write manually.
Don’t believe it? Check out the Benchmark.
Does Linqraft only work with Entity Framework?
No. Linqraft works with any LINQ provider that supports IEnumerable and/or IQueryable.
// Entity Framework Core
var efOrders = await dbContext.Orders
.UseLinqraft()
.Select(o => new { o.Id, o.CustomerName })
.ToListAsync();
// In-memory collection
var inMemoryOrders = myList
.UseLinqraft()
.Select(o => new { o.Id, o.CustomerName })
.ToList();
// LINQ to Objects
var filtered = items
.Where(x => x.IsActive)
.UseLinqraft()
.Select(x => new { x.Id, x.Name })
.ToArray();
Can generated DTOs be used elsewhere?
Yes. Generated DTOs are regular C# classes, so they work as API response models, method return types, parameters, variables, serializer targets, and test fixtures.
[HttpGet]
public async Task>> GetOrders()
{
var orders = await dbContext.Orders
.UseLinqraft()
.Select(o => new { o.Id, o.CustomerName })
.ToListAsync();
return Ok(orders);
}
Can I inspect the generated code?
Yes. Use F12 / Go to Definition on a generated DTO name, or emit the generated files to disk:
true
Generated
License
Copyright 2025-2026 arika0093
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
推奨ツール
別のキーワードを試すか、フィルタを外してください。
インストール
npx skillfish add arika0093/linqraft