Open GLuca74 opened 3 months ago
@GLuca74, currently, for SQL Server provider, only use data reader to get the corresponding property value. So that you can't change property value.
The workaround is you can use reflection to set property value again in IMaterializationInterceptor.InitializedInstance
.
You can create an IConventionSetPlugin
for retrieving all entity properties and discover property with IsFilePath()
annotation. I assume the name of annotation is filePath
for IsFilePath()
.
internal class FilePathAnnotationPlugin : IConventionSetPlugin
{
public ConventionSet ModifyConventions(ConventionSet conventionSet)
{
conventionSet.Add(new AddFilePathRuntimeAnnotationForEntity());
return conventionSet;
}
public class AddFilePathRuntimeAnnotationForEntity : IModelFinalizedConvention
{
public IModel ProcessModelFinalized(IModel model)
{
foreach (var entity in model.GetEntityTypes())
{
PropertyInfo? filePathProp = entity.GetProperties().FirstOrDefault(x => x.FindAnnotation("filePath") != null)?.PropertyInfo;
if (filePathProp !=null)
{
entity.AddRuntimeAnnotation("hasFilePath", filePathProp);
}
}
return model;
}
}
}
In your IDbContextOptionsExtension
class, you can register FilePathAnnotationPlugin
in dbcontext DI.
public void ApplyServices(IServiceCollection services)
{
...
services.AddSingleton<IConventionSetPlugin, FilePathAnnotationPlugin>();
}
In IMaterializationInterceptor.InitializedInstance
method, you can change property value as below code:
public object InitializedInstance(MaterializationInterceptionData materializationData, object entity)
{
if (materializationData.QueryTrackingBehavior is null) return entity;
var filePathProp = (PropertyInfo?)materializationData.EntityType.FindRuntimeAnnotation("hasFilePath")?.Value;
if (filePathProp is not null)
{
var dbValue = materializationData.GetPropertyValue(filePathProp.Name);
filePathProp.SetValue(entity, $"{rootPath}"+ dbValue);
}
return entity;
}
Hello @Li7ye , thanks. Now i am deep focused on another task, when I will complete the task I am working on, I will try your solution
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) :
When I configure the model I 'mark' the ImagePath with a fluent extension
modelBuilder.Entity<Person>().Property(itm => itm.ImagePath).IsFilePath()
this adds an Annotation with a 'metadata class' (class FilesMetadataExtension :MetadataExtension{ } ) When I configure my providerUseFilesExtension(@"\\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 :
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<Person>().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<Person>().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:
I can use a
SaveChangesInterceptor
, I can get the Saving entityEntries, check inEntityType
the marked properties and do the same work I do in my provider.When the user retrive the image path
var personImage = DB.Set<Person>().Select(itm => itm.ImagePath).Single();
I can use anIQueryExpressionInterceptor
so I can check that I am retriving an marked Entity property so I can return the expressionDB.Set<Person>().Select(itm => {rootPath}+itm.ImagePath).Single();
My problem is when the query retrive the full Entity
var person = DB.Set<Person>().Single();
the interceptor to use should be an
IMaterializationInterceptor
. In this interceptor I have theMaterializationInterceptionData.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 bothInitializingInstance
andInitializedInstance
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
andIQueryExpressionInterceptor
I am able to manipulate the value the user passed and the value is retrived by the database but withIMaterializationInterceptor
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 aboutIMaterializationInterceptor
, change properties that are not actual mapped and always knowing theEntityType
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?