Hi all,
I get the DuplicateKeyException “Cannot add an entity with a key that is already in use.” whether or not I have transaction scope setup on execute and update methods. Here is an excerpt from my code:
protected override void DataPortal_Execute()
{
using (var mgr = ContextManager<DalLinq.MyDBDataContext>
.GetManager(DalLinq.Database.MyDB))
{
var _part = BusinessObjects.Part.GetPart(_no, _rev);
_part.Description = "Test update";
_part.Save(); <-- Error happens when calling this, see below...
protected override void DataPortal_Update()
{
bool cancel = false;
OnUpdating(ref cancel);
if (cancel) return;
using (var mgr = ContextManager<DalLinq.MyDBDataContext>
.GetManager(DalLinq.Database.MyDB))
{
var data = new DalLinq.Part()
{
PartNo = ReadProperty<string>(NoProperty),
PartRev = ReadProperty<int>(RevProperty)
};
data.LastChanged = _lastChanged;
mgr.DataContext.Part.Attach(data); <-- Error happens here
I'm pretty new to L2S and I can't see where I'm going wrong. Anyone any ideas? I was under the impression the above should work based on Rocky's reply in the following post:
http://forums.lhotka.net/forums/thread/23484.aspx
Thanks,
Colm
Thanks Andy,
PartNo and PartRev are the key fields and are populated with proper values. It also seems mgr.DataContext.Part is populated with all the records in the database, well when I call the Count() method anyway - unless this is causing the database to be queried??
The DataPortal_Update portion of the code works fine when the "using (var mgr = ContextManager<DalLinq.MyDBDataContext>.GetManager(DalLinq.Database.MyDB))" line is removed from the DataPortal_Execute method. In this instance the mgr.DataContext.Part.Count() method returns same count as above...so now I'm really confused as I would have expected the same DuplicateKeyException.
Removal of the using ContextManager command in the DataPortal_Execute method presumably invalidates the Transaction logic within the CSLA framework so is not an option.
Any other ideas?
Thanks,
Colm
Thanks for your replies Andy. That code is generated by the Codesmith CSLA templates so not sure if I can change that or not, from the point of view of wanting to go down that road or not...
Can anyone else shed any light on the subject?
Thanks,
Colm
Hi all,
The following code is a Role BO generated using Codesmith with the CSLAcontrib template against the ProjectTracker database. I've included an UpdateName command to fully show what is going on here. Andy's solution above works and proves that the transaction can be persisted across BO's as Rocky suggests but his solution requires a trip to the database which is essentially redundant and doubles the amount of database hits.
Including the using statement in the DataPortal_Execute causes an error so I can only assume that there's something happening within the ContextManager that is causing this behaviour.
Perhaps I am going wrong in my use of the BO's or not doing something I should be. Any help would be greatly appreciated.
Thanks,
Colm
using System;
using
System.Linq;using
Csla;using
Csla.Data;using
Csla.Security;using
Csla.Validation;namespace
CSLAExample{
[
Serializable()] public partial class Role : Csla.BusinessBase<Role>{
#region
Business Properties and Methods //register properties private static PropertyInfo<int> IdProperty = RegisterProperty<int>(typeof(Role), new PropertyInfo<int>("Id")); private static PropertyInfo<string> NameProperty = RegisterProperty<string>(typeof(Role), new PropertyInfo<string>("Name")); private byte[] _lastChanged;[System.ComponentModel.
DataObjectField(true, false)] public int Id{
get { return GetProperty<int>(IdProperty); }}
public string Name{
get { return GetProperty<string>(NameProperty); } set { SetProperty<string>(NameProperty, value); }}
#endregion
//Business Properties and Methods#region
Validation Rules protected override void AddBusinessRules(){
// // Name //ValidationRules.AddRule(
CommonRules.StringRequired, NameProperty);ValidationRules.AddRule(
CommonRules.StringMaxLength, new CommonRules.MaxLengthRuleArgs(NameProperty, 50));AddCustomRules();
}
partial void AddCustomRules();#endregion
//Validation Rules#region
Factory Methods public static Role NewRole(int id){
return DataPortal.Create<Role>(new SingleCriteria<Role, int>(id));}
public static Role GetRole(int id){
return DataPortal.Fetch<Role>(new SingleCriteria<Role, int>(id));}
public static void DeleteRole(int id){
DataPortal.Delete(new SingleCriteria<Role, int>(id));}
private Role(){
/* require use of factory method */ }#endregion
//Factory Methods#region
Data Access#region
Data Access - Create[
RunLocal] private void DataPortal_Create(SingleCriteria<Role, int> criteria){
bool cancel = false;OnCreating(criteria,
ref cancel); if (cancel) return;LoadProperty<
int>(IdProperty, criteria.Value);ValidationRules.CheckRules();
OnCreated();
}
partial void OnCreating(SingleCriteria<Role, int> criteria, ref bool cancel); partial void OnCreated();#endregion
//Data Access - Create#region
Data Access - Fetch private void DataPortal_Fetch(SingleCriteria<Role, int> criteria){
bool cancel = false;OnFetching(criteria,
ref cancel); if (cancel) return; using (var mgr = ContextManager<DalLinq.PTRACKERDataContext>.GetManager(
Database.PTRACKER)){
var data = mgr.DataContext.Roles.Single(r => r.Id == criteria.Value);
OnMemberLoading(data);
LoadProperty<
int>(IdProperty, data.Id);LoadProperty<
string>(NameProperty, data.Name);_lastChanged = data.LastChanged.ToArray();
OnMemberLoaded();
ValidationRules.CheckRules();
}
//usingOnFetched();
}
partial void OnFetching(SingleCriteria<Role, int> criteria, ref bool cancel); partial void OnFetched(); partial void OnMemberLoading(DalLinq.Roles data); partial void OnMemberLoaded();#endregion
//Data Access - Fetch#region
Data Access - Insert protected override void DataPortal_Insert(){
bool cancel = false;OnInserting(
ref cancel); if (cancel) return; using (var mgr = ContextManager<DalLinq.PTRACKERDataContext>.GetManager(
Database.PTRACKER)){
var data = new DalLinq.Roles();OnMemberReading(data);
data.Id = ReadProperty<
int>(IdProperty);data.Name = ReadProperty<
string>(NameProperty);OnMemberRead();
mgr.DataContext.Roles.InsertOnSubmit(data);
mgr.DataContext.SubmitChanges();
_lastChanged = data.LastChanged.ToArray();
}
//usingOnInserted();
}
partial void OnInserting(ref bool cancel); partial void OnInserted(); partial void OnMemberReading(DalLinq.Roles data); partial void OnMemberRead();#endregion
//Data Access - Insert#region
Data Access - Update protected override void DataPortal_Update(){
bool cancel = false;OnUpdating(
ref cancel); if (cancel) return; using (var mgr = ContextManager<DalLinq.PTRACKERDataContext>.GetManager(
Database.PTRACKER)){
var data = new DalLinq.Roles(){
Id = ReadProperty<
int>(IdProperty)};
data.LastChanged = _lastChanged;
mgr.DataContext.Roles.Attach(data);
OnMemberReading(data);
if (IsSelfDirty){
data.Name = ReadProperty<
string>(NameProperty);}
OnMemberRead();
mgr.DataContext.SubmitChanges();
if (IsSelfDirty){
_lastChanged = data.LastChanged.ToArray();
}
}
//usingOnUpdated();
}
partial void OnUpdating(ref bool cancel); partial void OnUpdated();#endregion
//Data Access - Update
#region
Data Access - Delete protected override void DataPortal_DeleteSelf(){
bool cancel = false;OnSelfDeleting(
ref cancel); if (cancel) return; using (var mgr = ContextManager<DalLinq.PTRACKERDataContext>.GetManager(
Database.PTRACKER)){
OnMemberSelfDeleting();
var data = new DalLinq.Roles(){
Id = ReadProperty<
int>(IdProperty)};
data.LastChanged = _lastChanged;
mgr.DataContext.Roles.Attach(data);
mgr.DataContext.Roles.DeleteOnSubmit(data);
OnMemberSelfDeleted();
mgr.DataContext.SubmitChanges();
}
//usingOnSelfDeleted();
}
partial void OnSelfDeleting(ref bool cancel); partial void OnSelfDeleted(); partial void OnMemberSelfDeleting(); partial void OnMemberSelfDeleted(); private void DataPortal_Delete(SingleCriteria<Role, int> criteria){
bool cancel = false;OnDeleting(criteria,
ref cancel); if (cancel) return; using (var mgr = ContextManager<DalLinq.PTRACKERDataContext>.GetManager(
Database.PTRACKER)){
var data = mgr.DataContext.Roles.Single(r => r.Id == criteria.Value);
OnMemberDeleting(data);
mgr.DataContext.Roles.DeleteOnSubmit(data);
OnMemberDeleted();
mgr.DataContext.SubmitChanges();
}
//usingOnDeleted();
}
partial void OnDeleting(SingleCriteria<Role, int> criteria, ref bool cancel); partial void OnDeleted(); partial void OnMemberDeleting(DalLinq.Roles data); partial void OnMemberDeleted();#endregion
//Data Access - Delete#endregion
//Data Access#region
UpdateName public static bool UpdateName(int id, string newName){
return UpdateNameCommand.UpdateName(id, newName);}
[
Serializable()] private class UpdateNameCommand : CommandBase{
private int _id; private string _newName; public bool UpdateSuccessful { get; internal set; } public static bool UpdateName(int id, string newName){
UpdateNameCommand result = null;result =
DataPortal.Execute<UpdateNameCommand>(new UpdateNameCommand(id, newName)); return result.UpdateSuccessful;}
private UpdateNameCommand(int id, string newName){
_id = id;
_newName = newName;
}
protected override void DataPortal_Execute(){
using (var ctx = ContextManager<DalLinq.PTRACKERDataContext>.GetManager(
Database.PTRACKER)){
Role _role = Role.GetRole(_id);_role.Name = _newName;
_role.Save();
}
}
}
#endregion
}
}
Yeah, the CRUD methods here are generated by the CSLAContrib template. I've added the UpdateNameCommand object, that inherits from CommandBase, and this is what I'm particularly interested in. This is an example of updating the BO in the DataPortal_Execute method and calling the BO.Save method. Am I correct in doing this? I am trying to use the BO to update the database in an attempt to only have to code update logic once and re-use in both the front-end and DAL.
Theoretically this code works when I don't include the using contextmanager statement in the DataPortal_Execute method but that means I need to use DTS and this performance hit is not acceptable for our users.
Your solution above, Andy, was to use "var part = mgr.DataContext.Part.Where( p => p.PartNo == partNo && p.PartRev == partRev )" in the DataPortal_Update method which stops the duplicate key exception and essentially makes the code work. The downfall of this solution is that this is a redundant roundtrip to the database, just to populate DataContext.Part with the DalLinq.Part I wish to update.
So I need Rocky (or someone) to confirm that I am indeed correct using the BO within the DataPortal_Execute method to update the database and I'm hoping that they can tell me there's an issue with doing this that needs some property set or if there is a bug that it will be fixed in 3.6.
Thanks,
Colm
Hi Andy,
No, I haven't verified that as yet - can I do that with SQL Express installed and Linq logs, or do I need the full version of SQL Server installed?
As I'm new to both Linq and CSLA, I'm not sure where the problem lies but I was querying here because it's when the BO update is done inside a using CSLA contextmanager block that the error occurs and I thought that this would have been a common problem with an easy solution.
Thanks,
Colm
No, profiler just comes with full version.
I think the primary key is setup correctly because the same code works without being included in the using contextmanager block...so being in that using block is causing the object to exist in memory - so that's what we need to get to the bottom of.
Thanks,
Colm
There is an open source profiler for Sql Express (just google "Sql Express Profiler"). It does most of the tasks that the full version does.
Cheers Ian,
Andy, when you put it like that it suddenly becomes clear. The GetRole will be adding the Role object in the DataPortal_Fetch and then the save will be calling DataPortal_Update, which will try and add it again and therefore produce the error.
Role _role = Role.GetRole(_id);
_role.Name = _newName;
_role.Save();
Thanks for your help.
Colm
Copyright (c) Marimer LLC