Hibernate Envers - Adding Historical Data - hibernate-envers

Is there a way to add revision straight into the _AUD tables as a historical revision?
As I understand it as and when an entity is persisted envers creates a revision record in the _AUD table and keeping the current record in the entity table. Suppose I would like to add historical revision and not affect the entity table, is this possible?
For example, I have a Person entity
PERSON
ID | NAME
1 | SMITH
PERSON_AUD
ID | REV | REVTYPE | NAME
1 | 1 | 0 | SMITH
I would like to add the following in PERSON_AUD without modifying the PERSON table as SMITH is the current name.
PERSON_AUD
ID | REV | REVTYPE | NAME
1 | 1 | 0 | SMITH
1 | 2 | 2 | JONES

I'm afraid that's not possible with the current Envers API.

My background is C# but since the frameworks for Java are more powerful (at least I think this) I am sure you will find the related methods.
1) Reattaching the entity to the session will create a new revision:
private void Reattach(Person person)
{
_sessionContainer.Session.Transaction.Begin();
_sessionContainer.Session.Evict(person);
_sessionContainer.Session.Update(person);
_sessionContainer.Session.Flush();
_sessionContainer.Session.Transaction.Commit();
}
2) If you want to manipiulate the audit entry you need to append a listener (in this case for preupdate):
configuration.AppendListeners(ListenerType.PreUpdate, new object[] { new PreUpdateListener() });
where the implementation is doing the magic:
public class PreUpdateListener : IPreUpdateEventListener
{
public bool OnPreUpdate(PreUpdateEvent ev)
{
var person = ev.Entity as Person;
if (person != null)
{
person.Name = "Jones";
}
return true;
}
}
Please let me know if this approach is working for you.

You seem to be forgetting or not aware of the fact that revision number is global; it is tracked in a separate table which is called REVINFO in the default setup. Each entry for a revision number is accompanied by a timestamp as well. It is simply wrong (might even become a serious situation depending on the environment) to be inserting historical data that wasn't created naturally.
If you still want to do this, you can use native SQL to do it by creating a revision entry in REVINFO first and then using that in your audit table. Which is wrong.

Related

Convert Eloquent HasMany relationship to a HasOne?

Ok, i have model A that is linked to many model B, so model A have a hasMany relationship to model B and model B has a belongsTo relationship to model A.
But, amongst all those B there is a particular one, e.g. the one with the higher value, that i want model A to have a dedicated relationship with.
I know i can create a second relationship in model A that re-use the old one and add all the scopes and condition i want, but since i'm building this new relationship from a hasMany, it will return always, no matter what, a collection of results.
Is there a way to have it return a single result instead than a collection? I know i can add a first(), but this involve in using this new relationship as a function, i.e. always with parenthesis, and if possible i would like to still use it the eloquent way.
Is it possible to do that? if yes, how?
How about this:
public function Bs()
{
return $this->hasMany(B::class);
}
public function B()
{
$builder = $this->Bs()->latest(); // Add your own conditions etc...
$relation = new HasOne($builder->getQuery(), $this, 'a_id', 'id');
return $relation; // or return $relation->withDefault();
}
Update:
A new one-of-many relationship shipped with Laravel 8.42. You probably want to use this instead:
public function B(): HasOne
{
return $this->hasOne(B::class)->latestOfMany();
}
Not really no. You have 2 models mapping tables with some attributes as follow
+-----+-----+
| A | B |
+-----+-----+
| id | id |
| ... | a_id|
| | ... |
+-----+-----+
When you call onto the hasMany relationship, the builder executes a query. Something like
SELECT * FROM `B` WHERE `B`.`a_id` = ?
If you make a different relationship for it, you will only duplicate the query (if you load both relationships). You could make an accessor, or just use first().
EDIT Accessor code:
# A model
public function getXAttribute()
{
// Query only if necessary
if(!this->relationLoaded('b')) $this->load('b');
// Use Collection methods to filter
return $this->b->firstWhere(...);
}

#Transient annotation, #org.springframework.data.annotation.Transient annotation, transient keyword and password storing

