Eclipselink history of related objects - jpa

I can create history of an entity with a HistoryCustomizer
#Entity
#Customizer(MyHistoryCustomizer.class)
public class Employee {..}
the HistoryCustomizer is something like this one:
public class MyHistoryCustomizer implements DescriptorCustomizer {
public void customize(ClassDescriptor descriptor) {
HistoryPolicy policy = new HistoryPolicy();
policy.addHistoryTableName("EMPLOYEE_HIST");
policy.addStartFieldName("START_DATE");
policy.addEndFieldName("END_DATE");
descriptor.setHistoryPolicy(policy);
}
}
The history objects can be fetched with the "AS_OF" hint
javax.persistence.Query historyQuery = em
.createQuery("SELECT e FROM Employee e", Employee.class)
.setParameter("id", id)
.setHint(QueryHints.AS_OF, "yyyy/MM/dd HH:mm:ss.SSS")
.setHint(QueryHints.READ_ONLY, HintValues.TRUE)
.setHint(QueryHints.MAINTAIN_CACHE, HintValues.FALSE);
just fine BUT, if you start accessing objects referenced by this historical object, the referenced objects will be the actual version of them. So the Employee from last year (fetched by a historical query) will have the current Address assigned to it and no the one it used to have last year.
How can I tell EclipseLink (2.5.0) to fetch the related object from the past as well?

In order to query the historical state of several - not just one like above - entities, we have to create an EclipseLink specific HistoricalSession. Queries run through this session will use the same historical timestamp and represent the proper historical state of the object graph.
I am using JPA in other parts of the code, so I will start with converting the JPA Query to an EclipseLink ReadAllQuery.
The HistoricalSession has its own entity cache, so that the historical entities do not mix with the normal ones.
// Get the EclipseLink ServerSession from the JPA EntitiyManagerFactory
Server serverSession = JpaHelper.getServerSession(emf);
// Only a ClientSession can give us a HistoricalSession so ask one from the ServerSession
ClientSession session = serverSession.acquireClientSession();
// Create the HistoricalSessions. A HistoricalSession is sticked to a point in the past and all the queries are executed at that time.
Session historicalSessionAfterFirstChild = session.acquireHistoricalSession(new AsOfClause(afterFirstChildAdded));
ReadAllQuery q;
Query jpaQuery = em.createQuery(query);
jpaQuery.setParameter("root", "parent");
// Extract the EclipseLink ReadAllQuery from the JPA Query. We can use named queries this way.
q=JpaHelper.getReadAllQuery(jpaQuery);
// This is a possible EclipseLink bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=441193
List<Object> arguments = new Vector<Object>();
arguments.add("parent");
q.setArgumentValues(arguments);
Vector<Parent> historyAwareParents ;
// Execute the query
historyAwareParents = (Vector<Parent>) historicalSessionAfterFirstChild.executeQuery(q);
for (Child c : historyAwareParents.get(0).children) {
System.out.println(c.getExtension() + " " + c.getRoot());
}

Related

Deleting from a standalone index, does not reflect on the index where the deleted entity is used as #IndexedEmbedded

We are using hibernate search 5.9.2.
We have two entities with A and B. A has One-To-Many relationship with B. And we are using them as below:
#Entity
#Indexed(index="master_index")
public class A{
#IndexedEmbedded
private Set<B> b= new HashSet<>(0);
//Setter and getter for b
}
#Entity
#Indexed(index = "b")
public class B{
#ContainedIn
private A a;
//Setter and getter for a
}
One-to-Many relationship is defined under the .hbm files.
Now when some record is deleted from index B directly(but through hibernate process) the same record is not deleted from 'master-index'.
Let us assume I have a record 'xyz' which is available under index B and is also available under 'A' with a relationship like DUDE(data of A) can contain many data like 'xyz'.
DUDE->xyz
The expected result should be the record should delete from index 'b' as well as from the 'master-index'.
Does hibernate search provides a way to handle this situation.
Identified the reason, why this was not working. Will try to provide those findings below:
Initially in my older code, our system was fetching the to be deleted data with a query and then a executeUpdate() was fired and it was not deleting the data using the session.delete(). And due to this the hibernate cache was not aware of the deletion of the object.
Below is the old and new code:
Older Version:
Query query = session.getNamedQuery("deleteBySeqnumRec");
query.setLong("seqnum", seqnum);
int result = query.executeUpdate();
status.setNumOfRows(result);
New Version
DataTO dataTO = selectById(new DataTO, seqnum);
if(!dataTO.isRecNotFound()) {
session.delete(dataTO);
status.setNumOfRows(1);
}
Hibernate Search expects bi-directional associations to be updated consistently on both sides.
This means that, when you delete B, you are expected to remove it from A.b. This will cause a change in entity A, which will trigger reindexing of the entity.
If A wasn't reindexed, it probably means you forgot to remove B from A.b.
Identified the reason, why this was not working. Will try to provide those findings below:
Initially in my older code, our system was fetching the to be deleted data with a query and then a executeUpdate() was fired and it was not deleting the data using the session.delete(). And due to this the hibernate cache was not aware of the deletion of the object.
Below is the old and new code:
Older Version:
Query query = session.getNamedQuery("deleteBySeqnumRec");
query.setLong("seqnum", seqnum);
int result = query.executeUpdate();
status.setNumOfRows(result);
New Version
DataTO dataTO = selectById(new DataTO, seqnum);
if(!dataTO.isRecNotFound()) {
session.delete(dataTO);
status.setNumOfRows(1);
}

