During a save action an entity's update action failed.

Posts   
 
    
JanRHansen
User
Posts: 37
Joined: 02-Jan-2019
# Posted on: 22-Feb-2020 10:11:19   

Hi

So, I have an ASP.NET website with a simple ajax-communication to edit an item. Upon clicking Save, javascript collects values and submits, and the following happens on the server side:

// deserialize into c# dto class PurchaseOrderDto purchaseOrderDto = JsonConvert.DeserializeObject<PurchaseOrderDto>(ajaxRequestCommand.CommandObject.ToString());

// extension method on dto class that returns an LLBLGen entity PurchaseOrderEntity purchaseOrder = purchaseOrderDto.FromDto();

// save in database using (var adapter = new DataAccessAdapter(SessionHelper.ConnectionString)) { adapter.SaveEntity(purchaseOrder, true); }

The .FromDto() extension methods looks like this: public static PurchaseOrderEntity FromDto(this PurchaseOrderDto purchaseOrderDto) { if (purchaseOrderDto == null) { return null; } var purchaseOrder = new PurchaseOrderEntity { SupplierOrderNumber = purchaseOrderDto.SupplierOrderNumber, ... more left out for readability };

// if the DTO has an Id value, do an UPDATE, otherwise it is an INSERT
if (purchaseOrderDto.Id.HasValue)
{
    purchaseOrder.Id = purchaseOrderDto.Id.Value;
}
purchaseOrder.IsNew = !purchaseOrderDto.Id.HasValue;
return purchaseOrder;

}

My intention is to populate the LLBLGen entity, only setting the Id when relevant, and via the .IsNew indicate whether it should be updated or inserted.

For inserts and updates with changes, it works perfect - but if I click save on the same data twice, then I get the error "During a save action an entity's update action failed.". From what I can dig out of these forum posts, it is due to the number of rows changed being 0. While this is technically correct, as nothing changed - is there a way to handle that specific situation?

I have looked into the "Derived models" documentation, but found it to be too confusing/elaborate (maybe I was just too tired) without actually illustrationg how I go about generating the DTOs (where do I define them or generate them?), so I decided to just "roll my own". I only need it for few of my entities, so that is ok. However, if utilizing the derived models is the proper way of doing it and will prevent the above problem, do you then have a more end-to-end-like example other than https://www.llblgen.com/Documentation/5.6/Derived%20Models/dto_llblgen.htm#examples

Thanks in advance!

/Jan

MySQL version 5.6.43 LLBLGen version 5.6 (5.6.1) RTM DevArt version 8.16

JanRHansen
User
Posts: 37
Joined: 02-Jan-2019
# Posted on: 22-Feb-2020 17:02:07   

Figured it out!

For those who can't find it either, right click on "Derived Models" in the Project explorer, and follow the wizard.

Then I was able to do what the documentation illustrated, even with unchanged data:

var purchaseOrderDto = JsonConvert.DeserializeObject<PurchaseOrderDto>(ajaxRequestCommand.CommandObject.ToString());

using (var adapter = new DataAccessAdapter(SessionHelper.NhldbConnectionString)) { // fetch from database, or create new var metaData = new LinqMetaData(adapter); var purchaseOrder = metaData.PurchaseOrder.FirstOrDefault( PurchaseOrderDtoPersistence.CreatePkPredicate(purchaseOrderDto)) ?? new PurchaseOrderEntity();

// update entity with values of dto
purchaseOrder.UpdateFromPurchaseOrderDto(purchaseOrderDto);

// save
adapter.SaveEntity(purchaseOrder);

}

So, all is well and working.

However, having seen the generated Dto model, it doesn't look different from my own. The change seem to be that I now new() the object if its an insert, or update an existing from the database, hence having the IsNew, IsDirty and maybe also IsChanged pr. field correctly managed. It just requires a fetch first, couldn't that be avoided?

Also, I think you have a (very minor) "typo" in the documentation - at least the following wasn't quite coherent when I tried to understand it simple_smile