Currently I'm learning the Spring framework, mainly focusing on it's Security Module. I've watched some guides in connection with registration and login. I saw this common usage of transient keyword or #Transient annotation on the password field in the User class.
My dummy app is using Spring Boot + Spring MVC + Spring Security + MySQL.
I know that
Java's transient keyword is used to denote that a field is not to be serialized.
JPA's #Transient annotation...
...specifies that the property or field is not persistent. It is used to annotate a property or field of an entity class, mapped superclass, or embeddable class.
and the org.springframework.data.annotation's #Transient annotation...
Marks a field to be transient for the mapping framework. Thus the property will not be persisted and not further inspected by the mapping framework.
In my MySQL db I have my spring_demo schema which has 3 tables:
+-----------------------+
| Tables_in_spring_demo |
+-----------------------+
| role |
| user |
| user_role |
+-----------------------+
When I'm using the transient keyword on the password field int the User class, it would not be stored in the MySQL db. (example: test01)
mysql> select * from user;
+----+--------+------------------+----------+
| id | active | email | username |
+----+--------+------------------+----------+
| 1 | 1 | test01#gmail.com | test01 |
+----+--------+------------------+----------+
1 row in set (0,00 sec)
When I'm using the javax.persistence #Transient annotation on the password field in the User class, it also would not be stored in the MySQL db. (example: test02)
But... when I'm using the org.springframework.data.annotation #Transient annotation on the password field in the User class it does stored in the MySQL db. (example: test03) Why is that?
mysql> select * from user;
+----+--------+------------------+----------+--------------------------------------------------------------+
| id | active | email | username | password |
+----+--------+------------------+----------+--------------------------------------------------------------+
| 1 | 1 | test02#gmail.com | test02 | |
| 2 | 1 | test03#gmail.com | test03 | $2a$10$UbvmdhfcKxSNr/I4CjOLtOkKGX/j4/xQfFrv3FizxwEVk6D9sAoO |
+----+--------+------------------+----------+--------------------------------------------------------------+
2 rows in set (0,00 sec)
My main questions are, when I'm using the spring.data based #Transient annotation the password field has persisted. Why? And why should I use any #Transient annotation on a password field?
Thank you for your guidance and help in advance!
Within the Spring Framework you can use Mapping Framework to convert from one form to another. Say for example your spring java server side application needs send to user information to a client (webpage,mobile app) in JSON format.
#Entity
public class User {
#Id
private long id;
#Column(name = "username")
private String username;
#Column(name = "email")
private String email;
#Column(name = "password")
private String password;
}
Now to map this java entity object to JSON format you can either use a mapping framework (e.g jackson: com.fasterxml.jackson.databind.ObjectMapper) or do it manually.
The JSON format output that you would get when to convert user 2 object to JSON is:
{
"id": 2,
"email": "test03#gmail.com",
"username": "test03",
"password": "$2a$10$UbvmdhfcKxSNr/I4CjOLtOkKGX/j4/xQfFrv3FizxwEVk6D9sAoO"
}
Now if you added :
#org.springframework.data.annotation.Transient
#Column(name = "password")
private String password;
and then used the Mapping Framework to again generate the JSON for the user 2 entity you would get:
{
"id": 2,
"email": "test03#gmail.com",
"username": "test03",
}
Note the password field is missing from you JSON output. Thats because #org.springframework.data.annotation.Transient specifically states to the spring framework that the Object Mapper you are using should not include this value when converting from Java Object to JSON.
Also note if you attempted to persist the above entity into the database, it would still save it to the database because #org.springframework.data.annotation.Transient only applys to Object mapping frameworks not JPA.
So to recap:
transient is for all serializations (over the wire, saving to disk, saving to db)
javax.persistence.Transient is specifically for JPA DB serialization
#org.springframework.data.annotation.Transient is for ObjectMapping Framework serializations used within Spring

Why can't EF handle two properties with same foreign key, but separate references/instances?

