For my project, I have to log all changes made on my objects, through the entity framework. This consists just to register which fields have been edited on which table at which time.
Roughly, put changes in a table with this kind of structure:
IDEvent, EventDate, TableName, RowID, FieldName, OldValue, NewValue
If there is multiple changes, several rows will be inserted.
It already works for 90% of my cases, I'm listening the SavingChanges event of the ObjectContext
My only problem: In the case of an add, my primary keys that are generated by SQL(IDENTITY), are not present at this moment(logic) on the SavingChanges event, because it's not already stored in the DB, and the problem is that I really need it(To fill my RowID in my table)
So, do you have an idea how to do this? I didn't found any "ChangesSaved" event. An idea of workaround?
You will not be able to do this in SavingChanges event. I think you can create your own wrapper for ObjectContext and implement your own logic in wrapper method for SaveChanges. Logic should be like
public class MyContextWrapper : IDisposable
{
private ObjectContext _context;
public void SaveChanges()
{
// Detect changes but do not accept them
_context.SaveChanges(SaveOptions.DetectChangesBeforeSave); // SaveChanges(false) in .NET 3.5 SP1
// TODO audit trail
// Audit is completed so accept changes
_context.AcceptAllChanges();
}
}
You should also add TransactionScope to your new SaveChanges.
Related
I wanted to know if the {save} method in CrudRepository do an update if it finds already the entry in the database like :
#Repository
public interface ProjectDAO extends CrudRepository<Project, Integer> {}
#Service
public class ProjectServiceImpl {
#Autowired private ProjectDAO pDAO;
public void save(Project p) { pDAO.save(p); } }
So if I call that method on an already registred entry, it'll update it if it finds a changed attribute ?
Thanks.
I wanted to know if the {save} method in CrudRepository do an update
if it finds already the entry in the database
The Spring documentation about it is not precise :
Saves a given entity. Use the returned instance for further operations
as the save operation might have changed the entity instance
completely.
But as the CrudRepository interface doesn't propose another method with an explicit naming for updating an entity, we may suppose that yes since CRUD is expected to do all CRUD operations (CREATE, READ, UPDATE, DELETE).
This supposition is confirmed by the implementation of the SimpleJpaRepository
class which is the default implementation of CrudRepository which shows that both cases are handled by the method :
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
So if I call that method on an already registered entry, it'll update
it if it finds a changed attribute?
It will do a merge operation in this case. So all fields are updated according to how the merging cascade and read-only option are set.
Looking at the default implemantation of CrudRepository interface
/*
* (non-Javadoc)
* #see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
*/
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
Save method manage two situations:
-If the person Id is null (a new entity is created) then save will call persist method => insert query will be executed.
-If the person id is not null then save will call merge: fetch the existing entity from entityManagerFactory(from the 2 level cache if it doesn't exist then it will be fetched from the database) and comparing the detached entity with the managed and finally propagate the changes to the database by calling update query.
To be precise, the save(obj) method will treat obj as a new record if the id is empty (therefore will do an insert) and will treat obj as an existing record if the id is filled in (therefore will do the merge).
Why is this important?
Let's say the Project object contains an auto-generated id and also a person_id which must be unique. You make a Project object and fill in the person_id but not the id and then try to save. Hibernate will try to insert this record, since the id is empty, but if that person exists in the database already, you will get a duplicate key exception.
How to handle
Either do a findByPersonId(id) to check if the obj is in the db already, and get the id from that if it is found,
Or just try the save and catch the exception in which case you know it's in the db already and you need to get and set the id before saving.
I wanted to know if the {save} method in CrudRepository do an update if it finds already the entry in the database:
The Answer is Yes, It will update if it finds an entry:
From Spring Documentation: Herehttps://docs.spring.io/spring-data/jpa/docs/1.5.0.RELEASE/reference/html/jpa.repositories.html?
Saving an entity can be performed via the CrudRepository.save(…)-Method. It will persist or merge the given entity using the underlying JPA EntityManager. If the entity has not been persisted yet Spring Data JPA will save the entity via a call to the entityManager.persist(…)-Method, otherwise the entityManager.merge(…)-Method will be called.
In my case I had to add the id property to the Entity, and put the annotation #Id like this.
#Id
private String id;
This way when you get the object has the Id of the entity in the database, and does the Update operation instead of the Create.
TL;DR What is the proper way of rehydrating an entity framework object with a self referential many to many relationship from a DTO and updating it with the new values so that the database updates correctly?
I have the following entity (irrelevant stuff trimmed)
public class Role
{
[Key]
[Required]
public String RoleId { get; set; }
public List<Role> Children { get; set; }
}
In my dbContext, I have set up a many to many relationship
modelBuilder.Entity<Role>().HasMany(r => r.Children).WithMany();
I'm using MVC front end, with a web-api backend for an n-tier setup, and an mssql database.
The following chain of events happens
Browser->MVC Controller->REST call to Web API->WebAPI Controller->DB Context Query
This chain happens twice, once to view the page in edit mode, and then again when the user pushes the save button to persist.
When setting children on the entity, they always already exist first (IE, you don't create the parent and the children at the same time, you are just adding an existing child to a parent)
There is a DTO used by the MVC model and web API, which I re-hydrate to the entity on the web-api side.
public IHttpActionResult UpdateRoleInfo(RoleVM roleInfo){
//lookup existing entity to update
var existing = db.Roles.FirstOrDefault(y => y.RoleId == roleInfo.ExistingRoleId);
...Something happens here (see below for things i've tried)...
db.SaveChanges();
}
My first try was this :
existing.Children = roleInfo.Children
This tried to recreate all of the existing children as part of the save. (Primary key constraint violation on the roles table)
I changed that to
//Fetch all of the roles from the database to lookup the existing children
var allRoles = GetRoles();
//Have to reselect the roles from the DB so the DB doesn't try to recreate new ones for the children.
var childrenToAdd = roleInfo.Roles.Select(role2 => allRoles.FirstOrDefault(r => r.RoleId == role2.RoleId)).ToList();
existing.Children = childrenToAdd;
This correctly works for updating a role that does not already have any children, to add some the first time, but if you update a role that already has children, it tries to re-add the children to the database a second time, getting a primary key violation on the roles_role table
I then tried pre-pending this code to the second one above,
existing.Children.Clear();
db.SaveChanges();
I would expect this to delete all the existing parent-child relationships from the many to many table for this parent, and then recreate them with the new children. Why not?
TL;DR What is the proper way of rehydrating an entity framework object with a self referential many to many relationship from a DTO and updating it with the new values so that the database updates correctly?
Try turning off auto detect changes (before retrieving from the DB) via
context.Configuration.AutoDetectChangesEnabled = false;
Then set the state to modified on the specific role object you are updating
context.Entry(role).State = EntityState.Modified;
Haven't tried this myself on a self-referencing many-to-many table, but adding & updating entities in the manner can save all sorts of headaches where EF incorrectly infers what you are adding/updating
Found the problem.
On the initial load of the entity, I was using an include statement to eager load the children.
When I updated the entity, when I fetched it from the db again, I did not eager load the children. Therefore the additions/updates were getting confused. Once I put the include in during the upload Scenario #2 above worked (the explicit clear was not needed)
db.Roles.Include("Children").FirstOrDefault(z => z.RoleId == RoleId);
Also related, if you have this same problem when dealing with relationships across different tables, make sure all the entities that are involved in the graph are from the same DB context!
https://msdn.microsoft.com/en-us/magazine/dn166926.aspx
Given a "User" table and a "Login" table in MS SQL 2008:
CREATE TABLE [dbo].[User_User](
[UserID] [int] IDENTITY(1000,1) NOT NULL,
[UserName] [varchar](63) NOT NULL,
[UserPassword] [varchar](63) NOT NULL
)
CREATE TABLE [dbo].[Util_Login](
[LoginID] [int] IDENTITY(1000,1) NOT NULL,
[User_UserID] [int] NOT NULL, -- FK REFERENCES [dbo].[User_User] ([UserID])
[LoginDate] [datetime] NOT NULL,
)
How do I adjust my User_User entity framework model object to include a "UserLastLogin" column that returns a MAX(LoginDate)?
I know that I can create an EF4 model around a SQL View:
CREATE VIEW [v_User_User]
AS
SELECT
[User_User].*,
(
SELECT MAX(LoginDate)
FROM [Util_Login]
WHERE User_UserID = UserID
) AS UserLastLogin
FROM [User_User]
But is there a way that I can just modify the User_User model to include the calculated columnn?
EDIT: I am looking for a way to fetch a User or a List<User> including the Max(Util.LastLogin) date in a single db query.
Very good question, and Yes, there is a perfect way to accomplish this in EF4:
Custom properties are a way to provide computed properties to entities. The good news is that Custom properties don’t necessarily need to be calculated from other existing properties on the very same entity, by the code we are about to see, they can computed from just about anything we like!
Here are the steps:
First create a partial class and define a custom property on it (For simplicity, I assumed User_User table has been mapped to User class and Util_Login to Util)
public partial class User {
public DateTime LastLoginDate { get; set; }
}
So, as you can see here, rather than creating a LastLoginDate property in the model, which would be required to map back to the data store, we have created the property in the partial class and then we have the option to populate it during object materialization or on demand if you don’t believe that every entity object will need to provide that information.
In your case precalculating the LastLoginDate custom property for every User being materialized is useful since I think this value will be accessed for all (or at least most) of the entities being materialized. Otherwise, you should consider calculating the property only as needed and not during object materialization.
For that, we are going to leverage ObjectContext.ObjectMaterialized Event which is raised anytime data is returned from a query since the ObjectContext is creating the entity objects from that data. ObjectMaterialized event is an Entity Framework 4 thing.
So all we need to do is to create an event handler and subscribe it to the ObjectMaterialized Event.
The best place to put this code (subscribing to the event) is inside the OnContextCreated Method. This method is called by the context object’s constructor and the constructor
overloads which is a partial method with no implementation, merely a method signature created by EF code generator.
Ok, now you need to create a partial class for your ObjectContext. (I assume the name is UsersAndLoginsEntities) and subscribe the event handler (I named it Context_ObjectMaterialized) to ObjectMaterialized Event.
public partial class UsersAndLoginsEntities {
partial void OnContextCreated() {
this.ObjectMaterialized += Context_ObjectMaterialized;
}
}
The last step (the real work) would be to implement this handler to actually populate the Custom Property for us, which in this case is very easy:
void Context_ObjectMaterialized(object sender, ObjectMaterializedEventArgs args)
{
if (args.Entity is User) {
User user = (User)args.Entity;
user.LastLoginDate = this.Utils
.Where(u => u.UserID == user.UserID)
.Max(u => u.LoginDate);
}
}
Hope this helps.
After much deliberation, I ended up with the following solution:
First, create a view containing all User fields plus a LastLogin date field (from my original post).
After adding the user (call it User_Model) and the user view (call it UserView_Model) to my EF model, I created a wrapper class (call it User_Wrapper) around the User_Model and added an additional DateTime property for LastLogin.
I modifed the User_Wrapper class to fetch from the UserView_Model, and then populate the underlying User_Model by reflecting over all the properties shared between the User_Model and UserView_Model. Finally, I set the User_Wrapper.LastLogin property based on the fetched User_View.
All other functions (Create,Update,Delete...) operate on the User_Model. Only the Fetch uses the UserView_Model.
What did all this do? I now only have one database call to populate a single User_Wrapper or a List<User_Wrapper>.
The drawbacks? I guess that because my UserView_Model does not have any associated relationships, I would not be able to do any eager loading using the EF ObjectContext. Fortunately, in my situation, I don't find that to be an issue.
Is there a better way?
I just had a situation where I needed count properties for two related entities without loading the collections. One thing I found out is that you need to have MultipleActiveResultSets=True in the connection string to avoid an exception being thrown on the ObjectMaterialized eventhandler when querying other entitycollections.
RIA Services is returning a list of Entities that won't allow me to add new items. Here are what I believe to be the pertinent details:
I'm using the released versions of Silverlight 4 and RIA Services 1.0 from mid-April of 2010.
I have a DomainService with a query method that returns List<ParentObject>.
ParentObject includes a property called "Children" that is defined as List<ChildObject>.
In the DomainService I have defined CRUD methods for ParentObject with appropriate attributes for the Query, Delete, Insert, and Update functions.
The ParentObject class has an Id property marked with the [Key] attribute. It also has the "Children" property marked with the attributes [Include], [Composition], and [Association("Parent_Child", "Id",
"ParentId")].
The ChildObject class has an Id marked with the [Key] attribute as well as a foreign key, "ParentId", that contains the Id of the parent.
On the client side, data is successfully returned and I assign the results of the query to a PagedCollectionView like this:
_pagedCollectionView = new PagedCollectionView(loadOperation.Entities);
When I try to add a new ParentObject to the PagedCollectionView like this:
ParentObject newParentObject = (ParentObject)_pagedCollectionView.AddNew();
I get the following error:
" 'Add New' is not allowed for this view."
On further investigation, I found that _pagedCollectionView.CanAddNew is "false" and cannot be changed because the property is read-only.
I need to be able to add and edit ParentObjects (with their related children, of course) to the PagedCollectionView. What do I need to do?
I was just playing around with a solution yesterday and feel pretty good about how it works. The reason you can't add is the source collection (op.Entities) is read-only. However, even if you could add to the collection, you'd still want to be adding to the EntitySet as well. I created a intermediate collection that takes care of both these things for me.
public class EntityList<T> : ObservableCollection<T> where T : Entity
{
private EntitySet<T> _entitySet;
public EntityList(IEnumerable<T> source, EntitySet<T> entitySet)
: base(source)
{
if (entitySet == null)
{
throw new ArgumentNullException("entitySet");
}
this._entitySet = entitySet;
}
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
if (!this._entitySet.Contains(item))
{
this._entitySet.Add(item);
}
}
protected override void RemoveItem(int index)
{
T item = this[index];
base.RemoveItem(index);
if (this._entitySet.Contains(item))
{
this._entitySet.Remove(item);
}
}
}
Then, I use it in code like this.
dataGrid.ItemsSource = new EntityList<Entity1>(op.Entities, context.Entity1s);
The only caveat is this collection does not actively update off the EntitySet. If you were binding to op.Entities, though, I assume that's what you'd expect.
[Edit]
A second caveat is this type is designed for binding. For full use of the available List operation (Clear, etc), you'd need to override a few of the other methods to write-though as well.
I'm planning to put together a post that explains this a little more in-depth, but for now, I hope this is enough.
Kyle
Here's a workaround which I am using:
Instead of using the AddNew, on your DomainContext you can retrieve an EntitySet<T> by saying Context.EntityNamePlural (ie: Context.Users = EntitySet<User> )
You can add a new entity to that EntitySet by calling Add() and then Context.SubmitChanges() to send it to the DB. To reflect the changes on the client you will need to Reload (Context.Load())
I just made this work about 15mins ago after having no luck with the PCV so I am sure it could be made to work better, but hopefully this will get you moving forward.
For my particular situation, I believe the best fit is this (Your Mileage May Vary):
Use a PagedCollectionView (PCV) as a wrapper around the context.EntityNamePlural (in my case, context.ParentObjects) which is an EntitySet. (Using loadOperation.Entities doesn't work for me because it is always read-only.)
_pagedCollectionView = new PagedCollectionView(context.ParentObjects);
Then bind to the PCV, but perform add/delete directly against the context.EntityNamePlural EntitySet. The PCV automatically syncs to the changes done to the underlying EntitySet so this approach means I don't need to worry about sync issues.
context.ParentObjects.Add();
(The reason for performing add/delete directly against the EntitySet instead of using the PCV is that PCV's implementation of IEditableCollectionView is incompatible with EntitySet causing IEditableCollectionView.CanAddNew to be "false" even though the underlying EntitySet supports this function.)
I think Kyle McClellan's approach (see his answer) may be preferred by some because it encapsulates the changes to the EntitySet, but I found that for my purposes it was unneccessary to add the ObservableCollection wrapper around loadOperation.Entities.
Many thanks to to Dallas Kinzel for his tips along the way!
I have a User table and a ClubMember table in my database. There is a one-to-one mapping between users and club members, so every time I insert a ClubMember, I need to insert a User first. This is implemented with a foreign key on ClubMember (UserId REFERENCES User (Id)).
Over in my ASP.NET MVC app, I'm using LinqToSql and the Repository Pattern to handle my persistence logic. The way I currently have this implemented, my User and ClubMember transactions are handled by separate repository classes, each of which uses its own DataContext instance.
This works fine if there are no database errors, but I'm concerned that I'll be left with orphaned User records if any ClubMember insertions fail.
To solve this, I'm considering switching to a single DataContext, which I could load up with both inserts then call DataContext.SubmitChanges() only once. The problem with this, however, is that the Id for User is not assigned until the User is inserted into the database, and I can't insert a ClubMember until I know the UserId.
Questions:
Is it possible to insert the User into the database, obtain the Id, then insert the ClubMember, all as a single transaction (which can be rolled back if anything goes wrong with any part of the transaction)? If yes, how?
If not, is my only recourse to manually delete any orphaned User records that get created? Or is there a better way?
You can use System.Transactions.TransactionScope to perform this all in an atomic transaction, but if you are using different DataContext instances, it will result in a distributed transaction, which is probably not what you really want.
By the sounds of it, you're not really implementing the repository pattern correctly. A repository should not create its own DataContext (or connection object, or anything else) - these dependencies should be passed in via a constructor or public property. If you do this, you'll have no problem sharing the DataContext:
public class UserRepository
{
private MyDataContext context;
public UserRepository(MyDataContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
}
public void Save(User user) { ... }
}
Use the same pattern for ClubMemberRepository (or whatever you call it), and this becomes trivial:
using (MyDataContext context = new MyDataContext())
{
UserRepository userRep = new UserRepository(context);
userRep.Save(user);
ClubMemberRepository memberRep = new ClubMemberRepository(context);
memberRep.Save(member);
context.SubmitChanges();
}
Of course, even this is a little bit iffy. If you have a foreign key in your database, then you shouldn't even need two repositories, because Linq to SQL manages the relationship. The code to create should simply look like this:
using (MyDataContext context = new MyDataContext())
{
User user = new User();
user.Name = "Bob";
user.ClubMember = new ClubMember();
user.ClubMember.Club = "Studio 54";
UserRepository userRep = new UserRepository(context);
userRep.Save(user);
context.SubmitChanges();
}
Don't fiddle with multiple repositories - let Linq to SQL handle the relationship for you, that's what ORMs are for.
Yes, you can do it in a single transaction. Use the TransactionScope object to begin and commit the transaction (and rollback if there is an error of course)