I have a structure like this
DRDLines:
ID
DrawingRevisionID
DrawingRevision:
ID
Name
They're related in a one-to-many relationship.
In this code example
DRDLine line;
using (var db = new AMPX_DCEntities())
{
line = db.DRDLines.Single(p => p.ID == 1);
System.Console.WriteLine(line.DrawingRevision.ID);
}
using (var db = new AMPX_DCEntities())
{
var id = 12;
line.DrawingRevisionID = id;
}
using (var db = new AMPX_DCEntities())
{
db.Entry(line).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
}
I get this error
A referential integrity constraint violation occurred: The property value(s) of 'DrawingRevision.ID' on one end of a relationship do not match the property value(s) of 'DRDLine.DrawingRevisionID' on the other end.
What I've found: it doesn't update relations in DRDLines inside DrawingRevision
Debugging I see:
line.DrawingRevision.DRDLines[0].ID != line.DrawingRevisionID
If I remove line
System.Console.WriteLine(line.DrawingRevision.ID);
or write it like this
System.Console.WriteLine(line.DrawingRevisionID);
everything goes without errors. But I need that line to be used.
So, how can I fix that?
My guess is that the problem is caused by repeatedly creating a new context and then disposing it. When you set the DrawingRevisionID here
using (var db = new AMPX_DCEntities())
{
var id = 12;
line.DrawingRevisionID = id;
}
line is detached from the dbcontext from which it was retreived, but isn't attached to the new DbContext you've created, hence EF won't wire up the relationships when you change the ID.
You could attach the line object back to the context before changing the ID
db.DRDLines.Attach(line);
That will change both the IDs (although you could just change the other ID manually). Since that context is then disposed, you may need to set the EntityState to Modified for the DrawingRevision (or at least of the ID property) in the last DbContext session.
Also, I would add an Include to the original query to eagerly load the DrawingRevision. At the moment its only loaded when you query the ID on the System.Console line, hence why the behaviour is different. This also causes an extra trip to the database. Putting it into an include will be more efficient and more predictable.
Related
I'm new to using EF to handle data in SQL. In a MVC Core project we're testing EF (Microsoft.EntityFrameworkCore, version 2.2.3) to handle data.
When trying to update data and update failed for some reason (missing fields etc) it seemed like EF actually deleted the record from the database (MSSQL 2014) instead of throwing an update error...
Is it possible?
Code for updating:
public void Update(Contact contact)
{
_dbContext.Update(contact);
_dbContext.SaveChanges();
}
When trying to update data and update failed for some reason (missing fields etc) it seemed like EF actually deleted the record from the database (MSSQL 2014) instead of throwing an update error...
Is it possible?
It should not.
test it, try to debug here
_dbContext.Update(contact);
_dbContext.SaveChanges();
var updated = _dbContext.Contacts.FirstOrDefault(x => x.Id == contact.Id); //debug here
check if it has a value, if still none, these are the scenarios i can think of that may have caused your problem
investigate the missing field specially if it is not nullable.
is the _dbContext used here is the same connection string used with everything?
is the [Key] attribute listed on your Contact entity?
public class Contact
{
[Key]
public int Id
}
overridden the SaveChanges function?
is what you are passing Contact contains a Key and it is not 0?
is a delete function called after Update?
try using SQL Profiler to look at the Linq to SQL if it really generated an update query and if it is really pointing at the right [Key]
but if it is still not working properly, you could do
public void Update(Contact contact)
{
var selectedContactToBeUpdated = _dbContext.Contacts.FirstOrDefault(x => x.Id == contact.Id);
if (selectedContactToBeUpdated != null)
{
selectedContactToBeUpdated.PropertyToBeUpdated1 = newValue;
selectedContactToBeUpdated.PropertyToBeUpdated2 = newValue2;
//additional Properties
_dbContext.SaveChanges();
}
}
in the scenario above, it will only generate an Update statement with fields you have changed.
I am developing code for app engine. I tried to update an existing row by updating the same entity returned as result of query. But it creates new row instead of updating the same row. Following is the code:
public boolean updateProfile(DbProfile profile) {
Transaction txn = _datastore.beginTransaction();
Entity entity = getProfileEntity(profile.getLoginId());
if (entity != null) {
entity.setProperty(DbProfile.DbProfilePropertyNames.address, profile.getAddress());
entity.setProperty(DbProfile.DbProfilePropertyNames.name, profile.getName());
Key key = _datastore.put(entity);
txn.commit();
return true;
}
return false;
}
private Entity getProfileEntity(String userName) {
Key eRecommendationKey = KeyFactory.createKey("eRecommendation", _dbKeyName);
FilterPredicate predicateUsername =
new FilterPredicate(DbProfile.DbProfilePropertyNames.loginId, FilterOperator.EQUAL,
userName.toUpperCase());
Query query =
new Query(DbProfile.entityProfileName, eRecommendationKey).setFilter(predicateUsername);
List<Entity> profiles =
_datastore.prepare(query).asList(FetchOptions.Builder.withDefaults());
Utils.log.log( Level.SEVERE, "not found"+profiles.size() );
if (profiles.size() == 0) {
//profile data is not set yet
return null;
} else {
return profiles.get(0);
}
}
Following image shows fields in the entity.
Please let me know how can I fix the issue.
My Java skills are not too good, so I find it difficult to understand your code sample. I also don't see where updateProfile() is called and how your code is getting or constructing the profile object, especially whether the key of the profile object is altered.
But in general, if new entities are created instead of updating existing entities, the reason is that the key at your updating commit is different from the actual key of the existing entity.
Typical situations:
a string (key-name) is used instead of an integer (ID), or vice versa
a typo in the kind name of the key
different namespace or app properties of the key
parents are missing in the key path or are constructed wrongly
Suggestion:
In datastore viewer, compare the key of an existing entity with the key of the accidentally created entity. The difference between both keys might give you a hint where to look in your code for the bug.
I solved the problem. It was my mistake. I had called saveData servlet instead of updateProfile servlet.
So I'm trying to prevent a race condition between applications.
Using IsolationLevel/TransactionScope, I can lock the table the way I need to, but need to run the update operation first, then operate on the list of modified objects.
To do this, I need to run the update and get the list of updated ID's all in one shot.
If I were to try to take the IDs first, that wouldn't lock the table, and another app instance could query for that same list, before they were flagged.
Is there a way to do something like:
//modify some objects
var updatedIds = context.SaveChanges();
//Process updatedIds
Is there a way to do this? I've tried looking through the ObjectContext entries, but after the Save there doesn't seem to be anything.
Maybe I'll have to do an sproc?
This code can go into your Context class and should give you what you need
public override int SaveChanges()
{
using (var scope = new System.Transactions.TransactionScope())
{
//pre processing
var result = base.SaveChanges();
//post processing
scope.Complete();
return result;
}
}
I'm trying to update an entity using Session.Update then continue to execute another SQL query. The other query did not see the changed value. When i traced it using profiler, Session.Update did nothing.
public class InvoiceService()
{
public void Update(Invoice invoice)
{
using (var trans = BeginTransaction())
{
Session.Update(invoice); //Nhibernate did not update invoice.
ExecuteNamedQuery(); //Query executed before invoice updated.
trans.Commit(); //Invoice updated.
}
}
}
Then i add Session.Flush after Session.Update.
using (var trans = BeginTransaction())
{
Session.Update(invoice);
Session.Flush()
ExecuteNamedQuery();
trans.Commit();
}
After Session.Flush executed, SQL query for update is executed also.
It works perfectly. The execution order is correct. But then i executed another method to get all invoices. Committing transaction makes nhibernate execute update query to update my updated invoice earlier with old values. (ex: Quantity = 20, updated to 10, then updated again to 20)
public void FindAll()
{
using (var trans = BeginTransaction())
{
var invoices = Session.CreateCriteria<Invoice>().List<Invoice>();
trans.Commit(); // In here invoice that i updated earlier get updated again, using old values.
return invoices;
}
}
Why it's getting updated again?
What's the solution for this problem?
Thanks in advance.
Update is an unfortunate name for the method; the purpose of Update is to attach a transient instance to a new session. See the documentation for update and make sure you understand instance states.
The invoice is updated to the original values because NHibernate thinks it has changed. This "phantom" update may be caused by a property changing unexpectedly. A typical root cause is a nullable database column mapped to a non-nullable property (or vice-versa). The easiest way to troubleshoot is to turn on dynamic-update in the session factory configuration so that you can see which properties NHibernate detects as dirty.
Here my code for apex trigger.
trigger LeadTrigger on Lead (after insert)
{
if(Trigger.isInsert){
for(Lead newLead: Trigger.new)
{
//newLead.RecrodTypeId //'Give value of record type id.
//newLead.RecordType.Name //'Null'
}
}
}
Why "newLead.RecordType.Name" returns null?
The lists of objects available in triggers only have values for the fields on the object the trigger is running on. No relationships are traversed, only the IDs of the lookup records are included.
Therefore, to pull in any extra information you need to from related objects needs to be queried for.
You'll want to do something like this:
trigger LeadTrigger on Lead (after insert) {
map<id, RecordType> mapRecordTypes = new map<id, RecordType>();
if(Trigger.isInsert) {
for(Lead newLead: Trigger.new) {
mapRecordTypes.put(newLead.RecordTypeId, null);
}
}
for(RecordType rt : [select Id, Name from RecordType
where Id in : mapRecordTypes.ketSet()]) {
mapRecordTypes.put(rt.Id, rt);
}
for(Lead newLead : Trigger.new) {
string recordTypeName = mapRecordTypes.get(sLead.RecordTypeId).Name;
}
}
This is probably because some of your leads that just got inserted don't have record types associated with them. This is normal. You can enforce that record type selection is mandatory through configuration, if that's what you're looking for.
[EDIT]
Now I think I understand the issue (from your comment). The reason is that since you're in a trigger, the associated RecordType referenced object is not available. The RecordTypeId will always be available since it is literally part of the trigger object as an Id. However, child objects (referenced objects) will not be available to simply reference from within a trigger. To do this you need to create a map of the referenced object in question by doing an additional SOQL call WHERE Id IN: theIdList.
From Apex, not in a trigger, you need to specifically call this field out from your SOQL like this:
List<Lead> leads = [SELECT Id, RecordType.Name FROM Lead];
What just happened there is that the child object, the RecordType in this case, was included in the query and therefore available to you. By default a trigger will not have all of your child objects pre-selected and therefore need to be selected afterwards from within the trigger or class called by the trigger:
List<Id> recIds = new List<Id>();
for(Lead l : leads)
{
recIds.add(l.RecordTypeId);
}
List<RecordType> rt = [SELECT Id, Name FROM RecordType WHERE Id IN :recIds];
Map <Id, String> idRecNameMap = new Map<Id, String>();
for(RecordType r : rt)
{
idRecNameMap.put(r.Id, r.Name);
}
// And finally...
for(Lead l : Trigger.new)
{
String tmpRecordTypeName = idRecNameMap.get(l.RecordTypeId);
}
I did not test this code but I think it look ok. Hope this makes sense.
you can't get extra information on the related objects from this trigger. if you want to get more information you need to make query for other objects.
List<RecordType> records = [SELECT Id, Name FROM RecordType WHERE Id = newLead.RecrodTypeId];
string myname = records[0].name;
but remember that you shouldn't make a query in for loop. so if you wanted to do it in the right way go for Adam's solution.
Put some system debug inside the loop and check your system debug logs for more information
system.debug('lead:' + newLead);
inside the for loop and see what is being passed in. You may find that it is null.
We cant really give you a good answer without knowint the rest of your set up.