C# Linq is removing a value from my entity - c#

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();

Related

Can Entity Framework add many related entities with single SaveChanges()?

I am writing many (20+) parent child datasets to the database, and EF is requiring me to savechanges between each set, without which it complains about not being able to figure out the primary key. Can the data be flushed to the SQL Server so that EF can get the primary keys back from the identities, with the SaveChanges being sent at the end of writing all of the changes?
foreach (var itemCount in itemCounts)
{
var addItemTracking = new ItemTracking
{
availabilityStatusID = availabilityStatusId,
itemBatchId = itemCount.ItemBatchId,
locationID = locationId,
serialNumber = serialNumber,
trackingQuantityOnHand = itemCount.CycleQuantity
};
_context.ItemTrackings.Add(addItemTracking);
_context.SaveChanges();
var addInventoryTransaction = new InventoryTransaction
{
activityHistoryID = newInventoryTransaction.activityHistoryID,
itemTrackingID = addItemTracking.ItemTrackingID,
personID = newInventoryTransaction.personID,
usageTransactionTypeId = newInventoryTransaction.usageTransactionTypeId,
transactionDate = newInventoryTransaction.transactionDate,
usageQuantity = usageMultiplier * itemCount.CycleQuantity
};
_context.InventoryTransactions.Add(addInventoryTransaction);
_context.SaveChanges();
}
I would like to do my SaveChanges just once at the end of the big loop.
You don`t need to save changes every time if you use objects refernces to newly created objects not IDs:
var addItemTracking = new ItemTracking
{
...
}
_context.ItemTrackings.Add(addItemTracking);
var addInventoryTransaction = new InventoryTransaction
{
itemTracking = addItemTracking,
...
};
_context.InventoryTransactions.Add(addInventoryTransaction);
...
_context.SaveChanges();
Since they're all new items rather than
itemTrackingID = addItemTracking.ItemTrackingID,
you could go with
addItemTracking.InventoryTransaction = addInventoryTransaction;
(or whatever the associated navigation property is) and pull the _context.SaveChanges() out of the loop entirely. Entity Framework is very good at inserting object graphs when everything is new. When saving object graphs containing both new and existing items setting the associated id is always safer.
How about:
var trackingItems = itemCounts
.Select(i => new ItemTracking
{
availabilityStatusID = availabilityStatusId,
itemBatchId = i.ItemBatchId,
locationID = locationId,
serialNumber = serialNumber,
trackingQuantityOnHand = i.CycleQuantity
});
_context.ItemTrackings.AddRange(trackingItems);
_context.SaveChanges();
var inventoryTransactions = trackingItems
.Select(t => new InventoryTransaction
{
activityHistoryID = newInventoryTransaction.activityHistoryID,
itemTrackingID = t.ItemTrackingID,
personID = newInventoryTransaction.personID,
usageTransactionTypeId = newInventoryTransaction.usageTransactionTypeId,
transactionDate = newInventoryTransaction.transactionDate,
usageQuantity = usageMultiplier * t.trackingQuantityOnHand
});
_context.InventoryTransactions.AddRange(inventoryTransactions);
_context.SaveChanges();
However I haven't worked with EF for quite a while and above code is written in notepad so I cannot vouch for it

Why is a validation exception thrown on a field which is not being updated?

I am using EF 6.0.2 and trying to update only the Status field of an entity.
var drama = new Drama { Id = id };
using (var ctx = new DataContext()) {
ctx.Dramas.Attach(drama);
drama.Status = state;
ctx.SaveChanges();
}
This throws a ValidationException: The ClassName field is required.
The entity already exists, and is valid (including having a ClassName). Id is the entity key.
What's going on here to cause the exception to be thrown?
You're saying there already is an entity with the given Id in the database and you'd only like to change its Status value?
That's not what you're doing in your code. You have created a new entity with the given Id and set only its Status value. That's why it's throwing a ValidationException: ClassName field is empty unless you're setting it in Drama constructor.
To modify an existing entity you should first retrieve it from the database, then modify it and save the changes:
using (var ctx = new DataContext()) {
var drama = ctx.Dramas.Single(d => d.Id == id);
drama.Status = state;
ctx.SaveChanges();
}
EDIT:
If you want to simulate a scenario of editing a detached entity, you still need its values to be valid. Try fulfilling all the validation requirements before attaching the entity (i.e. fill in the ClassName), then Attach it, update Status and SaveChanges:
var drama = new Drama { Id = id, ClassName = "Dummy" };
using (var ctx = new DataContext()) {
ctx.Dramas.Attach(drama);
drama.Status = state;
ctx.SaveChanges();
}
When you do edits in EF, you should get the entire record that you are editing and then put the fields whatever you want to update.
using (var ctx = new DataContext()) {
drama = ctx.Dramas.Where(O => O.Id = id);
drama.Status = state;
ctx.Dramas.Attach(drama);
ctx.SaveChanges();
}
Hope this helps

Attaching objects retrieved from multiple sources

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).

Cannot add an entity with a key that is already in use (LINQ)

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

How to get yet-to-be-created ID in Linq/Entity framework

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;

Categories