Paging and sorting Entity Framework on a field from Partial Class

I have a GridView which needs to page and sort data which comes from a collection of Customer objects.
Unfortunately my customer information is stored separately...the customer information is stored as a Customer ID in my database, and the Customer Name in a separate DLL.
I retrieve the ID from the database using Entity Framework, and the name from the external DLL through a partial class.
I am getting the ID from my database as follows:
public class DAL
{
public IEnumberable<Customer> GetCustomers()
{
Entities entities = new Entities();
var customers = (from c in entities.Customers
select c);
//CustomerID is a field in the Customer table
return customers;
}
}
I have then created a partial class, which retrieves the data from the DLL:
public partial class Customer
{
private string name;
public string Name
{
if (name==null)
{
DLLManager manager = new DLLManager();
name= manager.GetName(CustomerID);
}
return name;
}
}
In my business layer I can then call something like:
public class BLL
{
public List<Customer> GetCustomers()
{
DAL customersDAL = new DAL();
var customers = customersDAL.GetCustomers();
return customers.ToList();
}
}
...and this gives me a collection of Customers with ID and Name.
My problem is that I wish to page and sort by Customer Name, which as we have seen, is populated from a DLL. This means I cannot page and sort in the database, which is my preferred solution. I am therefore assuming I am going to have to call of the database records into memory, and perform paging and sorting at this level.
My question is - what is the best way to page and sort an in-memory collection. Can I do this with my List in the BLL above? I assume the List would then need to be stored in Session.
I am interested in people's thoughts on the best way to page and sort a field that does not come from the database in an Entity Framework scenario.
Very grateful for any help!
Mart
p.s. This question is a development of this post here:
GridView sorting and paging Entity Framework with calculated field
The only difference here is that I am now using a partial class, and hopefully this post is a little clearer.
Yes, you can page and sort within you list in the BLL. As long as its fast enough I wouldn't care to much about caching something in the session. An other way would be to extend your database with the data from you DLL.
I posted this question slightly differently on a different forum, and got the following solution.
Basically I return the data as an IQueryable from the DAL which has already been forced to execute using ToList(). This means that I am running my sorting and paging against an object which consists of data from the DB and DLL. This also allows Scott's dynamic sorting to take place.
The BLL then performs OrderBy(), Skip() and Take() on the returned IQueryable and then returns this as a List to my GridView.
It works fine, but I am slightly bemused that we are perfoming IQueryable to List to IQueryable to List again.
1) Get the results from the database as an IQueryable:
public class DAL
{
public IQueryable<Customer> GetCustomers()
{
Entities entities = new Entities();
var customers = (from c in entities.Customers
select c);
//CustomerID is a field in the Customer table
return customers.ToList().AsQueryable();
}
}
2) Pull the results into my business layer:
public class BLL
{
public List<Customer> GetCustomers(intint startRowIndex, int maximumRows, string sortParameter)
{
DAL customersDAL = new DAL();
return customersDAL.GetCustomers().OrderBy(sortParameter).Skip(startRowIndex).Take(maximumRows).ToList();
}
}
Here is the link to the other thread.
http://forums.asp.net/p/1976270/5655727.aspx?Paging+and+sorting+Entity+Framework+on+a+field+from+Partial+Class
Hope this helps others!

EntityManager doesn't refresh the data after querying

