Entity Framework Core 2.1, ChangeTracker.Tracked - entity-framework-core

I'm trying to figure out how to use Entity Framework Cores 2.1 new ChangeTracker.Tracked Event to hook into reading queries. Unfortunately, I'm not being able to understand how to implement this.
Since it's a new feature it's not possible to find any articles on it and the official Microsoft docs site does not provide any help or sample code.
My scenario is pretty simple. I have a database with following columns:
id, customerId, metadata.
When a user queries this table I want to intercept the query result set and for every row, I want to compare the customerId with currently logged in user.
I'm hoping that ChangeTracker.Tracked Event can help me in intercepting the return result set. I'm looking for some sample code on how to achieve above.

Here is a sample usage of the ChangeTracker.Tracked event.
Add the following method to your context (requires using Microsoft.EntityFrameworkCore.ChangeTracking;):
void OnEntityTracked(object sender, EntityTrackedEventArgs e)
{
if (e.FromQuery && e.Entry.Entity is YourEntityClass)
{
var entity = (YourEntityClass)e.Entry.Entity;
bool isCurrentUser = entity.customerId == CurrentUserId;
// do something (not sure what)
}
}
and attach it to the ChangeTracker.Tracked even in your context constructor:
ChangeTracker.Tracked += OnEntityTracked;
As described in the Tracked event documentation:
An event fired when an entity is tracked by the context, either because it was returned from a tracking query, or because it was attached or added to the context.
Some things to mention.
The event is not fired for no-tracking queries
The event is fired for each entity instance created by the tracking query result set and not already tracked by the context
The bool FromQuery property of the event args is used to distinguish if the event is fired from the tracking query materialization process or via user code (Attach, Add etc. calls).
The EntityEntry Entry property of the event args gives you access to the entity instance and other related information (basically the same information that you get when calling the non-generic DbContext.Entry method)

Related

Trigger JPA callbacks only once per transaction or batch

I am deleting rows in a batch as follows (in an EJB).
int i=0;
List<Category> list = // Sent by a client which is JSF in this case.
for(Category category:list) {
if(++i%49==0) {
i=0;
entityManager.flush();
}
entityManager.remove(entityManager.contains(category) ? category : entityManager.merge(category));
}
Where Category is a JPA entity.
There is a callback that listens to this delete event.
#ApplicationScoped
public class CategoryListener {
#PostPersist
#PostUpdate
#PostRemove
public void onChange(Category category) {
//...
}
}
This callback method is invoked as many times as the number of rows which are deleted. For example, this method will be called 10 times, if 10 rows are deleted.
Is there a way to invoke the callback method only once at the end of a transaction i.e. as soon as the EJB method in which this code is executed returns or at least per batch i.e. when entityManager.flush(); occurs? The former is preferred in this case.
Additional Information :
I am doing some real time updates using WebSockets where clients are to be notified when such CRUD operations are performed on a few database tables. It is hereby meaningless to send a message to all the associated clients on deletion of every row which is performed in a batch - every time a single row is deleted. They should rather be notified only once/at once (as soon as) a transaction (or at least a batch) ends.
The following JPA 2.1 criteria batch delete approach does not work because it does not directly operate upon entities. No JPA callbacks will be triggered by this approach neither by using its equivalent JPQL.
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaDelete<Category> criteriaDelete = criteriaBuilder.createCriteriaDelete(Category.class);
Root<Category> root = criteriaDelete.from(entityManager.getMetamodel().entity(Category.class));
criteriaDelete.where(root.in(list));
entityManager.createQuery(criteriaDelete).executeUpdate();
I am using EclipseLink 2.5.2 having JPA 2.1
Unfortunately JPA provides entity callbacks, which are required to be called for each entity instances they listen on, so you will need to add in your own functionality to see that the listener is triggered only once per batch/transaction etc. The other alternative is to use provider specific behavior, in this case EclipseLink's session event listeners: https://wiki.eclipse.org/Introduction_to_EclipseLink_Sessions_(ELUG)#Session_Event_Manager_Events to listen for the PostCalculateUnitOfWorkChangeSet event or some other event that gets triggered when you need.

Best way to handle JPA merge?

