Fudie.Generator 1.0.12

dotnet add package Fudie.Generator --version 1.0.12
                    
NuGet\Install-Package Fudie.Generator -Version 1.0.12
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Fudie.Generator" Version="1.0.12">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Fudie.Generator" Version="1.0.12" />
                    
Directory.Packages.props
<PackageReference Include="Fudie.Generator">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Fudie.Generator --version 1.0.12
                    
#r "nuget: Fudie.Generator, 1.0.12"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Fudie.Generator@1.0.12
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Fudie.Generator&version=1.0.12
                    
Install as a Cake Addin
#tool nuget:?package=Fudie.Generator&version=1.0.12
                    
Install as a Cake Tool

Fudie.Generator

Source Generator for automatic repository implementations with Entity Framework Core.

Table of Contents


Base Interfaces

The generator recognizes the following infrastructure interfaces:

Interface Description Generated Method
IGet<T, ID> Read by ID Task<T> Get(ID id)
IAdd<T> Insert void Add(T entity)
IUpdate<T, ID> Update (inherits IGet) Task<T> Get(ID id) with tracking
IRemove<T, ID> Delete (inherits IGet) void Remove(T entity)

Basic Example

public interface ICustomerRepository : IGet<Customer, Guid>, IAdd<Customer>
{
}
// Generates: Get(Guid id) and Add(Customer entity)

Attributes

[GenerateRepository<TEntity>] / [GenerateRepository<TEntity, TId>]

Allows creating query-only repositories without exposing unsafe Get(id) methods.

[GenerateRepository<Ingredient>]
public interface IIngredientQueries
{
    Task<List<Ingredient>> FindByRestaurantId(Guid restaurantId);
}

[Include<TEntity>("path", ...)]

Configures eager loading with Include/ThenInclude.

[Include<Customer>("Orders.OrderItems.Product", "Address")]
public interface ICustomerRepository : IGet<Customer, Guid> { }

Generated LINQ:

query = query.Include(c => c.Orders)
    .ThenInclude(o => o.OrderItems)
    .ThenInclude(oi => oi.Product);
query = query.Include(c => c.Address);

[Tracking] / [Tracking(bool)]

Enables change tracking. Applicable to interfaces and methods.

[AsNoTracking]

Disables change tracking. Applicable to interfaces and methods.

[AsSplitQuery]

Uses split queries to avoid cartesian explosion.

[IgnoreQueryFilters]

Ignores global query filters (e.g. soft delete).


Query Method Conventions

The generator parses method names and generates LINQ code automatically.

Query Prefixes

Prefix Return Type Final LINQ
FindBy Task<List<T>> .ToListAsync()
FindFirstBy Task<T?> .FirstOrDefaultAsync()
FindTop{N}By Task<List<T>> .Take(N).ToListAsync()
CountBy Task<int> .CountAsync()
ExistsBy Task<bool> .AnyAsync()
DeleteBy Task<int> .ExecuteDeleteAsync()

Operators

Operator Method Usage Generated LINQ
(none) / Equal FindByName x.Name == name
NotEqual FindByStatusNotEqual x.Status != status
LessThan FindByPriceLessThan x.Price < price
LessThanOrEqual FindByAgeLessThanOrEqual x.Age <= age
GreaterThan FindByScoreGreaterThan x.Score > score
GreaterThanOrEqual FindByDateGreaterThanOrEqual x.Date >= date
Between FindByPriceBetween x.Price >= min && x.Price <= max
In FindByStatusIn statuses.Contains(x.Status)
NotIn FindByTypeNotIn !types.Contains(x.Type)
StartsWith FindByNameStartsWith x.Name.StartsWith(prefix)
EndsWith FindByEmailEndsWith x.Email.EndsWith(suffix)
Contains FindByDescriptionContains x.Description.Contains(text)
Like FindByNameLike EF.Functions.Like(x.Name, pattern)
IsNull FindByDeletedAtIsNull x.DeletedAt == null
IsNotNull FindByManagerIsNotNull x.Manager != null
True FindByIsActiveTrue x.IsActive == true
False FindByIsDeletedFalse x.IsDeleted == false

Modifiers