Apparently, EF6 doesn't like objects that have multiple foreign key properties that use the same key value, but do not share the same reference. For example:
var user1 = new AppUser { Id = 1 };
var user2 = new AppUser { Id = 1 };
var address = new Address
{
CreatedBy = user1, //different reference
ModifiedBy = user2 //different reference
};
When I attempt to insert this record, EF throws this exception:
Saving or accepting changes failed because more than one entity of type
'AppUser' have the same primary key value. [blah blah blah]
I've discovered that doing this resolves the issue:
var user1 = new AppUser { Id = 1 };
var user2 = user1; //same reference
I could write some helper code to normalize the references, but I'd rather EF just know they're the same object based on the ID alone.
As for why EF does this, one explanation could be that its trying to avoid doing multipe CRUD operations on the same object since separate instances of the same entity could contain different data. I'd like to be able to tell EF not to worry about that.
Update
So it's as I suspected per my last paragraph above. In absense of a means to tell EF not to do CRUD on either instance, I will just do this for now:
if (address.ModifiedBy.Id == address.CreatedBy.Id)
{
address.ModifiedBy = address.CreatedBy;
}
Works well enough so long as I am not trying to do CRUD on either.
Update2
I've previously resorted to doing this to prevent EF from validating otherwise-required null properties when all I need is the child entity's ID. However, it doesn't keep EF from going into a tizzy over separate instances with the same ID. If it's not going to do CRUD on either AppUser object, why does it care if the instances are different?
foreach (var o in new object[] { address.ModifiedBy, address.CreatedBy })
{
db.Entry(o).State = EntityState.Unchanged;
}
If you get AppUser from context, then you will not need to do anything, because Entity Framework will track entities:
var user1 = context.AppUsers.Find(1);
var user2 = context.AppUsers.Find(1);
var address = new Address
{
CreatedBy = user1, //different reference
ModifiedBy = user2 //different reference
};
Now, they both will point to same objects and will not cause to conflict.
You can add two extra properties to have the Id for the main objects which is the AppUser, then you can use only one AppUser object and reference it for both the created and modified by properties.
CreatedById = user1.Id,
ModifiedById = user1.Id
Otherwise, your code will end up by saving two instances of AppUser with the same primary key.
Another approach is to set both the foreign key properties to only one AppUserobject
The explanation is that EF's change tracker is an identity map. I.e. a record in the database is mapped to one, and only one, CLR object.
This can be demonstrated easily by trying to attach two objects with the same key:
context.AppUsers.Attach(new AppUser { Id = 1 });
context.AppUsers.Attach(new AppUser { Id = 1 });
The second line will throw an exception:
Attaching an entity of type 'AppUser' failed because another entity of the same type already has the same primary key value.
This also happens if you assign
CreatedBy = user1, //different reference
ModifiedBy = user2 //different reference
Somewhere in the process, user1 and user2 must be attached to the context, giving rise to the exception you get.
Apparently, you have a function that receives two Id values that can be different or identical. Admittedly, it would be very convenient if you could simply create two AppUser instances from these Ids, not having to worry about identical keys. Unfortunately, your solution ...
if (address.ModifiedBy.Id == address.CreatedBy.Id)
... is necessary. Solid enough, though.

how to insert multiple values using entity framework..?

I'm using Silverlight5 with MVVM framework and Entity framework. In my project i have one doubt..
I have an entity named 'Customer' the structure is as follows..
Customer ocustomer = new Customer();
ocustomer.CustomerName = customername;
ocustomer.CompanyName = company;
ocustomer.AddressLine1 = address1;
ocustomer.AddressLine2 = address2;
ocustomer.City = city;
ocustomer.State = state;
ocustomer.Country = country;
ocustomer.ZipCode = zipcode;
ocustomer.Notes = note;
_context.Customers.Add(ocustomer);
Now my need is to insert the Customer value into another table Named Customer_Entity
Customer_Entity ocustomerEntity=new Customer_Entity ();
ocustomerEntity.CustomerID=Customer_ID;
ocustomerEntity.EntityTypeID =1;
.
.
.
.
ocustomerEntity.CreatedTime =System.DateTime.Now;
ocustomerEntity.CreatedBy=Common.ActiveData.Instance.userid;
Here my need is to insert the customer value to Customer_Entity in every single row..
Customer_Entity Table Structure is as follows,
EntityID| CustomerID| EntityValue|
----------------------------------
1 | 22 |jasper |
2 | 22 |Company:Raj |
3 | 22 |Address |
ocustomer.CustomerName=customername
.
.
.
ocustomer.CreatedTime=system.DateTime.Now..
so i need to insert all the values in every single row using unique CustomerID..
Need help to solve this one..
On your CustomerEntity object you should have a navigation property pointing to the corresponding Customer record. Similarly, I would expect that your Customer class has a collection of CustomerEntity on it as well.
In which case, you should instantiate the Customer object, populating with the necessary information. Don't add it to the DbSet yet, though. Afterwards, create all of the CustomerEntity records that you need to, connecting it to the Customer object by using the navigation property itself (i.e. NOT the ID field), and adding that CustomerEntity record to the corresponding DbSet in you Context class.
As a final step add your Customer object to the corresponding DbSet, and run SaveChanges. You should be good to go. Entity Framework should automatically generate the ID for you and populate it to the CustomerEntity records.

Entity Framework: perform a query on a linking table / many to many relationship

I am trying to perform a query using linq to entities to that an entity/table doesn't contain the same values before I update it.
The structure of the database is as follows:
Users User_IPAddresses IPAddresses
----- ---------------- -----------
UserID >------ UserID ------< IPAddressID
User IPAddressID Address
So, the structure of the entity object is as follows
UserSet IPAddressSet
------- >-----< ------------
User IPAddress
All the ID fields are Primary Keys, so the link table (User_IPAddresses) must contain unique rows.
The problem I am having is that I can't get my head around how to check the entities so that I don't violate the unique row constraint on the User_IPAddresses table before I update it.
Any EF gurus out there that can help me?
//returns true if pair exists
public bool CheckIfUserIPPairExists(int ipID, int userID)
{
bool exists
= db.UserSet.Any(user=>user.UserID==userID
&& user.IPAddress.Any(ip=>ip.IPAddressID == ipID));
return exists;
}