My current project uses HSQLDB2.0 and JPA2.0 .
The scenario is: I query DB to get list of contactDetails of person. I delete single contactInfo at UI but do not save that data (Cancel the saving part).
I again do the same query, now the result list is 1 lesser than previous result coz I have deleted one contactInfo at UI. But that contactInfo is still available at DB if I cross check.
But if I include entityManager.clear() before start of the query, I get correct results every time.
I dont understand this behaviour. Could anyone make it clear for me?
Rather than querying again, try this:
entityManager.refresh(person);
A more complete example:
EntityManagerFactory factory = Persistence.createEntityManagerFactory("...");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Person p = (Person) em.find(Person.class, 1);
assertEquals(10, p.getContactDetails().size()); // let's pretend p has 10 contact details
p.getContactDetails().remove(0);
assertEquals(9, p.getContactDetails().size());
Person p2 = (Person) em.find(Person.class, 1);
assertTrue(p == p2); // We're in the same persistence context so p == p2
assertEquals(9, p.getContactDetails().size());
// In order to reload the actual patients from the database, refresh the entity
em.refresh(p);
assertTrue(p == p2);
assertEquals(10, p.getContactDetails().size());
assertEquals(10, p2.getContactDetails().size());
em.getTransaction().commit();
em.close();
factory.close();
The behaviour of clear() is explained in its javadoc:
Clear the persistence context, causing all managed entities to become detached. Changes made to entities that have not been flushed to the database will not be persisted.
That is, removal of contactInfo is not persisted.
ContactInfo is not getting removed from the database because you remove the relationship between ContactDetails and ContactInfo, but not ContactInfo itself. If you want to remove it, you need either do it explicitly with remove() or specify orphanRemoval = true on the relationship.

I get the error : “An entity object cannot be referenced by multiple instances of IEntityChangeTracker.” with .net MVC2 and the Entity Framework4

I have a big problem since some days and I’m a very beginner in the Entity Framework.
I have 2 entities : Group and News. A news is visible by one or many groups. I use two repositories (newsRepository and groupsRepository).
This is my Create method for the news :
public ActionResult Create()
{
return View(new CreateNewsViewModel(new News()));
}
[HttpPost]
public ActionResult Create(CreateNewsViewModel model)
{
model.news.CategoryId = Int32.Parse(Request.Form["news.CategoryId"]);
if (ModelState.IsValid)
{
News news = new News();
DateTime date = DateTime.Now;
//AuthorId a recuperer
news.AuthorId = 1;
news.Title = IntranetTools.UppercaseFirst(model.news.Title.Trim());
news.Content = model.news.Content;
news.IsVisible = Request.Form["news.IsVisible"].Contains("true");
news.CreateDate = date;
news.PublicationDate = date;
news.LastChangedDate = date;
news.CategoryId = model.news.CategoryId;
// Collection des groupes concernés
foreach (var c in model.allGroups)
{
if (Request.Form["" + c.GroupId].Contains("true"))
{
News.Groups.Add(c);
}
}
_newsRepository.AddToNewsSet(news);
_newsRepository.SaveChanges();
return Redirect("/NewsAdmin/Index/");
}
return View(model);
}
I say that all my groups are already created. I just want to insert the groups (chosen by the user via checkboxes). In my “CreateNewsViewModel”, I create a list of groups that contains all existing groups in my DB. I get the list in my view, via a “foreach” loop and create a checkbox for each group.
I reuse the same list in my controller to compare if checkboxes have been checked.
For each “true” value, I add groups in the groups collection of my news (just created).
With this, I get this Error Message :
“An entity object cannot be referenced by multiple instances of IEntityChangeTracker.” (at line _newsRepository.AddToNewsSet(news);)
I try some solutions but I still don’t understand how I can solve this problem.
Thanks for all
Edit
Actually, if I use explicitly two contexts and detach/attach my objects to other context it's fine and I have no erros.
ObjectContext context = _newsRepository.Context;
ObjectContext context2 = _groupsRepository.Context;
foreach (var c in groups)
{
if (Request.Form["" + c.GroupId].Contains("true"))
{
context2.Detach(c);
context.Attach(c);
news.Groups.Add(c);
}
}
I would like to use the Ladislav Mrnka's solution and use the dependency injection (I use Ninject framework) to give the same ObjectContext to my repositories (in single request processing).
I understand the concept but I don't know how to code it.
The error message says that News object or any of related Group objects is attached to different ObjectContext instance. How is your repository implemented and how did you get model.allGroups? If you loaded allGroups from GroupsRepository which has its own ObjectContext instance then it is probably source of the problem. The solution would be:
(Preferred) Share single ObjectContext for all repositories in single request processing
Detach objects when you load them from database (ObjectContext has Detach method)
Close ObjectContext when you load objects from database

EF Code First - Recreate Database If Model Changes

