|
| 1 | +> _A small weekend project with a higher future purpose_ |
| 2 | + |
| 3 | +This extension to the EntityFramework Core allows to decouple the read model from entities themselves. If you practice Domain-Driven Design, then you want to separate Domain Entity/Aggregate repositories from their querying mechanism. Query results must return Entity Projections/Views instead of Entities themselves (as they contain behavior), but building projection types by hand is tedious. |
| 4 | + |
| 5 | +## Example |
| 6 | + |
| 7 | +This is a Domain Entity that contains behaviors and implements a projection interface (as a contract) defined below. |
| 8 | + |
| 9 | +```csharp |
| 10 | +public class City : ICityProjection |
| 11 | +{ |
| 12 | + public string Name { get; private set; } |
| 13 | + |
| 14 | + public string State { get; private set; } |
| 15 | + |
| 16 | + public long Population { get; private set; } |
| 17 | + |
| 18 | + public int TimeZone { get; private set; } |
| 19 | + |
| 20 | + public void SwitchToSummerTime() |
| 21 | + { |
| 22 | + TimeZone += 1; |
| 23 | + } |
| 24 | +} |
| 25 | +``` |
| 26 | +A sample projection interface. An entity can have implement multiple projection interfaces. |
| 27 | +```csharp |
| 28 | +public interface ICityProjection |
| 29 | +{ |
| 30 | + string Name { get; } |
| 31 | + |
| 32 | + string State { get; } |
| 33 | + |
| 34 | + long Population { get; } |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +Just use the `HasProjections` extension method on the desired entity(-ies). That will automatically find all interfaces with get-only properties. |
| 39 | + |
| 40 | +```csharp |
| 41 | +using Dasync.EntityFrameworkCore.Extensions.Projections; |
| 42 | + |
| 43 | +public class SampleDbContext : DbContext |
| 44 | +{ |
| 45 | + protected override void OnModelCreating(ModelBuilder modelBuilder) |
| 46 | + { |
| 47 | + modelBuilder.Entity<City>(e => |
| 48 | + { |
| 49 | + // Declare that this entity has projection interfaces. |
| 50 | + e.HasProjections(); |
| 51 | + }); |
| 52 | + } |
| 53 | +} |
| 54 | +``` |
| 55 | + |
| 56 | +Time to query entities using their projection. |
| 57 | + |
| 58 | +```csharp |
| 59 | +var smallCities = await dbContext |
| 60 | + .Set<ICityProjection>() // Query directly on the projection interface |
| 61 | + .Where(c => c.Population < 1_000_000) |
| 62 | + .ToListAsync(); |
| 63 | +``` |
| 64 | + |
| 65 | +Note that the result set does not contain instances of the original `City` entity type. Instead, this extension library generates types at runtime that implement given projection interfaces. |
| 66 | + |
| 67 | +Then you can safely serialize your result set as it represents projections but not entities with behavior. Useful for API methods. |
| 68 | + |
| 69 | +```csharp |
| 70 | +var json = JsonConvert.SerializeObject(smallCities); |
| 71 | +``` |
| 72 | + |
| 73 | +Deserialization back can be done without involving entities as well (visit the '[samples](samples)' folder to see how `EntityProjectionJsonConverter` is implemented). |
| 74 | + |
| 75 | +```csharp |
| 76 | +var cityProjections = JsonConvert.DeserializeObject<List<ICityProjection>>( |
| 77 | + json, EntityProjectionJsonConverter.Instance); |
| 78 | +``` |
| 79 | + |
| 80 | +## How to start |
| 81 | + |
| 82 | +See the '[samples](samples)' folder, but in a nutshell: |
| 83 | + |
| 84 | +1. Add [Dasync.EntityFrameworkCore.Extensions.Projections NuGet Package](https://www.nuget.org/packages/Dasync.EntityFrameworkCore.Extensions.Projections) to your app |
| 85 | +1. Define projection interfaces and implement them on your entities |
| 86 | +1. Add `using Dasync.EntityFrameworkCore.Extensions.Projections;` to your code |
| 87 | +1. Use `HasProjections` extension method on entities while building your DbContext model |
| 88 | +1. Query with projection interfaces as shown above |
| 89 | +1. Serialize projections at the API layer |
| 90 | + |
0 commit comments