Vidyano.SourceGenerators
3.6.0
Prefix Reserved
dotnet add package Vidyano.SourceGenerators --version 3.6.0
NuGet\Install-Package Vidyano.SourceGenerators -Version 3.6.0
<PackageReference Include="Vidyano.SourceGenerators" Version="3.6.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Vidyano.SourceGenerators" Version="3.6.0" />
<PackageReference Include="Vidyano.SourceGenerators"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Vidyano.SourceGenerators --version 3.6.0
#r "nuget: Vidyano.SourceGenerators, 3.6.0"
#:package Vidyano.SourceGenerators@3.6.0
#addin nuget:?package=Vidyano.SourceGenerators&version=3.6.0
#tool nuget:?package=Vidyano.SourceGenerators&version=3.6.0
Disclaimer
- Use of SourceGenerators depend on available content.
- Analyzers and CodeFixes are available in any project.
Some Patterns are not supported by Analyzers at the moment:
- When Clause on Switch Expression / Statements
Generators
- Actions Generator
- BusinessRules Generator
- Context Generator
- CustomAction Generator
- Index Generator
- Model Generator
- ProjectTargetContextType Generator
- Recipients Generator
- SplittedValueObjects Generator
Generated Constant classes
All generated Constant classes are available under {RootNamespace}.Service.Generated namespace.
BusinessRuleNamesPersistentObjectTypesPersistentObjectAttributeNamesProgramUnitNamesProgramUnitItemsNamesQueryNamesQuerySourcesActionNamesLanguagesMessageKeysandMessagesAppRolesWebsiteNamesObsolete.PersistentObjectTypes
Dependency Injection
By using the InitializeByCtor attribute on a field, the source generator will inject this field via the generated constructor.
The Attribute can only be applied when no constructor is provided.
public partial class CompanyActions
{
[InitializeByCtor]
private readonly IMyService myService;
}
Attribute is available via reference Vidyano.Abstractions.
Async actions
Select the async actions base before writing any overrides:
[Async]
public partial class CustomerActions { }
The attribute is Vidyano.Service.Repository.AsyncAttribute in Vidyano.Abstractions.
The async family is available when Vidyano copies AsyncPersistentObjectActionsReference<,,>
into the application, next to the synchronous PersistentObjectActionsReference<,,>. Without
it the synchronous family is generated, whatever the preference says and whether or not an
async default is declared. The generator uses
DefaultAsyncPersistentObjectActions<TContext, TEntity, TQueryEntity>, which inherits that
reference and carries application-defined constructor dependencies and constraints. It is
generated whenever the reference is present. An application can declare that default as a
partial class or provide its implementation explicitly.
Without an explicit default base, the generated sync default inherits the application's
PersistentObjectActionsReference<,,> and the async default inherits
AsyncPersistentObjectActionsReference<,,>, both from the application's Service namespace.
Explicit bases take precedence. Both references can also be used directly as actions bases;
their constructors accept the target context.
Classes with an explicit base keep that base. A conflicting [Async] produces
VIDYANO0030. For classes without an explicit selection, configure the preference:
is_global = true
actions_source_generator_preferred_base = auto
Allowed values are auto, sync, and async; invalid values produce VIDYANO0031.
The default is auto, which considers only application-declared defaults:
| Declared defaults | Auto selects |
|---|---|
| Neither | Sync |
| Only DefaultPersistentObjectActions | Sync |
| Only DefaultAsyncPersistentObjectActions | Async |
| Both | Sync |
[Async] overrides the project preference. For gradual adoption, pin the preference to
sync before introducing an async default, then mark individual classes. Selection does
not inspect method bodies. Empty classes get override IntelliSense immediately, and
changing the preference updates the generated base without restarting the editor.
The resolved preference also controls runtime fallback for entities without an actions class when using the coordinated framework version. The generator emits assembly metadata so no separate runtime setting is needed.
VIDYANO0032 warns when a project uses async persistent-object actions but declares only
DefaultPersistentObjectActions. Its custom behavior, including Where and data-security
rules, is not inherited by the async family. Declare DefaultAsyncPersistentObjectActions
and port or share that behavior. Sync-only projects do not receive this warning. Declaring
both defaults removes the warning; the generator cannot verify that their logic is equivalent.
Lookup actions have their own default per family: DefaultLookupPersistentObjectActions
for sync and DefaultLookupAsyncPersistentObjectActions for async. Either one lets the
generator create the {Entity}Actions classes for lookup entities. The async lookup default
overrides GetEntityAsync; reference and key/value sorting keep the existing Value ordering.
RavenDB session mode is selected by the operation's context. The marker does not open
another session. Context discovery recognizes TargetRavenDBAsyncContext.
Index Generator
Generate an index by adding the GenerateIndex attribute to an entity, a nested objects class within an entity, or any deeper nested structure.
Index Types
The generator supports two types of indexes:
1. Entity Indexes
For entities, this will generate:
- A partial
{Entity}class with aQueryTypeattribute. - A partial
{VEntity}QueryType class. - A partial
{Entities}_OverviewIndex class. - A
V{Entities}property on theContextif it does not already exist.
Example:
[GenerateIndex]
public partial class Customer
{
public string Id { get; set; }
public string Name { get; set; }
}
2. Fan-out Indexes (Nested Collections)
For nested objects within entities, you can create fan-out indexes that flatten nested collections. These classes can optionally be marked with [ValueObject] but this is not required:
[GenerateIndex(typeof(Country), nameof(Country.Cities), nameof(City.Streets))]
[ValueObject(typeof(Country))]
public partial class Street
{
[ValueKey]
public string Id { get; set; }
public string Name { get; set; }
}
This creates an index that fans out from Country → Cities → Streets, making it easy to query all streets across all cities in all countries.
Fan-out Index Requirements:
- Must specify a root entity type (e.g.,
typeof(Country)) - Must provide a path through collections (e.g.,
nameof(Country.Cities), nameof(City.Streets)) - The final segment in the path must be the collection containing the target class
The generated context includes query methods for each level of the fan-out path:
public partial class ProjectContext
{
// Query streets by city
public IRavenQueryable<VStreet> City_Streets(CustomQueryArgs args)
...
// Query streets by country
public IRavenQueryable<VStreet> Country_Cities_Streets(CustomQueryArgs args)
...
public IRavenQueryable<VStreet> VStreets
...
}
3. Fan-out Indexes with SplittedCollection
For value objects stored in separate documents using [SplittedCollection], the Index Generator automatically detects the splitted collection and uses the root entity's Id as the ObjectId prefix in the generated index:
public class CarChanges
{
public string Id { get; set; } = null!;
[Reference(typeof(Car))]
public string Car { get; set; } = null!;
[SplittedCollection(typeof(Car))]
public List<CarChange> Changes { get; set; } = new();
}
[GenerateIndex(typeof(CarChanges), nameof(CarChanges.Changes))]
[ValueObject(typeof(CarChanges))]
public partial class CarChange
{
[ValueKey]
public string Id { get; set; } = null!;
public string Column { get; set; } = null!;
public string? OriginalValue { get; set; }
public string? NewValue { get; set; }
}
This generates an index where the ObjectId is constructed using the Car reference (from the [SplittedCollection] attribute) instead of the CarChanges.Id, ensuring proper linking back to the root entity.
The generated context query method is named based on the splitted root type: Car_Changes instead of CarChanges_Changes:
public partial class ProjectContext
{
// Query method named after splitted root (Car) + collection (Changes)
public IRavenQueryable<VCarChange> Car_Changes(CustomQueryArgs args)
...
public IRavenQueryable<VCarChange> VCarChanges =>
...
}
Important Notes:
- Index will not be generated if
QueryTypeorIndexis already added manually, except if they are partial. - If the index contains an
Idproperty, theIIdinterface will be applied - If the index contains any audit fields, the corresponding
IAudit...interface will be applied
Additional Attributes
You can add several attributes to control the index:
On the Class
IndexReferenceProperty: When used at the class level, includes properties from the root entity or parent entities in the fan-out path for nested collection indexes.When used on a class:
[GenerateIndex(typeof(Country), nameof(Country.Cities), nameof(City.Streets))] [IndexReferenceProperty(typeof(Country), "Id")] [IndexReferenceProperty(typeof(Country), "Name", Search = true)] [IndexReferenceProperty(typeof(City), nameof(City.Name))] public partial class Street { ... }Parameters:
- First parameter: Type of the reference entity (e.g.,
typeof(Country)) - Second parameter: Property path (e.g.,
nameof(Country.Name)or"Address.CityName") - Optional
Search: Creates a second{Property}_Sortproperty to allow full search (only needed when values can contain spaces) - Optional
IgnoreProperty: Include this property in the index mapping but do not sync to Vidyano
- First parameter: Type of the reference entity (e.g.,
IndexCustomVariable: Define custom variables for use in the index Map function.[IndexCustomVariable("countryDetails", """LoadDocument<CountryDetail>(country.Id + "/details")""")]IndexCustomWhere: Add custom where clauses to filter indexed documents.[IndexCustomWhere("countryDetails != null")]IndexCustomProperty: Add custom computed properties to the index.[IndexCustomProperty("HasDetails", typeof(bool), "countryDetails != null", IgnoreProperty = true)] [IndexCustomProperty("InternalCode", typeof(string), "countryDetails.Code", Nullable = false)]Parameters:
- Optional
Nullable: Specifies if the property type is nullable (for reference types). For value types, usetypeof(bool?)directly - Optional
IgnoreProperty: Include this property in the index mapping but do not sync to Vidyano
Note: For
IndexCustomVariableandIndexCustomWhere, the order is important and will be reflected in the generated index.- Optional
On Properties
Search: Creates a second{Property}_Sortproperty to allow full search on this property.Note: This is only needed when values can contain spaces.
IgnoreForIndex: This property will not be included in the index.IgnoreProperty: This attribute will be copied to the QueryType.IndexReferenceProperty: Include properties from a reference in the index.When used on a property:
[IndexReferenceProperty(nameof(Person.Name), Search = true)] [IndexReferenceProperty("Address.CityName")] [IndexReferenceProperty("InternalCode", IgnoreProperty = true)] [Reference(typeof(Person))] public string Person { get; set; }Parameters:
- First parameter: Property path (e.g.,
nameof(Person.Name)or"Address.CityName") Search: Creates a second{Property}_Sortproperty to allow full search (only needed when values can contain spaces)IgnoreProperty: Include this reference property in the index mapping but do not sync to Vidyano
Note:
- The reference type is inferred from the
[Reference]attribute - If no
Referenceattribute is found, you can specify the type as the first parameter onIndexReferenceProperty
- First parameter: Property path (e.g.,
[JsonProperty(DefaultValueHandling = Ignore | IgnoreAndPopulate)]on a non-nullableboolproperty:Json.NET omits these fields from the stored JSON when the value equals the configured default, so the index would otherwise fault when reading a missing field. The generator emits a comparison whose result matches what a
session.Load<T>()would yield for the missing field — keeping the index in lockstep with deserialization:Handling mode Other signal Generated expression Ignore= trueC# initializerx == truebecomesx != falseIgnoreanything else (incl. [DefaultValue(true)])x == trueIgnoreAndPopulate[DefaultValue(true)]x != falseIgnoreAndPopulateanything else x == trueTwo counter-intuitive cases worth knowing:
- With
Ignore,[DefaultValue(true)]is ignored — Json.NET doesn't consult it during deserialization in this mode. - With
IgnoreAndPopulate, the C# initializer (= true) is overwritten todefault(T)—Populateconsults[DefaultValue]instead.
Applies only to non-nullable
bool.bool?properties and other types are left unchanged.- With
Complete Example
[GenerateIndex(Index = typeof(Indexes.City_Overview))]
public partial class City
{
public string Id { get; set; }
[Search]
public string Name { get; set; }
[IgnoreForIndex]
public string InternalNotes { get; set; }
[IndexReferenceProperty(nameof(Country.Name), Search = true)]
[IndexReferenceProperty(nameof(Country.Code), IgnoreProperty = true)]
[Reference(typeof(Country))]
public string Country { get; set; }
public List<Street> Streets { get; set; }
}
[GenerateIndex(typeof(City), nameof(City.Streets))]
[IndexCustomVariable("cityDetails", """LoadDocument<CityDetail>(city.Id + "/details")""")]
[IndexCustomWhere("cityDetails != null")]
[IndexCustomProperty("HasCityDetails", typeof(bool), "cityDetails != null", Nullable = false)]
[IndexReferenceProperty(typeof(City), "Country.Name")]
[ValueObject(typeof(City))]
public partial class Street
{
[ValueKey]
public string Id { get; set; }
public string Name { get; set; }
[IndexReferenceProperty(nameof(House.Id), IgnoreProperty = true)]
[Reference(typeof(House))]
public string House { get; set; }
}
Recipients Generator
Generate recipient and courier queue registrations by implementing IRecipient<TMessage>.
Add services.Add{ProjectName}Recipients() to register all recipients and courier queues.
Courier queue support:
Messages decorated with [CourierQueue("...")] will automatically generate .AddCourierQueue("...") calls in the registration method. Queue names are deduplicated and sorted alphabetically, and placed before the .AddRecipient<>() calls.
Example:
[CourierQueue("QueueA")]
public record QueueAMessage(string Name);
public record DefaultMessage(string Name);
public class QueueAHandler : IRecipient<QueueAMessage>
{
public Task ReceiveAsync(ReceiveArgs<QueueAMessage> args, CancellationToken cancellationToken)
=> Task.CompletedTask;
}
public class DefaultHandler : IRecipient<DefaultMessage>
{
public Task ReceiveAsync(ReceiveArgs<DefaultMessage> args, CancellationToken cancellationToken)
=> Task.CompletedTask;
}
Generated output:
public static class RecipientsEx
{
public static IServiceCollection AddMyProjectRecipients(this IServiceCollection services)
{
return services
.AddCourierQueue("QueueA")
.AddRecipient<QueueAMessage, QueueAHandler>()
.AddRecipient<DefaultMessage, DefaultHandler>();
}
}
Messages without [CourierQueue] use the default queue and no AddCourierQueue call is generated for them.
SplittedValueObjects Generator
Generate registration for splitted value objects by adding the SplittedCollection attribute to a collection property.
Add services.AddSplittedValueObjects() to register all splitted value objects.
Attribute syntax:
[SplittedCollection(typeof(RootType), suffix?: string)]
Parameters:
- First parameter (required):
typeof(RootType)- The root entity type - Second parameter (optional):
suffix- Id suffix (e.g.,"/invoiceLines")
Requirements:
- Element type must be decorated with
[ValueObject] - Containing class must have a
[Reference]property of the RootType
Example:
public class CarChanges
{
public string Id { get; set; } = null!;
[Reference(typeof(Car))]
public string Car { get; set; } = null!;
[SplittedCollection(typeof(Car))]
public List<CarChange> Changes { get; set; } = new();
}
Projects
Main project
Additional Files & Global Usings
Add following ItemGroup to the .csproj project file.
<ItemGroup>
<AdditionalFiles Include="App_Data\**\*.json" />
<Using Include="$(MSBuildProjectName).Service.Generated" />
<Using Alias="Types" Include="$(MSBuildProjectName).Service.Generated.PersistentObjectTypes.$(MSBuildProjectName)" />
<Using Alias="AttributeNames" Include="$(MSBuildProjectName).Service.Generated.PersistentObjectAttributeNames.$(MSBuildProjectName)" />
<Using Alias="QueryNames" Include="$(MSBuildProjectName).Service.Generated.QueryNames.$(MSBuildProjectName)" />
<Using Alias="QuerySources" Include="$(MSBuildProjectName).Service.Generated.QuerySources.$(MSBuildProjectName)" />
</ItemGroup>
External Context
If the context file does not exist in the main project, you can use the appsettings.json file as an alternative, as the context will be read from there.
<ItemGroup>
<AdditionalFiles Include="appsettings.json" />
</ItemGroup>
Library project
Missing App_Data json files warning
When incorporating SourceGenerators into a library project, you may encounter a VIDYANO0010 warning indicating missing App_Data JSON files. This warning is not applicable to library projects, as they do not utilize App_Data.
To bypass this warning, two methods are available.
- By disabling the Model SourceGenerator via
.editorconfigfile (see EditorConfig Settings)
is_global = true
disable_model_source_generator = true
- By ignoring the warning via csproj file
<NoWarn>VIDYANO0010</NoWarn>
EditorConfig Settings
When your project or library does not follow the default naming conventions, you can configure the source generators via .editorconfig file.
Create or update your .editorconfig file with the following settings:
is_global = true
# Global Settings (apply to all generators)
source_generator_namespaces = MyNamespace, AnotherNamespace
source_generator_excluded_namespaces = MyNamespace.Internal
# Per-Generator Settings
# Disable specific generators
disable_model_source_generator = true
disable_actions_source_generator = true
disable_businessrules_source_generator = true
disable_context_source_generator = true
disable_customaction_source_generator = true
disable_index_source_generator = true
disable_projecttargetcontexttype_source_generator = true
disable_recipients_source_generator = true
disable_splittedvalueobjects_source_generator = true
# Per-generator namespace configuration (overrides global settings)
model_source_generator_namespaces = MyNamespace
model_source_generator_excluded_namespaces = MyNamespace.Internal
Available Settings
| Setting | Description |
|---|---|
source_generator_namespaces |
Comma-separated list of namespaces to include (global) |
source_generator_excluded_namespaces |
Comma-separated list of namespaces to exclude (global) |
disable_{generator}_source_generator |
Set to true to disable a specific generator |
{generator}_source_generator_namespaces |
Namespaces to include for a specific generator |
{generator}_source_generator_excluded_namespaces |
Namespaces to exclude for a specific generator |
Notes:
- Excluded namespaces are typically used to omit sub-namespaces from an included parent namespace (e.g., including
MyNamespacebut excludingMyNamespace.Internal). - When overriding default excluded namespaces, be aware that source generator behavior can change. For example, all generators except
customactionshould exclude the custom actions namespace (e.g.,{RootNamespace}.Service.CustomActions) to function as expected.
Generator Names
Use these names in the settings (lowercase):
model- Model Generatoractions- Actions Generatorbusinessrules- BusinessRules Generatorcontext- Context Generatorcustomaction- CustomAction Generatorindex- Index Generatorprojecttargetcontexttype- ProjectTargetContextType Generatorrecipients- Recipients Generatorsplittedvalueobjects- SplittedValueObjects Generator
Default Behavior
By default, generators scan:
- The root namespace of your project
- The
CronosCorenamespace
And exclude:
Microsoft.*,System.*,JetBrains.*,Raven.*namespaces (always excluded){RootNamespace}.Service.CustomActionsnamespace
Exceptions
When using EmitCompilerGeneratedFiles in .csproj project file you can get exceptions when compiling in Windows because of use of long filenames.
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
To fix this issue you need to add the following registration key (Reboot needed to take effect)
reg ADD HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem /v LongPathsEnabled /t REG_DWORD /d 1
Code Refactorings
Extract Index, QueryType and Context members
When you need to customize a generated index beyond what the [GenerateIndex] attribute supports, you can "eject" the generated code into explicit files using the Extract Index, QueryType and Context members code refactoring.
How to Use
- Place your cursor on the
[GenerateIndex]attribute - Open the lightbulb menu (Ctrl+. or Cmd+.)
- Select Vidyano → Extract Index, QueryType and Context members
What It Does
The refactoring extracts the generated code into explicit files:
- Index class - Creates
{IndexClassName}.cs(e.g.,Customers_Overview.cs) - QueryType class - Creates
V{ClassName}.cs(e.g.,VCustomer.cs) - Context property - Adds the query property to your existing context class
The refactoring also:
- Replaces
[GenerateIndex]with[QueryType(typeof(V{ClassName}))] - Removes index-related attributes from the model class:
- Class-level:
[IndexCustomProperty],[IndexCustomVariable],[IndexCustomWhere] - Both class-level and property-level:
[IndexReferenceProperty] - Property-level:
[IndexReference](obsolete),[Search],[IgnoreForIndex]
- Class-level:
- Adds required using directives
- Places files in folders matching the generated namespace structure
Example
Before:
[GenerateIndex]
[IndexCustomProperty("FullName", typeof(string), "Name + \" \" + Surname")]
public partial class Customer
{
public string Id { get; set; }
[Search]
public string Name { get; set; }
public string Surname { get; set; }
}
After applying the refactoring:
// Customer.cs - Original file modified
[QueryType(typeof(VCustomer))]
public partial class Customer
{
public string Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
// Indexes/Customers_Overview.cs - New file created
public partial class Customers_Overview : AbstractIndexCreationTask<Customer, VCustomer>
{
public Customers_Overview()
{
Map = customers => from customer in customers
select new VCustomer
{
Id = customer.Id,
Name = customer.Name,
Name_Sort = customer.Name,
Surname = customer.Surname,
FullName = customer.Name + " " + customer.Surname
};
// ... index configuration
}
}
// VCustomer.cs - New file created
public partial class VCustomer : IId
{
public string Id { get; set; }
public string Name { get; set; }
public string Name_Sort { get; set; }
public string Surname { get; set; }
public string FullName { get; set; }
}
When to Use
Use this refactoring when you need to:
- Add custom index configuration not supported by attributes
- Modify the Map/Reduce logic
- Add custom index settings or analyzers
- Have full control over the generated code
Note: After extracting, the Index generator will no longer generate code for this entity. You are now responsible for maintaining the index, query type, and context property manually.
Backwards Compatibility
For backwards compatibility we created an Obsolete PersitentObjectTypes class (old version).
The only thing you need todo when upgrading to the new source generator is changing the using in de .csproj project file by adding the Generated.Obsolete namespace.
<Using Alias="Types" Include="Fleet.Service.Generated.Obsolete.PersistentObjectTypes.Fleet" />
This way the project wil run as before.
Note: If you add the new usings directly you can use de CodeFix to update to the correct code.
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.6.0 | 495 | 9/22/2026 |
| 3.5.0 | 30,032 | 2/25/2026 |
| 3.4.0 | 20,769 | 12/10/2025 |
| 3.3.0 | 805 | 12/3/2025 |
| 3.2.0 | 602 | 11/24/2025 |
| 3.1.0 | 697 | 11/18/2025 |
| 3.0.0 | 455 | 11/7/2025 |
| 2.23.0 | 1,877 | 9/19/2025 |
| 2.22.1 | 2,351 | 5/16/2025 |
| 2.22.0 | 461 | 5/13/2025 |
| 2.21.2 | 1,954 | 3/12/2025 |
| 2.21.1 | 285 | 3/12/2025 |
| 2.21.0 | 592 | 3/5/2025 |
| 2.19.1 | 1,503 | 1/23/2025 |
| 2.19.0 | 609 | 1/16/2025 |
| 2.18.0 | 279 | 1/16/2025 |
| 2.16.0 | 1,751 | 12/4/2024 |
| 2.15.0 | 1,171 | 11/19/2024 |