Materialization Interceptor - Change a property value
- Dominant language
- C#
- Stars
- 14.8k
- Forks
- 3.4k
- PR merge metrics
- PR metrics pending
Description
I developed some extensions for my custom provider for EFCore and I am trying to port this extensions when the user is using another provider to keep the same user experience.
For example, with this simplified model (please not consider too much that in the example below I am working directly with file system, like a client server app, the actual implementation works with distribuited systems but is much more complicated to explain) :
```
public class Person
{
public Guid ID {get;}
public string Name {get;}
public string ImagePath {get;}
}
```
When I configure the model I 'mark' the ImagePath with a fluent extension
`modelBuilder.Entity().Property(itm => itm.ImagePath).IsFilePath()
`
this adds an Annotation with a 'metadata class' (class FilesMetadataExtension :MetadataExtension{ } )
When I configure my provider
```
var contextOptions = new DbContextOptionsBuilder()
.UseMyProvider(xxx,optionsBuilder => optionsBuilder.UseFilesExtension(@"\\ImagesStore\")).Options;
var DB = new MyContext(contextOptions);
```
`UseFilesExtension(@"\\ImagesStore\")` registers some services through a class that implements IDbContextOptionsExtension.
The expected behavour is any image must be tranfered to the shared store images and data must be wrote and read accordingly.
When the user add a Person :
```
Person p = new Person();
p.PersonID = guid.NewGuid();
p.Name = "Name";
p.ImagePath = @"d:\PersonImage.png";
DB.Set().Add(p);
DB.SaveChanges();
```
In my implementation of Database class, I am able to check that the property ImagePath is marked with the annotation, so I can get in the registered services the one that generate a path in the shared configured path, example "\Person\{ID}", copy the image to the shared path and change the saved property value to "Person\{ID}" (instead of "d:\PersonImage.png").
When the user retrive the image path:
`var personImage = DB.Set().Select(itm => itm.ImagePath).Single();
`
In the class that inherit from QueryableMethodTranslatingExpressionVisitor Im am able to check that i am getting an entity property and that the property is marked with the annotation, so I can add to the query/expression the root path
`[...].Select(itm => {rootPath}+itm.ImagePath).Single();
`
so the query returns the full path of the image and the value is bindable to any viewer.
Similar story when the user retrive the full person :
`var person = DB.Set().Single();
`
when I build the shaper, I am able to check that i am getting an entity and there is the property marked with the annotation so also here the person entity will have the property as the bindable full path in the Image store.
Now, If I want to bring this behavour when the user uses another provider, for example SqlServer.
The model configuration, with the added Annotation, is the same.
When the user Save an entity:
```
Person p = new Person();
p.PersonID = guid.NewGuid();
p.Name = "Name";
p.ImagePath = @"d:\PersonImage.png";
DB.Set().Add(p);
DB.SaveChanges();
```
I can use a `SaveChangesInterceptor`, I can get the Saving entityEntries, check in `EntityType `the marked properties and do the same work I do in my provider.
When the user retrive the image path
`var personImage = DB.Set().Select(itm => itm.ImagePath).Single();
`
I can use an `IQueryExpressionInterceptor `so I can check that I am retriving an marked Entity property so I can return the expression
`DB.Set().Select(itm => {rootPath}+itm.ImagePath).Single();`
My problem is when the query retrive the full Entity
`var person = DB.Set().Single();`
the interceptor to use should be an `IMaterializationInterceptor`. In this interceptor I have the `MaterializationInterceptionData.EntityType` property that let me check if i am retriving an entity with a marked property, but I can only read the property value.
In both `InitializingInstance `and `InitializedInstance `there is no way to change the value of the property(in my case add the root of the shared image path to the saved value).
So, with the `SaveChangesInterceptor` and `IQueryExpressionInterceptor `I am able to manipulate the value the user passed and the value is retrived by the database but with `IMaterializationInterceptor `I can only read and be just a watcher of the data that from the database is returned to the user. All the example I found about `IMaterializationInterceptor`, change properties that are not actual mapped and always knowing the `EntityType `where change the not mapped value.
The perfect way may be if in `InitializingInstance`, when the values are still not assigned to entity instance,
`MaterializationInterceptionData `give two methods to change the value, like it give two methods to read the value, but now have I a way to complete this last step?
Contributor guide
Research direction
Start with IMaterializationInterceptor, especially InitializingInstance and InitializedInstance, and inspect the MaterializationInterceptionData APIs described in the issue. Trace how mapped property values are assigned during full-entity materialization. Done means establishing whether an interceptor can replace those values, or defining the API change needed to support it.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend, database
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100