using(var adapter = new DataAccessAdapter()) { var metaData = new LinqMetaData(adapter); // fetch the entity from the DB var entity = metaData.Customer .FirstOrDefault(CustomerOrderPersistence.CreatePkPredicate(dto)); <--- should probably be CustomerOrderDtoPersistence, as that is the typical naming of the Dto object and hence of the generated code. if(entity==null) { // doesn't exist, so create a new instance. entity = new CustomerEntity(); <--- should probably be CustomerOrderEntity } // update entity with values of dto entity.UpdateFromCustomerOrder(dto); // save entity adapter.SaveEntity(entity); }

just a detail - and it might as well be me who doesn't see a bigger picture here simple_smile

/Jan

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39612
Joined: 17-Aug-2003
# Posted on: 24-Feb-2020 09:55:01   

JanRHansen wrote:

Figured it out!

For those who can't find it either, right click on "Derived Models" in the Project explorer, and follow the wizard.

Then I was able to do what the documentation illustrated, even with unchanged data:

var purchaseOrderDto = JsonConvert.DeserializeObject<PurchaseOrderDto>(ajaxRequestCommand.CommandObject.ToString());

using (var adapter = new DataAccessAdapter(SessionHelper.NhldbConnectionString)) { // fetch from database, or create new var metaData = new LinqMetaData(adapter); var purchaseOrder = metaData.PurchaseOrder.FirstOrDefault( PurchaseOrderDtoPersistence.CreatePkPredicate(purchaseOrderDto)) ?? new PurchaseOrderEntity();

// update entity with values of dto
purchaseOrder.UpdateFromPurchaseOrderDto(purchaseOrderDto);

// save
adapter.SaveEntity(purchaseOrder);

}

So, all is well and working.

However, having seen the generated Dto model, it doesn't look different from my own. The change seem to be that I now new() the object if its an insert, or update an existing from the database, hence having the IsNew, IsDirty and maybe also IsChanged pr. field correctly managed. It just requires a fetch first, couldn't that be avoided?

No it can't as it can't know the entity already exists unless it checks the database. LLBLGen Pro currently doesn't support an upsert in the framework so you have to do the fetch first. The read/write dto preset will generate the update method for you so all you need to do is fetch the entity and call the method.

Also, I think you have a (very minor) "typo" in the documentation - at least the following wasn't quite coherent when I tried to understand it simple_smile

using(var adapter = new DataAccessAdapter()) { var metaData = new LinqMetaData(adapter); // fetch the entity from the DB var entity = metaData.Customer .FirstOrDefault(CustomerOrderPersistence.CreatePkPredicate(dto)); <--- should probably be CustomerOrderDtoPersistence, as that is the typical naming of the Dto object and hence of the generated code. if(entity==null) { // doesn't exist, so create a new instance. entity = new CustomerEntity(); <--- should probably be CustomerOrderEntity } // update entity with values of dto entity.UpdateFromCustomerOrder(dto); // save entity adapter.SaveEntity(entity); }

just a detail - and it might as well be me who doesn't see a bigger picture here simple_smile

The 'Dto' suffix is configurable so it doesn't have to be there. The default is an empty string, so dto's aren't suffixed with 'Dto' by default, it's up to you whether you want to have things suffixed. simple_smile

The CustomerEntity is correct, as there are two things in play: you have entity instances which is what you use to work with the database, and you have dto's which are basically buckets of data created from projections of entity instances (see them as the result of a select query on the entities, so you can have e.g. denormalized data in that set). To update a Customer instance's data, we need to fetch it from the database and if it doesn't exist we have to instantiate a new CustomerEntity instance. We then update the fields in the entity object with the values in the dto using the method generated by the templates. Which is a bit like your FromDto method.

We then save the entity object again. If there were changed fields in the existing one, we update these, and if it didn't exist we'll insert a new one, this is transparent. If no field was changed on an existing entity, it's a no-op and nothing is saved.

Frans Bouma | Lead developer LLBLGen Pro