Logical Connectors
Modifier Usage LINQ
And FindByNameAndAge x.Name == name && x.Age == age
Or FindByNameOrEmail x.Name == name \|\| x.Email == email
Case Insensitive
Modifier Usage LINQ
IgnoreCase FindByNameIgnoreCase x.Name.ToLower() == name.ToLower()
Ordering
Modifier Usage LINQ
OrderBy{Prop} FindByStatusOrderByName .OrderBy(x => x.Name)
OrderBy{Prop}Asc FindByStatusOrderByNameAsc .OrderBy(x => x.Name)
OrderBy{Prop}Desc FindByStatusOrderByNameDesc .OrderByDescending(x => x.Name)

Full Examples

Task<List<Customer>> FindByName(string name);

LINQ:

return await _query.Query<Customer>()
    .Where(x => x.Name == name)
    .ToListAsync();

2. First Result with Two Conditions
Task<Customer?> FindFirstByIdAndRestaurantId(Guid id, Guid restaurantId);

LINQ:

return await _query.Query<Customer>()
    .Where(x => x.Id == id && x.RestaurantId == restaurantId)
    .FirstOrDefaultAsync();

3. Search with OR
Task<List<User>> FindByEmailOrPhone(string email, string phone);

LINQ:

return await _query.Query<User>()
    .Where(x => x.Email == email || x.Phone == phone)
    .ToListAsync();

4. Value Range
Task<List<Product>> FindByPriceBetween(decimal min, decimal max);

LINQ:

return await _query.Query<Product>()
    .Where(x => x.Price >= min && x.Price <= max)
    .ToListAsync();

5. Values in List
Task<List<Order>> FindByStatusIn(List<OrderStatus> statuses);

LINQ:

return await _query.Query<Order>()
    .Where(x => statuses.Contains(x.Status))
    .ToListAsync();

6. Case Insensitive
Task<List<Customer>> FindByNameIgnoreCase(string name);

LINQ:

return await _query.Query<Customer>()
    .Where(x => x.Name.ToLower() == name.ToLower())
    .ToListAsync();

7. With Ordering
Task<List<Product>> FindByCategoryOrderByPriceDesc(string category);

LINQ:

return await _query.Query<Product>()
    .Where(x => x.Category == category)
    .OrderByDescending(x => x.Price)
    .ToListAsync();

8. Top N Results
Task<List<Product>> FindTop5ByIsActiveTrue();

LINQ:

return await _query.Query<Product>()
    .Where(x => x.IsActive == true)
    .Take(5)
    .ToListAsync();

9. Count with Condition
Task<int> CountByRestaurantIdAndIsActiveTrue(Guid restaurantId);

LINQ:

return await _query.Query<Ingredient>()
    .Where(x => x.RestaurantId == restaurantId && x.IsActive == true)
    .CountAsync();

10. Check Existence
Task<bool> ExistsByEmailIgnoreCase(string email);

LINQ:

return await _query.Query<User>()
    .Where(x => x.Email.ToLower() == email.ToLower())
    .AnyAsync();

11. Bulk Delete
Task<int> DeleteByIsDeletedTrueAndDeletedAtLessThan(DateTime cutoff);

LINQ:

return await _query.Query<AuditLog>()
    .Where(x => x.IsDeleted == true && x.DeletedAt < cutoff)
    .ExecuteDeleteAsync();

12. String Operations
Task<List<Customer>> FindByNameStartsWithAndEmailEndsWith(string prefix, string domain);

LINQ:

return await _query.Query<Customer>()
    .Where(x => x.Name.StartsWith(prefix) && x.Email.EndsWith(domain))
    .ToListAsync();

13. Null Checks
Task<List<Employee>> FindByManagerIsNullAndDepartmentIsNotNull();

LINQ:

return await _query.Query<Employee>()
    .Where(x => x.Manager == null && x.Department != null)
    .ToListAsync();

Tracking

Tracking can be configured at the interface or method level. Method-level attributes take priority.

Priority Rules

  1. Method attribute → Always wins
  2. Interface attribute → Default for methods without attribute
  3. No attributes → Default is AsNoTracking (no tracking)

Generated Dependencies

Configuration Injected Dependency Query Source
No tracking IQuery _query.Query<T>()
With tracking IEntityLookup _entityLookup.Set<T>()
Mixed Both Per method

Example: Mixed Tracking

[GenerateRepository<Ingredient>]
public interface IIngredientRepository
{
    // No tracking - read only
    [AsNoTracking]
    Task<List<Ingredient>> FindByName(string name);

