- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
WCF and IsNew property
Joined: 25-Jan-2009
Sorry to keep posting, but I think perhaps this might be a threading issue.
I cannot replicate the problems using the test app even though I call the same methods in the same order with the same data. But in my forms app I am fetching entities on another thread.
I have yet to prove this - as I need to do a lot more testing - but this is one possibility.....
My services are not multi-threaded so if this is a problem it can be fixed in the GUI.
Joined: 25-Jan-2009
I've spent quite a bit of time on this now, and one thing I can't quite figure out is under what conditions does LLBLGen determine whether to perform an insert or an update? Reading the documentation and the various threads it seems setting the entity's IsNew flag to false should be enough to perform an update. Well this is not the case for me.
I acknowledge that in my client I am using threading so I could have an issue, except when I do a fetch, modify, update this is all on the same thread, so i'm not convinced. But in any case when I pass the entity to the service for persistence this is on another machine, so threading has nothing to do with it. My service is implemented as a singleton and during testing I have no other operations going on.
This is my service's save method:
public ProductEntity SaveProduct(ProductEntity productEntity, bool refetchAfter)
{
using (DataAccessAdapter adapter = GetDataAccessAdapter())
{
try
{
productEntity = ValidateProduct(productEntity);
LogProductEntity(productEntity);
adapter.SaveEntity(productEntity, refetchAfter);
}
catch (ORMQueryExecutionException qryex)
{
logging.Log.Error(qryex.Message);
logging.Log.Error(qryex.QueryExecuted);
throw new Exception("Failed to save product entity", qryex);
}
catch (Exception ex)
{
logging.Log.Error(ex.Message);
throw new Exception("Failed to save product entity", ex);
}
}
return productEntity;
}
This is the validate method:
private ProductEntity ValidateProduct(ProductEntity product)
{
if (product.IsDirty && !product.IsNew)
{
product.LastModifiedOn = DateTime.Now;
}
// No need for further checks as the entity already exists
if (!product.IsNew) return product;
if (string.IsNullOrEmpty(product.FirstEnteredBy))
{
product.FirstEnteredBy = defaultUser;
}
if (string.IsNullOrEmpty(product.LastModifiedBy))
{
product.LastModifiedBy = defaultUser;
}
return product;
}
This is the logging method:
private void DebugLogProductEntity(ProductEntity productEntity)
{
logging.Log.Debug("===============================================================================");
logging.Log.DebugFormat("Entity IsNew: {0}", productEntity.IsNew);
logging.Log.DebugFormat("Entity IsDirty: {0}", productEntity.IsDirty);
logging.Log.DebugFormat("Entity State: {0}", productEntity.Fields.State);
logging.Log.Debug("Field Value IsChanged");
logging.Log.Debug("-------------------------------------------------------------------------------");
logging.Log.Debug(GetDebugProduct(productEntity, "ProductNumber"));
logging.Log.Debug(GetDebugProduct(productEntity, "AccountNumber"));
logging.Log.Debug(GetDebugProduct(productEntity, "AccountDescription"));
logging.Log.Debug(GetDebugProduct(productEntity, "ExaltDescription"));
logging.Log.Debug(GetDebugProduct(productEntity, "Length"));
logging.Log.Debug(GetDebugProduct(productEntity, "Width"));
logging.Log.Debug(GetDebugProduct(productEntity, "Height"));
logging.Log.Debug(GetDebugProduct(productEntity, "FirstEnteredOn"));
logging.Log.Debug(GetDebugProduct(productEntity, "FirstEnteredBy"));
logging.Log.Debug(GetDebugProduct(productEntity, "LastModifiedOn"));
logging.Log.Debug(GetDebugProduct(productEntity, "LastModifiedBy"));
}
private string GetDebugProduct(ProductEntity productEntity, string fieldName)
{
return string.Format(
"{0,-20}{1,-50}{2}",
fieldName,
productEntity.Fields[fieldName].CurrentValue,
productEntity.Fields[fieldName].IsChanged);
}
The first test creates a new entity in the client and passes it to the service's save method. This is the log output:
===============================================================================
Entity IsNew: True
Entity IsDirty: True
Entity State: New
Field Value IsChanged
-------------------------------------------------------------------------------
ProductNumber 5039036029704 True
AccountNumber 67 True
AccountDescription Young Riders Season 1 [DVD] [1989] True
ExaltDescription Young Riders Season 1 [DVD] [1989] True
Length 181 True
Width 138 True
Height 15 True
FirstEnteredOn False
FirstEnteredBy System True
LastModifiedOn False
LastModifiedBy System True
The client then receives the re-fetched entity and changes the description field only. No flags are set in the client, only the two description fields. The client then calls the service's save method again:
===============================================================================
Entity IsNew: False
Entity IsDirty: True
Entity State: Fetched
Field Value IsChanged
-------------------------------------------------------------------------------
ProductNumber 5039036029704 False
AccountNumber 67 False
AccountDescription **Some new description** True
ExaltDescription **Some new description** True
Length 181 False
Width 138 False
Height 15 False
FirstEnteredOn 27/05/2010 10:38:22 AM False
FirstEnteredBy System False
LastModifiedOn 27/05/2010 10:38:44 AM True
LastModifiedBy System False
Note that the IsNew is now false and the entity state flag has changed itself. And the field's IsChanged flags all reflect changes that either the client or the service's validate methods have made.
But when the adapter.SaveEntity method is called the exception is raised the second time round:
An exception was caught during the execution of an action query: The field
'tblProducts.LastModifiedBy' cannot contain a Null value because the Required property
for this field is set to True. Enter a value in this field.. Check InnerException,
QueryExecuted and Parameters of this exception to examine the cause of this exception.
Query: INSERT INTO [tblProducts] ([ProductNumber], [AccountNumber], [AccountProductNumber],
[AccountDescription], [ExaltDescription], [GrossWeight], [Length], [Width], [Height],
[IsActive], [ProductTypeID])
VALUES (@ProductNumber, @AccountNumber, @AccountProductNumber, @AccountDescription,
@ExaltDescription, @GrossWeight, @Length, @Width, @Height, @IsActive, @ProductTypeId)
Parameter: @ProductNumber : String. Length: 20. Precision: 0. Scale: 0. Direction: Input. Value: "5039036029704".
Parameter: @AccountNumber : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 67.
Parameter: @AccountProductNumber : String. Length: 25. Precision: 0. Scale: 0. Direction: Input. Value: "5039036029704".
Parameter: @AccountDescription : String. Length: 75. Precision: 0. Scale: 0. Direction: Input. Value: "Young Riders Season 1 [DVD] [1989]".
Parameter: @ExaltDescription : String. Length: 75. Precision: 0. Scale:0. Direction: Input. Value: "Young Riders Season 1 [DVD] [1989]".
Parameter: @GrossWeight : Double. Length: 0. Precision: 15. Scale: 0. Direction: Input. Value: 0.082.
Parameter: @Length : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 181.
Parameter: @Width : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 138.
Parameter: @Height : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 15.
Parameter: @IsActive : Boolean. Length: 2. Precision: 0. Scale: 0. Direction: Input. Value: True.
Parameter: @ProductTypeId : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 2.
So firstly, why are we trying to insert when the IsNew flag is false, and secondly why are we trying to persist old data. The two description fields contain the original data values and not the new values as shown in the log. Does the entity store internally the old data for comparison? So why is the new data not used?
Joined: 25-Jan-2009
Right, some good news....
Changing a related entity was screwing everything up. The product entity has a related product type, such as books, dvds etc. In the client I have an MRUEdit control which retrieves a list of these types. When I come to edit the product, the code performs a lookup and retrieves the product type entity and assigns this to the product entity. Something like this:
myProductEntity.ProductType = proxy.GetProductType(productTypeName);
So even though I don't change the product type, by fetching a new product type entity and assigning it to the product's related entity property this broke something and so the adapter's save method treat the product entity as new somehow...
So for the time being I now perform a separate lookup on the product type and set the product type id of the product entity instead.
myProductEntity.ProductTypeId = proxy.GetProductType(productTypeName).Id;
So far this is working well.
Thanks.
is 'IsNew' false BEFORE and true right after myProductEntity.ProductType = proxy.GetProductType(productTypeName);
?
That would mean something is seriously wrong as IsNew is never changed after the ctor has ran. Assigning an entity like in the statement above also resets the FK field in product to the pk value of producttype. This can't make Product become 'new'
Are you setting/resetting IsNew along the way?
Joined: 25-Jan-2009
If I can briefly describe the application and sequence of events, this will hopefully make things a bit clearer.
I use a barcode scanner to scan items with a UPC or EAN barcode. When a barcode is scanned into a textbox I perform a lookup for the product with a PK of the barcode.
searchProduct = proxy.GetProduct(upc);
This method uses a prefetch to include both the product and the related product type entity.
If the method returns null then I use Amazon web services to fetch the data including description, dimensions and an image.
I then call my service's save method to initially persist the new entity. I then add some of the entity's fields to a grid control before disposing of the entity. You can see the logging for the first save operation in my earlier email.
Now a user can go and edit some of this data so they double-click the grid which loads up a dialog. This again calls the above GetProduct method which now should return the created entity. The dialog is populated and the user can change some stuff.
When the user is finished he clicks the save button. I still have the fetched entity at this point and so I can simply update any fields that have changed in the dialog. In the earlier example I just changed the description field.
In addition I allowed the user to assign/change the product type. So I fetched a collection of product type entities and filled a lookup control with the type description field of each. When the user selected one I performed a lookup to fetch the product type entity and assigned it to the product entity.
Finally I called the service's save method to persist the modified entity. See second log.
It was this last part that screwed things up. At no point do I either explicitly set IsNew or Fields.State on any entity, but anyway the state flags shown in the example appear to be correct. Because I am modifying a previously fetched entity I would expect IsNew to be false just prior to persisting, which it is. But LLBLGen tries to perform an INSERT query anyway?
So now as a fix I set the ProductTypeId of the product entity with the PK of the lookup product type entity. I must say though, I don't really understand why this should cause a problem. Possibly if the related entity is not the original entity then the parent is treat as new or something?
Joined: 25-Jan-2009
..and finally, today I cannot replicate the issues of the last few days. Both client and test projects utilising local and remote services work perfectly. I've even tried old code which used to break immediately and this is also working fine.
Go figure.....
I think i'll take Wednesdays and Thursdays off in future
Regards.