I'm currently working on a project which is using EF Code First with POCOs. I have 5 POCOs that so far depends on the POCO "User".
The POCO "User" should refer to my already existing MemberShip table "aspnet_Users" (which I map it to in the OnModelCreating method of the DbContext).
The problem is that I want to take advantage of the "Recreate Database If Model changes" feature as Scott Gu shows at: http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx - What the feature basically does is to recreate the database as soon as it sees any changes in my POCOs. What I want it to do is to Recreate the database but to somehow NOT delete the whole Database so that aspnet_Users is still alive. However it seems impossible as it either makes a whole new Database or replaces the current one with..
So my question is: Am I doomed to define my database tables by hand, or can I somehow merge my POCOs into my current database and still take use of the feature without wipeing it all?
As of EF Code First in CTP5, this is not possible. Code First will drop and create your database or it does not touch it at all. I think in your case, you should manually create your full database and then try to come up with an object model that matches the DB.
That said, EF team is actively working on the feature that you are looking for: altering the database instead of recreating it:
Code First Database Evolution (aka Migrations)
I was just able to do this in EF 4.1 with the following considerations:
CodeFirst
DropCreateDatabaseAlways
keeping the same connection string and database name
The database is still deleted and recreated - it has to be to for the schema to reflect your model changes -- but your data remains intact.
Here's how: you read your database into your in-memory POCO objects, and then after the POCO objects have successfully made it into memory, you then let EF drop and recreate the database. Here is an example
public class NorthwindDbContextInitializer : DropCreateDatabaseAlways<NorthindDbContext> {
/// <summary>
/// Connection from which to ead the data from, to insert into the new database.
/// Not the same connection instance as the DbContext, but may have the same connection string.
/// </summary>
DbConnection connection;
Dictionary<Tuple<PropertyInfo,Type>, System.Collections.IEnumerable> map;
public NorthwindDbContextInitializer(DbConnection connection, Dictionary<Tuple<PropertyInfo, Type>, System.Collections.IEnumerable> map = null) {
this.connection = connection;
this.map = map ?? ReadDataIntoMemory();
}
//read data into memory BEFORE database is dropped
Dictionary<Tuple<PropertyInfo, Type>, System.Collections.IEnumerable> ReadDataIntoMemory() {
Dictionary<Tuple<PropertyInfo,Type>, System.Collections.IEnumerable> map = new Dictionary<Tuple<PropertyInfo,Type>,System.Collections.IEnumerable>();
switch (connection.State) {
case System.Data.ConnectionState.Closed:
connection.Open();
break;
}
using (this.connection) {
var metaquery = from p in typeof(NorthindDbContext).GetProperties().Where(p => p.PropertyType.IsGenericType)
let elementType = p.PropertyType.GetGenericArguments()[0]
let dbsetType = typeof(DbSet<>).MakeGenericType(elementType)
where dbsetType.IsAssignableFrom(p.PropertyType)
select new Tuple<PropertyInfo, Type>(p, elementType);
foreach (var tuple in metaquery) {
map.Add(tuple, ExecuteReader(tuple));
}
this.connection.Close();
Database.Delete(this.connection);//call explicitly or else if you let the framework do this implicitly, it will complain the connection is in use.
}
return map;
}
protected override void Seed(NorthindDbContext context) {
foreach (var keyvalue in this.map) {
foreach (var obj in (System.Collections.IEnumerable)keyvalue.Value) {
PropertyInfo p = keyvalue.Key.Item1;
dynamic dbset = p.GetValue(context, null);
dbset.Add(((dynamic)obj));
}
}
context.SaveChanges();
base.Seed(context);
}
System.Collections.IEnumerable ExecuteReader(Tuple<PropertyInfo, Type> tuple) {
DbCommand cmd = this.connection.CreateCommand();
cmd.CommandText = string.Format("select * from [dbo].[{0}]", tuple.Item2.Name);
DbDataReader reader = cmd.ExecuteReader();
using (reader) {
ConstructorInfo ctor = typeof(Test.ObjectReader<>).MakeGenericType(tuple.Item2)
.GetConstructors()[0];
ParameterExpression p = Expression.Parameter(typeof(DbDataReader));
LambdaExpression newlambda = Expression.Lambda(Expression.New(ctor, p), p);
System.Collections.IEnumerable objreader = (System.Collections.IEnumerable)newlambda.Compile().DynamicInvoke(reader);
MethodCallExpression toArray = Expression.Call(typeof(Enumerable),
"ToArray",
new Type[] { tuple.Item2 },
Expression.Constant(objreader));
LambdaExpression lambda = Expression.Lambda(toArray, Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(tuple.Item2)));
var array = (System.Collections.IEnumerable)lambda.Compile().DynamicInvoke(new object[] { objreader });
return array;
}
}
}
This example relies on a ObjectReader class which you can find here if you need it.
I wouldn't bother with the blog articles, read the documentation.
Finally, I would still suggest you always back up your database before running the initialization. (e.g. if the Seed method throws an exception, all your data is in memory, so you risk your data being lost once the program terminates.) A model change isn't exactly an afterthought action anyway, so be sure to back your data up.
One thing you might consider is to use a 'disconnected' foreign key. You can leave the ASPNETDB alone and just reference the user in your DB using the User key (guid). You can access the logged in user as follows:
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
And then use the User's key as a FK in your DB:
Guid UserId = (Guid) currentUser.ProviderUserKey ;
This approach decouples your DB with the ASPNETDB and associated provider architecturally. However, operationally, the data will of course be loosely connected since the IDs will be in each DB. Note also there will be no referential constraints, whcih may or may not be an issue for you.