I have, errr had a working wpf application that manipulates database info (using Entity Framework, database first).
The structure of the data is 4 tables of finance info (all 1:1 mapped to the main table of the 5), with a couple of lookup tables with foreign key refs in the main table.
I added a table (another 1:1 mapping to the main table) in SqlServer and then ran the 'Update Model from Database...' wizard to add the new table to the model. Everything looks alright in the .edmx file, including the '0..1' relationship link.
However, when I try to save, I am receiving a 'Violation of Unique Constraint' error.
My creation code:
private void AddNewStatementsQuery(LGFinanceEntities lGFinanceEntities)
{
StatementsMain newStatement = StatementsMain.CreateStatementsMain(9999, this.LocalGovt.StakeholderID, 161, this.Year.FinancialYearID);
StatementsIncome newInc = StatementsIncome.CreateStatementsIncome(newStatement.StatementsMainID);
StatementsNote newNote = StatementsNote.CreateStatementsNote(newStatement.StatementsMainID);
StatementsRSSFinPos newRSSFinPos = StatementsRSSFinPos.CreateStatementsRSSFinPos(newStatement.StatementsMainID);
StatementsSurplusDeficit newSurplusDeficit = StatementsSurplusDeficit.CreateStatementsSurplusDeficit(newStatement.StatementsMainID);
lGFinanceEntities.StatementsMains.Context.AddObject("StatementsMains", newStatement);
lGFinanceEntities.StatementsMains.Context.AddObject("StatementsIncomes", newInc);
lGFinanceEntities.StatementsMains.Context.AddObject("StatementsNotes", newNote);
lGFinanceEntities.StatementsMains.Context.AddObject("StatementsRSSFinPos", newRSSFinPos);
lGFinanceEntities.StatementsMains.Context.AddObject("StatementsSurplusDeficit", newSurplusDeficit);
if (lGFinanceEntities.SaveChanges() != 1) // this is causing the exception
{
MessageBox.Show("Error. New Statements not created", "Database Error");
}
}
Prior to adding the new table, the above code was working. The only change was the addition of the lines:
StatementsSurplusDeficit newSurplusDeficit =
StatementsSurplusDeficit.CreateStatementsSurplusDeficit(newStatement.StatementsMainID);
...
lGFinanceEntities.StatementsMains.Context.AddObject("StatementsSurplusDeficit",
newSurplusDeficit);
Interestingly, something is creating a record somewhere, because when I check SqlServer I do have new records for the 5 tables. Also interestingly, each time I try something and run the method, the primary key has been incremented by 2. It looks like the same record is being added twice, but I can't work out how.
Edit:
Following a comment suggestion, I changed the 'AddNewStatementsQuery' so lines that looked like:
lGFinanceEntities.StatementsMains.Context.AddObject("StatementsMains", newStatement);
were changed to:
lGFinanceEntities.StatementsMains.AddObject(newStatement);
and then to:
lGFinanceEntities.AddObject("StatementsMains", newStatement);
This did not solve the key violation error.
How do I find out where/how the data is being saved twice (ie, other than lGFinanceEntities.SaveChanges() in the if statement)?
Hmm. Looking at your code, I can see it being simplified down to:
// Create the new objects
var statement = new StatementsMain()
{
this.LocalGovt.StakeholderID, 161, this.Year.FinancialYearID
};
var income = new StatementsIncome()
{
StatementsMain = statement
};
var note = new StatementsNote()
{
StatementsMain = statement
};
var rss = new StatementsRSSFinPos()
{
StatementsMain = statement
};
var surplus = new StatementsSurplusDeficit()
{
StatementsMain = statement
};
// Add the objects into the context
lGFinancialEntities.AddObject(statement);
lGFinancialEntities.AddObject(income);
lGFinancialEntities.AddObject(note);
lGFinancialEntities.AddObject(rss);
lGFinancialEntities.AddObject(surplus);
// Persist the objects to the data storage
lGFinancialEntities.SaveChanges();
Or, even better:
// Create the main object
var statement = new StatementsMain()
{
this.LocalGovt.StakeholderID, 161, this.Year.FinancialYearID
};
// Add the objects into the context
lGFinancialEntities.AddObject(statement);
lGFinancialEntities.AddObject(new StatementsIncome() { StatementsMain = statement });
lGFinancialEntities.AddObject(new StatementsNote() { StatementsMain = statement });
lGFinancialEntities.AddObject(new StatementsRSSFinPos() { StatementsMain = statement });
lGFinancialEntities.AddObject(new StatementsSurplusDeficit() { StatementsMain = statement });
// Persist the objects to the data storage
lGFinancialEntities.SaveChanges();
But, this tells me there is a lot about your data schema that's not obvious here. For instance, what does the value 161 reference in the StatementsMain object?
FYI, assigning the primary object to the objects in which it is a foreign-key lets EF do the work of assigning the new ID to the other objects as they are persisted.
Related
I have three classes, Fish (which contains two properties of type Chips and MushyPeas respectively), MushyPeas (which contains a property of type Chips) and Chips (which has a Name property).
I am running the following piece of hypothetical code:
int chipsId;
using (var db = new FishContext())
{
var creationChips = new Chips() { Name = "A portion of chips" };
db.Chips.Add(creationChips);
db.SaveChanges();
chipsId = creationChips.ChipsId;
}
Chips retrievedChips1;
using (var db = new FishContext())
{
retrievedChips1 = db.Chips.Where(x => x.ChipsId == chipsId).ToList()[0];
}
Chips retrievedChips2;
using (var db = new FishContext())
{
retrievedChips2 = db.Chips.Where(x => x.ChipsId == chipsId).ToList()[0];
}
using (var db = new FishContext())
{
db.Chips.Attach(retrievedChips1);
db.Chips.Attach(retrievedChips2);
var mushyPeas = new MushyPeas() { Chips = retrievedChips2 };
var fish = new Fish() { Chips = retrievedChips1, MushyPeas = mushyPeas };
db.Fish.Add(fish);
db.ChangeTracker.DetectChanges();
db.SaveChanges();
}
This is to simulate a situation in my real app, in which EF objects (which may actually represent the same database record) are loaded from a variety of different DbContexts and then added to an object tree in another DbContext.
If I don't call the two db.Chips.Attach lines, then brand new Chips entities are created when the Fish object is saved to the database, and assigned new IDs.
Calling db.Chips.Attach solves this issue for one of the retrieved obejcts, but the second Attach call fails with the exception "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
What is the best way to achieve what I want to achieve here?
As a grizzled EF vet, I've come to the conclusion that it's best to avoid using Attach in many cases.
The exception "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key" is usually misleading since the object that you're trying to attach isn't actually attached to the data context. What happens when you attach an object is it recursively attaches any entities that it references. So, if you attach an entity to the data context, and then attach another entity that references any entity that was implicitly attached previously, you will get this error. The solution is pretty simple:
using (var db = new FishContext())
{
var chips1 = db.Chips.Find(retrievedChips1.Id);
var chips2 = db.Chips.Find(retrievedChips2.Id);
var mushyPeas = new MushyPeas() { Chips = chips2 };
var fish = new Fish() { Chips = chips1, MushyPeas = mushyPeas };
db.Fish.Add(fish);
db.ChangeTracker.DetectChanges();
db.SaveChanges();
}
This guarantees that both entities will be attached to the data context without any sort of ObjectStateManager issues.
You could query the Local collection to check if an entity with the same key is already attached and if yes, use the attached entity:
using (var db = new FishContext())
{
var attachedChips1 = db.Chips.Local
.SingleOrDefault(c => c.ChipsId == retrievedChips1.ChipsId);
if (attachedChips1 == null)
{
db.Chips.Attach(retrievedChips1);
attachedChips1 = retrievedChips1;
}
var attachedChips2 = db.Chips.Local
.SingleOrDefault(c => c.ChipsId == retrievedChips2.ChipsId);
if (attachedChips2 == null)
{
db.Chips.Attach(retrievedChips2);
attachedChips2 = retrievedChips2;
}
var mushyPeas = new MushyPeas() { Chips = attachedChips2 };
var fish = new Fish() { Chips = attachedChips1, MushyPeas = mushyPeas };
//...
}
(The first check doesn't make sense in this simple example because a new context is empty with nothing attached to it. But you get the idea...)
However, in the case that you also want to update the related entities (for example by setting the state to Modified after attaching) it would be a problem if retrievedChips1 and retrievedChips2 have (except the key value) different property values. You had to decide somehow which is the "correct one". But that would be business logic. You just have to hand over one of them to EF and only one. In your scenario it wouldn't matter which one you use because you are only creating a relationship and for this EF will only care about the key value.
Side note: Instead of ...ToList()[0] the more natural way would be ...First() (or Single() in this case because you are querying the key).
In our application, we create a few thousand phonecall records. Each phonecall should have a different owner, determined by a method named GetAnyAppropriateSystemUser(), which finds some random SystemUser based on some criteria.
In the code example below, we create a phonecall, and later use AssignRequest on it to specify its owner.
PhoneCall phoneCall = new PhoneCall();
//
// stuff to set up the new PhoneCall instance here; populate fields, etc...
//
// determine this phonecall's owner through some algorithm
Guid appropriateOwner = GetAnyAppropriateSystemUser();
Guid createdPhoneCallId = _serviceProxy.Create(phoneCall);
if (createdPhoneCallId != Guid.Empty)
{
AssignRequest phoneCallAssign = new AssignRequest();
phoneCallAssign.Assignee = new EntityReference(SystemUser.EntityLogicalName, appropriateOwner);
phoneCallAssign.Target = new EntityReference(PhoneCall.EntityLogicalName, createdPhoneCallId);
_serviceProxy.Execute(phoneCallAssign);
}
This works allright, but there are two calls, one to create, and one to assign. Is it ok to just set "ownerid" of the PhoneCall record before calling Create() method, thus eliminating the need to call an AssignRequest later? It seems to work, and I even found an example doing a similar thing in the SDK, as shown below.
SDK Sample: Roll Up Goal Data for a Custom Period Against the Target Revenue
// Create three goals: one parent goal and two child goals.
Goal parentGoal = new Goal()
{
Title = "Parent Goal Example",
RollupOnlyFromChildGoals = true,
ConsiderOnlyGoalOwnersRecords = true,
TargetMoney = new Money(300.0M),
IsFiscalPeriodGoal = false,
MetricId = new EntityReference
{
Id = _metricId,
LogicalName = Metric.EntityLogicalName
},
GoalOwnerId = new EntityReference
{
Id = _salesManagerId,
LogicalName = SystemUser.EntityLogicalName
},
OwnerId = new EntityReference
{
Id = _salesManagerId,
LogicalName = SystemUser.EntityLogicalName
},
GoalStartDate = DateTime.Today.AddDays(-1),
GoalEndDate = DateTime.Today.AddDays(30)
};
_parentGoalId = _serviceProxy.Create(parentGoal);
Although it seems to work, are there anything that we must be aware of if we set ownerid before creating the new record? Are there any differences?
Thank you very much in advance.
As you already found is allowed to set the ownerid when you create the record.
But is not possible to edit the owner of an existing record in the same way, in that case you must use the AssignRequest.
Check also this question:
ETL Software, can't retrieve owner of a contact
I get this error Cannot add an entity with a key that is already in use. when I run the code below.
Tables:
What am i missing?
private void CopyAllPageObjects(int fromPageID, int toPageID)
{
CMSDataContext _db = new CMSDataContext();
// Copy page objects
var originalPageObjects = (from x in _db.CMSPageObjects
where x.PageID == fromPageID
select x);
List<CMSPageObject> newPageObjects = new List<CMSPageObject>();
foreach (CMSPageObject po in originalPageObjects)
{
CMSPageObject newPageObject = new CMSPageObject();
newPageObject.PageID = toPageID;
newPageObject.CMSObjectID = po.CMSObjectID;
newPageObject.Name = po.Name;
newPageObject.Sorting = po.Sorting;
newPageObjects.Add(newPageObject);
// Copy page object attribute values
var originalPoavs = (from x in _db.CMSPageObjectAttributeValues
where x.CMSPageObjectID == po.ID
select x);
List<CMSPageObjectAttributeValue> newPoavs = new List<CMSPageObjectAttributeValue>();
foreach (CMSPageObjectAttributeValue poav in originalPoavs)
{
CMSPageObjectAttributeValue newPoav = new CMSPageObjectAttributeValue();
newPoav.CMSAttributeID = poav.CMSAttributeID;
newPoav.CMSPageObjectID = newPageObject.ID;
newPoav.LCID = poav.LCID;
newPoav.Value = poav.Value;
newPoavs.Add(newPoav);
}
_db.CMSPageObjectAttributeValues.InsertAllOnSubmit(newPoavs);
}
_db.CMSPageObjects.InsertAllOnSubmit(newPageObjects);
_db.SubmitChanges();
}
I was getting this error and it was because I had forgotten to set the Primary Key field in the database to "Identity Specification" (auto-increment). But that is just a guess
It looks like you're trying to add an object, while another one with same primary key exists. Are PageID or CMSObjectID primary keys? Or CMSAttributeID?
You might also want to share more data about how your data tables look like.
Update: after you added database struct, I would look closer at this line:
newPoav.CMSPageObjectID = newPageObject.ID;
the newPageObject.ID is probably not known at this time, because you didn't add the object to the DB yet (I suspect ID is identity). I think you could use:
newPoav.CMSPageObject = newPageObject
Seems you are missing primary key or an unique key on CMSPageObject table. Please try to verify the keys in the database. I had same issue since I had missed the PK on the table.
Cheers.
you have to add some code just for testing if the list newPoavs have a key exist already in the database
you can just add this
foreach (CMSPageObjectAttributeValue poav in originalPoavs)
{
CMSPageObjectAttributeValue newPoav = new CMSPageObjectAttributeValue();
newPoav.CMSAttributeID = poav.CMSAttributeID;
newPoav.CMSPageObjectID = newPageObject.ID;
newPoav.LCID = poav.LCID;
newPoav.Value = poav.Value;
newPoavs.Add(newPoav);
if(_db.CMSPageObjectAttributeValues.Any(x=>x.LCID == newPoav.LCID & x.CMSAttributeID == newPoav.CMSAttributeID & x.CMSPageObjectID == newPoav.CMSPageObjectID ))
MessageBox.Show("Already exist");
}
just to test your values
So, in a desperate attempt to wrangle EntityFramework into being usable. I am here..
private MyEntity Update(MyEntity orig)
{
//need a fresh copy so we can attach without adding timestamps
//to every table....
MyEntity ent;
using (var db = new DataContext())
{
ent = db.MyEntities.Single(x => x.Id == orig.Id);
}
//fill a new one with the values of the one we want to save
var cpy = new Payment()
{
//pk
ID = orig.ID,
//foerign key
MethodId = orig.MethodId,
//other fields
Information = orig.Information,
Amount = orig.Amount,
Approved = orig.Approved,
AwardedPoints = orig.AwardedPoints,
DateReceived = orig.DateReceived
};
//attach it
_ctx.MyEntities.Attach(cpy, ent);
//submit the changes
_ctx.SubmitChanges();
}
_ctx is an instance variable for the repository this method is in.
The problem is that when I call SubmitChanges, the value of MethodId in the newly attached copy is sent to the server as 0, when it is in fact not zero if I print it out after the attach but before the submit. I am almost certain that is related to the fact that the field is a foreign key, but I still do not see why Linq would arbitrarily set it to zero when it has a valid value that meets the requirements of the constraint on the foreign key.
What am I missing here?
You should probably set Method = orig.Method, but I can't see your dbml, of course.
I think you need to attach the foreign key reference
var cpy = new Payment()
{
//pk
ID = orig.ID,
//other fields
Information = orig.Information,
Amount = orig.Amount,
Approved = orig.Approved,
AwardedPoints = orig.AwardedPoints,
DateReceived = orig.DateReceived
};
//create stub entity for the Method and Add it.
var method = new Method{MethodId=orig.MethodId)
_ctx.AttachTo("Methods", method);
cpy.Methods.Add(method);
//attach it
_ctx.MyEntities.Attach(cpy, o);
//submit the changes
_ctx.SubmitChanges();
I have two table like this:
**Complaint**
-Id
-CreatedBy
-CreatedDate
....
**Solution**
-Id
-ComplaintId
Sometimes, a complaint has an instant solution, which means, when it is created, a solution is also created. The Database is Oracle, and to insert new record into database, I set the StoredGeneratePattern to Identity and use trigger to insert a sequence's value.
here my code:
using (var context = new Entities())
{
var complaint = new Complaint
{
Title = TitleTextBox.Text.Trim(),
CreatedBy = CurrentUser.UserID,
Description = DescriptionTextBox.Text.Trim(),
ServiceId = Convert.ToDecimal(ddlService2.Value),
Contact = ContactTextBox.Text.Trim(),
CreatedDate = DateTime.Now,
Customer = txtUserName.Text.Trim(),
ResellerId = CurrentUser.ResellerID,
Status = ComplaintStatus.GetStatusCode("New complaint")
};
if (CompletedCheckBox.Checked)
{
complaint.Status = ComplaintStatus.GetStatusCode("Completed");
var solution = new Solution
{
CreatedBy = CurrentUser.UserID,
CreatedDate = DateTime.Now,
SolutionDesc = DescriptionTextBox.Text,
ComplaintId = complaint.Id
};
context.Solutions.AddObject(solution);
}
context.Complaints.AddObject(complaint);
if(context.SaveChanges() > 0)
{
ResetFrom();
return true;
}
}
the problem is, I can't get the id of newly created complaint to set the field in the solution. How can I do that?
Thank you.
Could you not perform the first operation call SaveChanges() and then query your complaint object which should now have a complaintID.
Assuming you are using a trigger/sequence with Oracle, you will need to do a get after you save changes to get an object with the Id populated. If you are not using a trigger, you can set the Id manually on the new object by getting the next value from the sequence.
If you add the complaint and SaveChanges() before you create the solution the complaint object will have the Identity value, then after creating the solution add it to the context and call SaveChanges() a second time.
context.Complaints.AddObject(complaint);
if (CompletedCheckBox.Checked)
{
complaint.Status = ComplaintStatus.GetStatusCode("Completed");
context.SaveChanges();
var solution = new Solution
{
CreatedBy = CurrentUser.UserID,
CreatedDate = DateTime.Now,
SolutionDesc = DescriptionTextBox.Text,
ComplaintId = complaint.Id
};
context.Solutions.AddObject(solution);
}
if(context.SaveChanges() > 0)
{
ResetFrom();
return true;
}
Also if you were to add a Foreign Key Relationship Between the Solution and the Complaint, you would no set the ComplaintId, you would just set solution.Complaint = complaint and the Ids would be set correctly during the save.
The answer is actually easy. In this case I do not believe you need the ID at all (at least not just to add this relationship), but in case you do, do this:
Make sure you have the ID on the Complaint entity to refresh on Insert (We use DevArt, but I forget the exact setting name here, but if you select the ID on the entity you should see an UpdateMode or something like that that needs to be set to UpdateOnInsert i think), then
To just insert this relationship do this:
using (var context = new MyContext())
{
var complaint = new Complaint {...};
context.Complaints.AddObject(complaint);
var solution = new Solution {..., Complaint = complaint};
context.Solutions.AddObject(solution);
context.SaveChanges();
}
You will not want to do SaveChanges twice as that requires a separate transactionscope. This way you don't need it.
You can add the Complaint to the Solutions "Complaint" navigation property.
So create your solution object like you are doing then do the following:
Soltion.Complaint = newCreatedComplaintObject;