-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Interception for object materialization (a.k.a. "ObjectMaterialized") #15911
Comments
@ajcvickers thanks for breaking this down into even smaller work items. Wasn't sure what granularity @divega was looking for. Anyway, could this be promoted to 3.0.0 milestone just like #15910 ? The reason being we need hooks at both paths to and from the database - to preserve symmetry and avoid data corruption during data transformations. Having both Thanks |
@SidShetye Can you give some details on how you intend to use |
@ajcvickers : Sure. We basically use integration points in any ORM to enforce our data security pipeline. Specifically, for data about to be written to the database we'd use something like So on the reverse path, we again need our security pipeline to kick in that results in plaintext data being presented to the EF application. |
@ajcvickers - will this be marked for the 3.0 release? Did my response address your question on usage? Thanks |
@SidShetye It's pretty unlikely to go into 3.0 at this point. |
@ajcvickers Could we please have this for 3.1? |
@SidShetye Have you considered using value conversions instead? Presumably you have a mechanism for flagging which properties need to be encrypted? You could also consider introducing an Encrypted (potentially with an implicit operator) and expose that in your domain. https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions |
@SidShetye We haven't decided what will be in 3.1 yet. |
With this event we could implement child entity ordering in a way that could simplify our code-base a lot. We used to have a (pretty unoptimized) implementation that relied on internal EFCore classes. This solution broke when transitioning to EFCore 3.0 (as expected, but it was a risk we where willing to take at the time). For now we will probably order manually per entity type that needs it. But it would be great if we configure this globally for the entity type, which this event would allow. |
We need this feature because our entities track their own changes independently of the DbContext, and our framework needs to know the difference between a property being set on initialization vs later. In EF6, we can set an IsInitializing flag to false after the object is materialized, and our tracking code (in property setters) can ignore property changes that happen during initialization. I'm open to ideas as to how else we could achieve similar functionality. |
@EMooreMAC, I’ve solved this in the past. Give me a few days and I’ll try and get some code for you. |
Ok.... posting this for anyone else that would like a workaround in the short term until this is implemented. This isn't fully tested so use at your own risk... and with the caveat that the underlying code may change at any time since it is relying on some EF Core internals. Perhaps someone from the EF team could comment on how stable/usable this might be for the short term. That being said... I'm using the code with great success. Declare an interface for methods to be invoked interface IMaterialize
{
void OnMaterializing();
void OnMaterialized();
} Implement the interface on your model: class YourModel : IMaterialize
{
public void OnMaterializing()
{
// Called after the constructor has been called and object created
}
public void OnMaterialized()
{
// Called after all properties have been populated
}
} Create a new implementation of the class MyEntityMaterializerSource : Microsoft.EntityFrameworkCore.Query.EntityMaterializerSource
{
public MyEntityMaterializerSource(EntityMaterializerSourceDependencies dependencies)
: base(dependencies)
{
}
public override Expression CreateMaterializeExpression(IEntityType entityType, string entityInstanceName, Expression materializationContextExpression)
{
var baseExpression = base.CreateMaterializeExpression(entityType, entityInstanceName, materializationContextExpression);
if (entityType.ClrType.GetInterfaces().FirstOrDefault(i => i == typeof(IMaterialize)) != null)
{
var onMaterializingMethod = entityType.ClrType.GetMethod(nameof(IMaterialize.OnMaterializing));
var onMaterializedMethod = entityType.ClrType.GetMethod(nameof(IMaterialize.OnMaterialized));
var blockExpressions = new List<Expression>(((BlockExpression)baseExpression).Expressions);
var instanceVariable = blockExpressions.Last() as ParameterExpression;
var onMaterializingExpression = Expression.Call(instanceVariable, onMaterializingMethod);
var onMaterializedExpression = Expression.Call(instanceVariable, onMaterializedMethod);
blockExpressions.Insert(1, onMaterializingExpression);
blockExpressions.Insert(blockExpressions.Count - 1, onMaterializedExpression);
return Expression.Block(new[] { instanceVariable }, blockExpressions);
}
return baseExpression;
}
} Now simply replace the service while adding the DbContext to the service collection or in the services.AddDbContext<TestContext>(options =>
{
options.ReplaceService<IEntityMaterializerSource, MyEntityMaterializerSource>();
}...
// Or...
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.ReplaceService<IEntityMaterializerSource, MyEntityMaterializerSource>();
} |
wow ... MyEntityMaterializerSource works great :) an official approach will be great but for now this is perfect |
@StevenRasmussen thanks so much for that workaround! I have just tried it and it works! |
Hi all,,
Do you all have any idea on it |
@StevenRasmussen just wanted to check back and say that this solution is working for us as well. Thanks a ton, you helped clear a blockage for our large scale migration to EF Core. |
@haiplk - Your query does not materialize any entity. |
#15911 (comment) Is a good solution, but a proper callback method being called (eg to provide an object authorisation service audit the object) would be ideal. Are there any plans for this? Thank you |
@StevenRasmussen thanks for the workaround, however So unfortunately this won't work if you want a callback after the "whole" entity is loaded. |
It also seems that the implementation in #15911 (comment) does not call Edit: My mistake - I had called |
Note from triage: consider configure await when implementing this. |
Part of #626 Part of #15911 Fixes #20135 Fixes #14554 Fixes #24902 This is the lowest level of materialization interception--it allows the actual constructor/factory binding to be changed, such that the expression tree for the compiled delegate is altered. Introduces singleton interceptors, which cannot be changed per context instance without re-building the internal service provider. (Note that we throw by default if this is abused and results in many service providers being created.) The use of this for proxies has two big advantages: - Proxy types are created lazily, which vastly improves model building time for big models with proxies. See #20135. - Proxies can now be used with the compiled model, since the proxy types are not compiled into the model. See
Part of #626 Part of #15911 Fixes #20135 Fixes #14554 Fixes #24902 This is the lowest level of materialization interception--it allows the actual constructor/factory binding to be changed, such that the expression tree for the compiled delegate is altered. Introduces singleton interceptors, which cannot be changed per context instance without re-building the internal service provider. (Note that we throw by default if this is abused and results in many service providers being created.) The use of this for proxies has two big advantages: - Proxy types are created lazily, which vastly improves model building time for big models with proxies. See #20135. - Proxies can now be used with the compiled model, since the proxy types are not compiled into the model. See
Part of #626 Fixes #15911 Introduces a new `IMaterializationInterceptor` singleton interceptor that allows: - Interception before any entity instance has been created, allowing a customized instance to be created (if desired), thereby suppressing of normal EF instance creation. - Interception after the instance has been created, but before property values have been set. The instance can be replaced with a new instance (if desired), without preventing EF from setting property values. - Interception before property values have been set, allowing custom setting of the values and/or suppression of setting the values by EF (if desired). - Interception after property values have been set, allowing the instance to be changed (if desired.) Access to property values, including shadow and service properties is provided at each point. If no singleton materialization interceptors are configured, then the materialization delegate is the same as before, meaning any perf impact only applies if interception is used.
Part of #626 Fixes #15911 Introduces a new `IMaterializationInterceptor` singleton interceptor that allows: - Interception before any entity instance has been created, allowing a customized instance to be created (if desired), thereby suppressing of normal EF instance creation. - Interception after the instance has been created, but before property values have been set. The instance can be replaced with a new instance (if desired), without preventing EF from setting property values. - Interception before property values have been set, allowing custom setting of the values and/or suppression of setting the values by EF (if desired). - Interception after property values have been set, allowing the instance to be changed (if desired.) Access to property values, including shadow and service properties is provided at each point. If no singleton materialization interceptors are configured, then the materialization delegate is the same as before, meaning any perf impact only applies if interception is used.
Is it possible to access the related |
@VahidN No; this is operating at a different level in the stack. |
Hello, thanks for this, it is exactly what I needed for my current development.(I am updating the You can find that information however in the following article: What's new in EF 7. But I guess the above link won't be the one people looking for that kind of behavior where they will be looking at. If you could update the documentation, I am sure it would be helpfull for many people. |
The official documentation is indeed lacking a reference to the new |
Splitting this out as a separate issue as required on #626 so we can track it independently of other life-cycle hooks.
The
ObjectMaterialized
event will fire after an entity instance has been created and all it's non-navigation properties have been set.The text was updated successfully, but these errors were encountered: