Skip to content

Commit f9b3920

Browse files
committed
version 0.2, with auto properties.
1 parent 8c2275f commit f9b3920

15 files changed

Lines changed: 813 additions & 8 deletions

File tree

README.md

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,111 @@
11
# SmartSelector
2-
Autoc selection expression generator to map object to object and apply to IQueryable.
2+
3+
Gerador/Source Generator para criar automaticamente projeções (`Expression<Func<TFrom, TDto>>`), métodos auxiliares e propriedades em DTOs, reduzindo drasticamente boilerplate em consultas LINQ / EF Core.
4+
5+
## Principais Recursos
6+
- `[AutoSelect<TFrom>]`: gera expressão de seleção, método `From`, extensões `Select{Dto}` / `To{Dto}`.
7+
- `[AutoProperties]` ou `[AutoProperties<TFrom>]`: gera propriedades simples automaticamente (primitivos, string, bool, DateTime, enum, struct, coleções simples `IEnumerable<T>` desses tipos).
8+
- Flattening por convenção: nomes concatenados em PascalCase resolvem cadeias aninhadas (ex.: `CustomerAddressCountryRegionName` ? `a.Customer.Address.Country.Region.Name`).
9+
- Exclusão de propriedades: `Exclude = [ nameof(Entity.Prop) ]`.
10+
- Diagnósticos de compilação para uso incorreto, tipos incompatíveis e conflitos.
11+
12+
## Instalação
13+
```xml
14+
<ItemGroup>
15+
<PackageReference Include="RoyalCode.SmartSelector" Version="x.y.z" />
16+
<PackageReference Include="RoyalCode.SmartSelector.Generators" Version="x.y.z" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
17+
</ItemGroup>
18+
```
19+
20+
## Exemplo 1 – Projeção Simples
21+
```csharp
22+
[AutoSelect<User>, AutoProperties]
23+
public partial class UserDetails { }
24+
25+
// Uso
26+
var list = db.Users.SelectUserDetails().ToList();
27+
var dto = UserDetails.From(user);
28+
var expr = UserDetails.SelectUserExpression; // reutilizável / componível
29+
```
30+
Código gerado (essencial):
31+
```csharp
32+
public static Expression<Func<User, UserDetails>> SelectUserExpression => u => new UserDetails { Id = u.Id, Name = u.Name };
33+
public static UserDetails From(User u) => (selectUserFunc ??= SelectUserExpression.Compile())(u);
34+
```
35+
36+
## Exemplo 2 – Objeto Aninhado + Exclusão
37+
```csharp
38+
[AutoSelect<Book>, AutoProperties(Exclude = [ nameof(Book.Sku) ])]
39+
public partial class BookDetails
40+
{
41+
public ShelfDetails Shelf { get; set; }
42+
}
43+
44+
[AutoProperties<Shelf>]
45+
public partial class ShelfDetails { }
46+
```
47+
Trecho gerado:
48+
```csharp
49+
Shelf = new ShelfDetails { Id = a.Shelf.Id, Location = a.Shelf.Location },
50+
Price = a.Price,
51+
// Sku excluído
52+
```
53+
54+
## Exemplo 3 – Flattening Profundo
55+
```csharp
56+
public class Order { public Customer Customer { get; set; } }
57+
// Customer -> Address -> Country -> Region
58+
[AutoSelect<Order>]
59+
public partial class OrderDetails
60+
{
61+
public string CustomerAddressCountryRegionName { get; set; }
62+
}
63+
```
64+
Trecho da expressão:
65+
```csharp
66+
CustomerAddressCountryRegionName = a.Customer.Address.Country.Region.Name
67+
```
68+
69+
## Regras de Flattening
70+
- Nome da propriedade = concatenação PascalCase dos segmentos do caminho.
71+
- Sem necessidade de atributos extras.
72+
73+
## Tipos Suportados em AutoProperties
74+
- Primitivos numéricos, `bool`, `string`, `char`, `DateTime` / nullable simples
75+
- `enum`, `struct`
76+
- `IEnumerable<T>` onde `T` é suportado acima / enum / struct
77+
78+
## Exclusões
79+
```csharp
80+
[AutoProperties<Product>(Exclude = [ nameof(Product.InternalCode), nameof(Product.Secret) ])]
81+
```
82+
83+
## Diagnósticos Principais
84+
- Tipos inválidos ou classe não `partial` (`RCSS000`).
85+
- Propriedade não encontrada (`RCSS001`).
86+
- Tipos incompatíveis (`RCSS002`).
87+
- Uso incorreto de atributos (`RCSS003`–`RCSS005`).
88+
89+
## Limitações Resumidas
90+
- Sem renome/alias explícito ainda (`MapFrom`).
91+
- Sem transformações de tipo (formatters / custom converters).
92+
- Desambiguação de flattening limitada em colisões de prefixo.
93+
94+
## Boas Práticas
95+
- Use `nameof` em `Exclude`.
96+
- Prefira consumir a expressão gerada para reutilização e composição LINQ.
97+
- Para caminhos muito longos, avalie DTOs aninhados por clareza.
98+
99+
## FAQ Rápido
100+
| Pergunta | Resposta |
101+
|----------|----------|
102+
| Preciso configurar algo no runtime? | Não, pura geração de código. |
103+
| Funciona com EF Core? | Sim, a expressão é traduzível. |
104+
| Posso só gerar propriedades? | Sim: `[AutoProperties<TFrom>]`. |
105+
| Flattening precisa de atributo? | Não, é por nome. |
106+
107+
## Mais Informações
108+
Documentação detalhada: ver `docs.md` no repositório.
109+
110+
---
111+
Happy coding!

src/AnalyzerReleases.Shipped.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ RCSS000 | Usage | Error | RCSS000_Invalid_model_type, [Documentation](htt
88
RCSS001 | Usage | Error | RCSS001_It_is_not_possible_to_determine_a_corresponding_property_for_the_other_type, [Documentation](https://google.com)
99
RCSS002 | Usage | Error | RCSS002_Incompatible_property_types, [Documentation](https://google.com)
1010
RCSS003 | Usage | Error | RCSS003_Invalid_auto_property, [Documentation](https://google.com)
11+
RCSS004 | Usage | Error | RCSS004_Conflict_auto_property_attribute, [Documentation](https://google.com)
12+
RCSS005 | Usage | Error | RCSS005_Invalid_auto_property_from, [Documentation](https://google.com)

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<ExtSrcGenVer>0.1.7</ExtSrcGenVer>
1010
</PropertyGroup>
1111
<PropertyGroup>
12-
<Ver>0.1.0</Ver>
12+
<Ver>0.2.0</Ver>
1313
<Prev></Prev>
1414
</PropertyGroup>
1515
</Project>

src/RoyalCode.SmartSelector.Demo/Details/Library/BookDetails.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace RoyalCode.SmartSelector.Demo.Details.Library;
55
[AutoSelect<Book>, AutoProperties(Exclude = [ nameof(Book.Sku) ])]
66
public partial class BookDetails
77
{
8-
public Guid Id { get; set; }
8+
public ShelfDetails Shelf { get; set; }
99
}
1010

11+
[AutoProperties<Shelf>]
12+
public partial class ShelfDetails { }

src/RoyalCode.SmartSelector.Demo/Generated/RoyalCode.SmartSelector.Generators/RoyalCode.SmartSelector.Generators.IncrementalGenerator/BookDetails.AutoProperties.g.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ namespace RoyalCode.SmartSelector.Demo.Details.Library;
33

44
public partial class BookDetails
55
{
6+
public Guid Id { get; set; }
7+
68
public string Title { get; set; }
79

810
public string Author { get; set; }

src/RoyalCode.SmartSelector.Demo/Generated/RoyalCode.SmartSelector.Generators/RoyalCode.SmartSelector.Generators.IncrementalGenerator/BookDetails.g.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ public partial class BookDetails
99

1010
public static Expression<Func<Book, BookDetails>> SelectBookExpression { get; } = a => new BookDetails
1111
{
12+
Shelf = new ShelfDetails
13+
{
14+
Id = a.Shelf.Id,
15+
Location = a.Shelf.Location
16+
},
1217
Id = a.Id,
1318
Title = a.Title,
1419
Author = a.Author,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+

2+
namespace RoyalCode.SmartSelector.Demo.Details.Library;
3+
4+
public partial class ShelfDetails
5+
{
6+
public Guid Id { get; set; }
7+
8+
public string Location { get; set; }
9+
}

src/RoyalCode.SmartSelector.Demo/Infra/AppDbContext.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Microsoft.EntityFrameworkCore;
33
using RoyalCode.SmartSelector.Demo.Entities;
44
using RoyalCode.SmartSelector.Demo.Entities.Blogs;
5+
using RoyalCode.SmartSelector.Demo.Entities.Library; // added for Book & Shelf
56

67
namespace RoyalCode.SmartSelector.Demo.Infra;
78

@@ -25,6 +26,10 @@ public class AppDbContext : DbContext
2526

2627
public DbSet<Author> Authors { get; set; } = default!;
2728

29+
public DbSet<Book> Books { get; set; } = default!;
30+
31+
public DbSet<Shelf> Shelves { get; set; } = default!;
32+
2833
protected override void OnModelCreating(ModelBuilder modelBuilder)
2934
{
3035
modelBuilder.Entity<Product>().ToTable("Products");
@@ -36,6 +41,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
3641
modelBuilder.Entity<Post>().ToTable("Posts");
3742
modelBuilder.Entity<Comment>().ToTable("Comments");
3843
modelBuilder.Entity<Author>().ToTable("Authors");
44+
modelBuilder.Entity<Book>().ToTable("Books");
45+
modelBuilder.Entity<Shelf>().ToTable("Shelves");
3946
}
4047

4148
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
namespace RoyalCode.SmartSelector.Demo.Tests;
2+
3+
using RoyalCode.SmartSelector.Demo.Details.Library;
4+
using RoyalCode.SmartSelector.Demo.Entities.Library;
5+
using RoyalCode.SmartSelector.Demo.Infra;
6+
7+
public class BookDemoTests
8+
{
9+
[Fact]
10+
public void GetSelectExpression()
11+
{
12+
// act
13+
var expression = BookDetails.SelectBookExpression;
14+
15+
// assert
16+
Assert.NotNull(expression);
17+
}
18+
19+
[Fact]
20+
public void Create_BookDetails_From_Book()
21+
{
22+
// arrange
23+
var shelf = new Shelf { Location = "A-01" };
24+
var book = new Book
25+
{
26+
Title = "DDD",
27+
Author = "Eric Evans",
28+
PublishedDate = new DateTime(2003, 8, 30),
29+
ISBN = "1234567890",
30+
Price = 99.90m,
31+
InStock = true,
32+
Sku = "SKU-123",
33+
Shelf = shelf
34+
};
35+
36+
// act
37+
var details = BookDetails.From(book);
38+
39+
// assert
40+
Assert.NotNull(details);
41+
Assert.Equal(book.Id, details.Id);
42+
Assert.Equal(book.Title, details.Title);
43+
Assert.Equal(book.Author, details.Author);
44+
Assert.Equal(book.PublishedDate, details.PublishedDate);
45+
Assert.Equal(book.ISBN, details.ISBN);
46+
Assert.Equal(book.Price, details.Price);
47+
Assert.Equal(book.InStock, details.InStock);
48+
// Sku excluído (não foi gerado)
49+
// Propriedade Shelf (aninhada)
50+
Assert.NotNull(details.Shelf);
51+
Assert.Equal(book.Shelf.Id, details.Shelf.Id);
52+
Assert.Equal(book.Shelf.Location, details.Shelf.Location);
53+
}
54+
55+
[Fact]
56+
public void Query_BookDetails_From_Database()
57+
{
58+
// arrange
59+
var db = new AppDbContext();
60+
db.Database.EnsureCreated();
61+
var shelf = new Shelf { Location = "B-10" };
62+
db.Shelves.Add(shelf);
63+
db.Books.Add(new Book { Title = "Book 1", Author = "Author 1", PublishedDate = DateTime.Today, ISBN = "111", Price = 10m, InStock = true, Shelf = shelf });
64+
db.Books.Add(new Book { Title = "Book 2", Author = "Author 2", PublishedDate = DateTime.Today, ISBN = "222", Price = 20m, InStock = false, Shelf = shelf });
65+
db.SaveChanges();
66+
db.ChangeTracker.Clear();
67+
68+
// act
69+
var details = db.Books
70+
.Select(BookDetails.SelectBookExpression)
71+
.ToList();
72+
73+
// assert
74+
Assert.Equal(2, details.Count);
75+
Assert.Contains(details, d => d.Title == "Book 1" && d.Shelf.Location == "B-10");
76+
Assert.Contains(details, d => d.Title == "Book 2" && d.Shelf.Location == "B-10");
77+
}
78+
79+
[Fact]
80+
public void Create_ShelfDetails_From_Shelf_Via_BookDetails_Nested()
81+
{
82+
// arrange
83+
var shelf = new Shelf { Location = "C-15" };
84+
var book = new Book { Title = "Nested", Author = "Author", PublishedDate = DateTime.Today, ISBN = "333", Price = 30m, InStock = true, Shelf = shelf };
85+
86+
// act
87+
var details = BookDetails.From(book);
88+
89+
// assert nested shelf mapping
90+
Assert.NotNull(details.Shelf);
91+
Assert.Equal(shelf.Id, details.Shelf.Id);
92+
Assert.Equal(shelf.Location, details.Shelf.Location);
93+
}
94+
}

src/RoyalCode.SmartSelector.Generators/AnalyzerDiagnostics.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,19 @@ internal static class AnalyzerDiagnostics
3838
defaultSeverity: DiagnosticSeverity.Error,
3939
isEnabledByDefault: true);
4040

41+
public static readonly DiagnosticDescriptor ConflictingAutoPropertiesAttributes = new(
42+
id: "RCSS004",
43+
title: "Conflicting AutoProperties attributes",
44+
messageFormat: "The class {0} cannot use both AutoPropertiesAttribute and AutoPropertiesAttribute<TFrom>",
45+
category: Category,
46+
defaultSeverity: DiagnosticSeverity.Error,
47+
isEnabledByDefault: true);
48+
49+
public static readonly DiagnosticDescriptor InvalidAutoPropertiesTypeArgument = new(
50+
id: "RCSS005",
51+
title: "Invalid AutoProperties type argument",
52+
messageFormat: "Invalid type argument '{0}' for AutoPropertiesAttribute<TFrom>; it must be a valid class or struct type",
53+
category: Category,
54+
defaultSeverity: DiagnosticSeverity.Error,
55+
isEnabledByDefault: true);
4156
}

0 commit comments

Comments
 (0)