Skip to content

Commit a583575

Browse files
author
Alexander Turtsevich
committed
update README.md
1 parent dbae89f commit a583575

File tree

2 files changed

+93
-18
lines changed

2 files changed

+93
-18
lines changed

ProjectionTools/ProjectionTools.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<Author>Alexander Turtsevich</Author>
2525
<Authors>Alexander Turtsevich</Authors>
2626
<Description>Primitives for building reusable LINQ projections and specifications/predicates</Description>
27-
<Copyright>Alexander Turtsevich 2023</Copyright>
27+
<Copyright>Copyright © Alexander Turtsevich 2023</Copyright>
2828
<PackageIcon>logo.png</PackageIcon>
2929
<PackageProjectUrl>https://github.com/kemsky/projection-tools</PackageProjectUrl>
3030
<RepositoryUrl>https://github.com/kemsky/projection-tools</RepositoryUrl>

README.md

Lines changed: 92 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22

33
# Projection Tools
44

5-
This package provides two primitives `Projection<TSource, TResult>` and `Specification<TSource>` for building reusable LINQ projections and predicates.
5+
This package provides primitives for building reusable LINQ projections and specifications.
66

77
Package is available on [Nuget](https://www.nuget.org/packages/ProjectionTools/).
88

9-
Install using dotnet cli:
9+
Install using dotnet CLI:
1010
```commandline
1111
dotnet add package ProjectionTools
1212
```
13-
Install using package-manager console:
13+
Install using Package-Manager console:
1414
```commandline
1515
PM> Install-Package ProjectionTools
1616
```
@@ -21,24 +21,67 @@ My initial goal was to replace packages like AutoMapper and similar.
2121

2222
The common drawbacks of using mappers:
2323

24-
- Code "black hole" and dirty magic: IDE can not show code usages, mappings are resolved in runtime;
25-
- Complex API: API is complex yet limited in many cases;
26-
- Maintenance costs: authors often change APIs without considering other options;
24+
- IDE can not show code usages, mappings are resolved in runtime (sometimes source generators are used);
25+
- API is complex yet limited in many cases;
26+
- Maintenance costs: authors frequently change APIs without considering other options;
2727
- Do not properly separate instance API (mapping object instances) and expression API (mapping through LINQ projections) which leads to bugs in runtime;
2828
- Bugs: despite all the claims you can not be sure in anything unless you manually test mapping of each field and each scenario (instance/LINQ);
29-
- Poor testing experience;
30-
- Compatibility with LINQ providers: AutoMapper has broken compatibility with EF6 for no reason at all;
29+
- Poor testing experience: sometimes you have to create your own "tools" specifically for testing mappings;
30+
- Compatibility with LINQ providers: e.g. AutoMapper has broken compatibility with EF6 for no reason at all;
3131

32-
In the most cases mapping splits into two independent stages:
32+
In the most cases mapping splits into two independent scenarios:
3333

34-
- Fetch DTOs directly from DB using automatic projections and pass result to client;
35-
- Map incoming DTOs to entities to apply changes from client and then save modified entities to DB;
34+
1. Fetch DTOs from DB using automatic projections;
35+
2. DTOs to entities and then save modified entities to DB;
3636

37-
In reality mapping from DTO to entity is rarely a good idea: there are validations, access rights, business logic. It means that you end up using custom code in each case.
37+
In reality direct mapping from DTO to entity is rarely viable: there are validations, access rights, business logic. It means that you end up writing custom code for each save operation.
3838

39-
`Projection<TSource, TResult>` - provides option to define mapping from entity to DTO.
39+
In case we want to support only 1st scenario there is no need to deal with complex mapper configurations.
4040

41-
Quick example, controller should return only active users and users should have only active departments:
41+
`Projection<TSource, TResult>` - provides an option to define reusable mappings.
42+
43+
You can create projection using mapping expression:
44+
45+
```csharp
46+
Projection<DepartmentEntity, DepartmentDto> DepartmentDtoProjection = new (
47+
x => new DepartmentDto
48+
{
49+
Name = x.Name
50+
}
51+
);
52+
```
53+
or delegate:
54+
55+
```csharp
56+
Projection<DepartmentEntity, DepartmentDto> DepartmentDtoProjection = new (
57+
default,
58+
x => new DepartmentDto
59+
{
60+
Name = x.Name
61+
}
62+
);
63+
```
64+
65+
or both (e.g. when DB only features are used like DBFunctions, delegate should match DB behavior):
66+
67+
```csharp
68+
Projection<DepartmentEntity, DepartmentDto> DepartmentDtoProjection = new (
69+
x => new DepartmentDto
70+
{
71+
Name = x.Name
72+
},
73+
x => new DepartmentDto
74+
{
75+
Name = x.Name
76+
}
77+
);
78+
```
79+
80+
You can use projections in other projections.
81+
82+
Thanks to `DelegateDecompiler` package and built-in ability to compile expression trees all of the options above will work but with different performance implications.
83+
84+
Full example, controller should return only active users and users should have only active departments:
4285

4386
```csharp
4487
public class UserEntity
@@ -129,17 +172,49 @@ public class UserController : Controller
129172

130173
## Specifications (reusable predicates)
131174

132-
Projection works but we have a problem: we do not reuse `Where(x => x.Active)` checks. There is one predicate in `UserController.GetUser` method and another in `UserDtoProjection`.
175+
Projections work but we have a problem: we do not reuse `Where(x => x.Active)` checks. There is one predicate in `UserController.GetUser` method and another in `UserDtoProjection`.
133176

134-
This predicate can be more complex, often it is a combination of different predicates depending on business logic.
177+
This predicates can be more complex, often a combination of different predicates depending on business logic.
135178

136179
There is a well-known specification pattern and there are many existing .NET implementations but they all share similar problems:
137180

138181
- Verbose syntax for declaration and usage;
139182
- Many intrusive extensions methods that pollute project code;
140183
- Can only be used in certain contexts;
141184

142-
This is how we can use `Specification<TSource>` to solve these problems:
185+
`Specification<TSource>` can solve these problems.
186+
187+
You can create specification using expression:
188+
```csharp
189+
Specification<DepartmentEntity> ActiveDepartment = new (
190+
x => x.Active
191+
);
192+
```
193+
or delegate:
194+
```csharp
195+
Specification<DepartmentEntity> ActiveDepartment = new (
196+
default,
197+
x => x.Active
198+
);
199+
```
200+
or both (e.g. when DB has case-insensitive collation, delegate should match DB behavior):
201+
```csharp
202+
Specification<DepartmentEntity> ActiveDepartment = new (
203+
x => x.Active,
204+
x => x.Active
205+
);
206+
```
207+
208+
You can also combine specifications (using `&&`, `||`,`!`):
209+
```csharp
210+
Specification<DepartmentEntity> CustomerServiceDepartment = new (
211+
x => x.Name == "Customer Service"
212+
);
213+
214+
Specification<DepartmentEntity> ActiveCustomerServiceDepartment = ActiveDepartment && CustomerServiceDepartment;
215+
```
216+
217+
Full example:
143218

144219
```csharp
145220
public class UserEntity

0 commit comments

Comments
 (0)