    // With tracking - for later modification
    [Tracking]
    Task<Ingredient?> FindFirstByIdAndRestaurantId(Guid id, Guid restaurantId);
}

Generated Code:

public class IngredientRepository : IIngredientRepository
{
    private readonly IEntityLookup _entityLookup;  // For tracked methods
    private readonly IQuery _query;                 // For untracked methods

    public IngredientRepository(IEntityLookup entityLookup, IQuery query)
    {
        _entityLookup = entityLookup;
        _query = query;
    }

    public async Task<List<Ingredient>> FindByName(string name)
    {
        return await _query.Query<Ingredient>()  // No tracking
            .Where(x => x.Name == name)
            .ToListAsync();
    }

    public async Task<Ingredient?> FindFirstByIdAndRestaurantId(Guid id, Guid restaurantId)
    {
        return await _entityLookup.Set<Ingredient>()  // With tracking
            .Where(x => x.Id == id && x.RestaurantId == restaurantId)
            .FirstOrDefaultAsync();
    }
}

Example: Interface-Level Tracking

[GenerateRepository<Ingredient>]
[AsNoTracking]  // Default for all methods
public interface IIngredientReadRepository
{
    Task<List<Ingredient>> FindByRestaurantId(Guid restaurantId);

    [Tracking]  // Override: this method DOES use tracking
    Task<Ingredient?> FindFirstByIdAndRestaurantId(Guid id, Guid restaurantId);
}

Multi-Tenant Use Cases

For security in multi-tenant applications, use [GenerateRepository] instead of IGet:

// BAD - Get(id) does not filter by tenant
public interface IIngredientRepository : IGet<Ingredient, Guid> { }

// GOOD - Only exposes methods that require tenant
[GenerateRepository<Ingredient>]
public interface IIngredientRepository
{
    Task<Ingredient?> FindFirstByIdAndRestaurantId(Guid id, Guid restaurantId);
    Task<List<Ingredient>> FindByRestaurantId(Guid restaurantId);
}

Summary: Method → LINQ

Method Generated LINQ
FindByX(v) .Where(x => x.X == v).ToListAsync()
FindFirstByX(v) .Where(x => x.X == v).FirstOrDefaultAsync()
FindTop10ByX(v) .Where(x => x.X == v).Take(10).ToListAsync()
CountByX(v) .Where(x => x.X == v).CountAsync()
ExistsByX(v) .Where(x => x.X == v).AnyAsync()
DeleteByX(v) .Where(x => x.X == v).ExecuteDeleteAsync()
FindByXAndY(x,y) .Where(x => x.X == x && x.Y == y)...
FindByXOrY(x,y) .Where(x => x.X == x \|\| x.Y == y)...
FindByXOrderByY(x) .Where(...).OrderBy(x => x.Y)...
FindByXOrderByYDesc(x) .Where(...).OrderByDescending(x => x.Y)...
FindByXIgnoreCase(x) .Where(x => x.X.ToLower() == x.ToLower())...
FindByXBetween(min,max) .Where(x => x.X >= min && x.X <= max)...
FindByXIn(list) .Where(x => list.Contains(x.X))...
FindByXIsNull() .Where(x => x.X == null)...
FindByXIsNotNull() .Where(x => x.X != null)...
FindByXTrue() .Where(x => x.X == true)...
FindByXFalse() .Where(x => x.X == false)...
FindByXStartsWith(p) .Where(x => x.X.StartsWith(p))...
FindByXEndsWith(s) .Where(x => x.X.EndsWith(s))...
FindByXContains(t) .Where(x => x.X.Contains(t))...
FindByXLike(p) .Where(x => EF.Functions.Like(x.X, p))...
FindByXGreaterThan(v) .Where(x => x.X > v)...
FindByXLessThanOrEqual(v) .Where(x => x.X <= v)...
There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .NETStandard 2.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Fudie.Generator:

Package Downloads
Fudie

Fudie framework — all packages in one reference. Install this to get the full framework.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.12 133 3/8/2026
1.0.11 124 3/7/2026
1.0.10 120 3/5/2026
1.0.9 126 3/5/2026
1.0.8 118 3/5/2026
1.0.7 130 3/4/2026
1.0.6 121 3/4/2026
1.0.5 120 3/4/2026
1.0.4 128 3/3/2026
1.0.3 127 3/3/2026
1.0.2 127 3/3/2026
1.0.1 123 3/2/2026
1.0.0 124 3/2/2026