I'm new to the whole JPA thing so I have multiple questions about the best way to handle JPA merge and persist.
I have an user object which should be updated (some values like date and name). Do I have to merge the passed object first or is it safer to find the new object?
Currently my code for updating a user looks like this:
public void updateUserName(User user, String name) {
// maybe first merge it?
user.setName(name);
user.setChangeDate(new Date());
em.merge(user);
}
How can I be sure that the user has not been manipulated before the update method is called? Is it safer to do something like this:
public void updateUserName(int userId, String name) {
User user = em.getReference(User.class, userId);
user.setName(name);
user.setChangeDate(new Date());
em.merge(user);
}
Maybe other solutions? I watched multiple videos and saw many examples but they all were different and nobody explained what the best practice is.
What is the best approach to add children to relationships? For example my user object has a connection to multiple groups. Should I call the JPA handler for users and just add the new group to the user group list or should I create the group in a so group handler with persist and add it manually to my user object?
Hope somebody here has a clue ;)
It depends on what you want to achieve and how much information you have about the origin of the object you're trying to merge.
Firstly, if you invoke em.merge(user) in the first line of your method or in the end it doesn't matter. If you use JTA and CMT, your entity will be updated when method invocation finishes. The only difference is that if you invoke em.merge(user) before changing the user you should use the returned instance instead of your parameter, so either it is:
public void updateUserName(User user, String name) {
User managedUser = em.merge(user);
managedUser.setChangeDate(new Date());
// no need of additional em.merge(-) here.
// JTA automatically commits the transaction for this business method.
}
or
public void updateUserName(User user, String name) {
user.setChangeDate(new Date());
em.merge(user);
// JTA automatically commits the transaction for this business method.
}
Now about updating entity.
If you just want to update some well-defined fields in your entity - use the second approach as it's safer. You can't be sure if a client of your method hasn't modified some other fields of your entity. Therefore, em.merge(-) will update them as well which might not be what you wanted to achieve.
On the other hand - if you want to accept all changes made by user and just override / add some properties like changeDate in your example, the first approach is also fine (merge whole entity passed to the business method.) It really depends on your use-case.
I guess it depends on your cascading settings. If you want to automatically persist / merge all Groups whenever the User entity is changed - it's safe to just add it to the User's collection (something like User#addGroup(Group g) { groups.add(g)}. If you don't want cascading, you can always create your own methods that will propagate to the other side of the relationship. It might be something like: User#addGroup(Group g) that automatically invokes g.addUser(this);.
Question 1
The merge method must be called on a detached entity.
The merge method will return the merged object attached to the entityManager.
What does it mean ?
An entity is detached as soon as the entityManager you use to fetch it is closed. (i.e. most of the time because you fetch it in a previous transaction).
In your second sample of code: user is attached (because you just fetch it) and so calling merge is useless. (BTW : it is not getReference but find)
In your first sample: we don't know the state of user (detached entity or not ?). If it is detached, it make sense to call merge , but careful that merge don't modify it's object passed as an argument. So here is my version of your first sample:
/**
* #param user : a detached entity
* #return : the attached updated entity
**/
public User updateUserName(User user, String name) {
user.setName(name);
user.setChangeDate(new Date());
return em.merge(user);
}
Question 2
Maybe some code sample to explain what you mean by jpa handler can help us to understand your concern. Anyway, I'll try to help you.
If you have a persistent user and you need to create a new group and associating it with the persistent user:
User user = em.find(User.class, userId);
Group group = new Group();
...
em.persist(group);
user.addToGroups(group);
group.addToUsers(user); //JPA won't update the other side of the relationship
//so you have to do it by hand OR being aware of that
If you have a persistent user and a persistent group and you need to associate them:
User user = em.find(User.class, userId);
Group group = em.find(Group.class, groupId);
...
user.addToGroups(group);
group.addToUsers(user);
General considerations
The best practices regarding all of this really depends on you manage the transactions (and so the lifecycle of the entityManager) vs the lifecycle of your objects.
Most of the time: an entityManager is a really short time living object. On the other hand your business objects may live for longer and so you will have to call merge (and being careful about the fact that merge don't modify the object passed in argument !!!).
You can decide to fetch and modify your business objects in the same transaction (i.e. with the same entityManager): it means a lot more database access and this strategy must generally be combined with a second-level cache for performance reason. But in this case you won't have to call merge.
I hope this help.

Why does a JPA #PreUpdate-annotated method get called during a query?

I have a named query that returns a Collection of entities.
These entities have a #PreUpdate-annotated method on them. This method is invoked during query.getResultList(). Because of this, the entity is changed within the persistence context, which means that upon transaction commit, the entity is written back to the database.
Why is this? The JPA 2.0 specification does not mention explicitly that #PreUpdate should be called by query execution.
The specification says:
The PreUpdate and PostUpdate callbacks occur before and after the
database update operations to entity data respectively. These database
operations may occur at the time the entity state is updated or they
may occur at the time state is flushed to the database (which may be
at the end of the transaction).
In this case calling query.getResultList() triggers a em.flush() so that the query can include changed from current EntityManager session. em.flush() pushes all the changes to the database (makes all UPDATE,INSERT calls). Before UPDATE is sent via JDBC #PreUpdate corresponding hooks are called.
This is just my comment from rzymek's answer with some follow up code:
I tried to reproduce the problem OP had, because it sounded like the EntityManager would get flushed everytime the query is called. But that's not the case. #PostUpdate methods are only called when there is actual changed being done to the Database as far as I can tell. If you made a change with the EntityManager that is not yet flushed to the DB query.getResultList will trigger the flush to the DB which is the behaviour one should expect.
Place valinorDb = em.find(Place.class, valinorId);
// this should not trigger an PostUpdate and doesn't
// TODO: unit-testify this
em.merge(valinorDb);
valinorDb.setName("Valinor123");
valinorDb.setName("Valinor");
// this shouldn't trigger an PostUpdate because the Data is the same as in the beginning and doesn't
em.merge(valinorDb);
{
// this is done to test the behaviour of PostUpdate because of
// this:
// http://stackoverflow.com/questions/12097485/why-does-a-jpa-preupdate-annotated-method-get-called-during-a-query
//
// this was tested by hand, but should maybe changed into a unit
// test? PostUpdate will only get called when there is an actual
// change present (at least for Hibernate & EclipseLink) so we
// should be fine
// to use PostUpdate for automatically updating our index
// this doesn't trigger a flush as well as the merge didn't even trigger one
Place place = (Place) em.createQuery("SELECT a FROM Place a")
.getResultList().get(0);
Sorcerer newSorcerer = new Sorcerer();
newSorcerer.setName("Odalbort the Unknown");
place.getSorcerers().add(newSorcerer);
//this WILL trigger an PostUpdate as the underlying data actually has changed.
place = (Place) em.createQuery("SELECT a FROM Place a")
.getResultList().get(0);
}
In my case JPA Event Listener (#EntityListeners) calls query.getResultList() in its logic (to do some validation) and in effect goes into
neverending loop that calls the same listener once again and again and in the end got StackOverflowError. I used flush-mode = COMMIT to avoid flush on query like below. Maybe for someone it will be helpful.
List l = entityManager.createQuery(query)
/**
* to NOT do em.flush() on query that trigger
* #PreUpdate JPA listener
*/
.setFlushMode(FlushModeType.COMMIT)
.getResultList();

Is it possible to tell if an entity is tracked?

I'm using Entity Framework 4.1. I've implemented a base repository using lots of the examples online. My repository get methods take a bool parameter to decide whether to track the entities. Sometimes, I want to load an entity and track it, other times, for some entities, I simply want to read them and display them (i.e. in a graph). In this situation there is never a need to edit, so I don't want the overhead of tracking them. Also, graph entities are sent to a silverlight client, so the entities are disconnected from the context. Hence my Get methods can return a list of entities that are either tracked or not. This is achieved dynamically creating the query as follows:
DbQuery<E> query = Context.Set<E>();
// Track the entities in the context?
if (!trackEntities)
{
query = query.AsNoTracking();
}
However, I now want to enable the user to interact with the graph and edit it. This will not happen very often, so I still want to get some entities without tracking them but to have the ability to save them. To do this I simply attach them to the context and set the state as modified. Everything is working so far.
I am auditing any changes by overriding the SaveChanges method. As explained above I may, in some low cases, need to save modified entities that were disconnected. So to audit, I have to retrieve the current values from the database and then compare to work out what was changed while disconnected. If the entity has been tracked, there is no need to get the old values, as I've got access to them via the state manager. I'm not using self tracking entities, as this is overkill for my requirements.
QUESTION: In my auditing method I simply want to know if the modified entity is tracked or not, i.e. do I need to go to the db and get the original values?
Cheers
DbContext.ChangeTracker.Entries (http://msdn.microsoft.com/en-us/library/gg679172(v=vs.103).aspx) returns DbEntityEntry objects for all tracked entities. DbEntityEntry has Entity property that you could use to find out whether the entity is tracked. Something like
var isTracked = ctx.ChangeTracker.Entries().Any(e => Object.ReferenceEquals(e.Entity, myEntity));

persisting an update query using openJPA

I am attempting to update an existing record using JPA. The following link seems to suggest that the only way to update a record would be to write the update query for it
enter link description here
Which is fine. But again, I am wondering why am I pulling this out of stored proc to use all f the magic of open JPA?
I thought that If I had an instance of a JPA object that if I tried to persist to the database using a call similar to this
emf.persist(launchRet)
the JPA framework would check to see if the record allready exists, if so, it would then proceed to make the changes to that record, if not, it would just add a new record. Which would be really cool. Instead, I am going to have to end up writing all that logic myself in an update query. Which is fine, But why can't I just use a stored proc and just pass it all the necessary values?
UPDATE: CODE EXPLAINING WHAT MY LAST COMMENT IS ALL ABOUT
try{
launchRet = emf.find(QuickLaunch.class, launch.getQuickLaunchId());
if(launchRet==null){
emf.getTransaction().begin();
emf.persist(launchRet);
emf.getTransaction().commit();
}
else{
emf.refresh(launchRet);
}
}
The variable launch is passed into the method...
public QuickLaunch UpdateQuickLaunchComponent(QuickLaunch launch)
Would I simple just set the found launch launchRet equal to the launch that was passed in?
Read the link that you posted:
You can modify an entity instance in one the following ways:
Using an Updating Query
Using the Entity's Public API
[...]
The way used in 99% of the cases is the second way:
Foo someExistingFoo = em.find(Foo.class, someExistingFooId);
someExistingFoo.setSomeValue(theNewValue);
// no need to call any persist method: the EM will flush the new state
// of the entity to the database when needed
Of course, the entity can also be loaded via a query, or by navigating through the graph of entities.
If you have a detached entity, and you want to persist its state to the database, use the EntityManager.merge() method. It finds the entity with the same ID as the detached one passed as argument, copies the state from the detached entity to the attached one, and returns the attached one:
Foo attachedModifiedFoo = em.merge(detachedFoo);
If the detached entity isn't persistent (i.e. doesn't have any ID), then it is created and made persistent.