Question
Why is EF first inserting a child object (PersonnelWorkRecord) with a dependency, before the object that it is depended on (TimesheetActivity). Also what are my options on correcting this?
ERD (simplified)
This is predefined by another system out of my direct control.
EF setup and save code
I am not sure I understand why/how Entity Framework is inserting the objects I have in the order it does however here is the code I am using to insert a parent and several children.
using (var db = new DataContext(user))
{
timesheet.State = State.Added;
timesheet.SequenceNumber = newSequenceNumber;
this.PrepareAuditFields(timesheet);
//To stop EF from trying to add all child objects remove them from the timehseets object.
timesheet = RemoveChildObjects(timesheet, db);
//Add the Timesheet object to the database context, and save.
db.Timesheets.Add(timesheet);
result = db.SaveChanges() > 0;
}
SQL Trace of EF's Inserts
When I run the code I get a SQL foreign key violation on the PersonnelWorkRecord (TimesheetActivityID) because I have not yet added the Activity (see trace).
exec sp_executesql N'insert [dbo].[Timesheets]([ProjectID], [TimesheetStatusID], ...
exec sp_executesql N'insert [dbo].[PersonnelWorkdays]([TimesheetID], [PersonnelID], ...
exec sp_executesql N'insert [dbo].[PersonnelWorkRecords]([PersonnelWorkdayID],[TimesheetActivityID], ...
Data Context Summary
modelBuilder.Entity<PersonnelWorkday>().HasRequired(pwd => pwd.Personnel).WithMany(p => p.PersonnelWorkdays).HasForeignKey(pwd => pwd.PersonnelID).WillCascadeOnDelete(false);
modelBuilder.Entity<PersonnelWorkday>().HasRequired(pwd => pwd.Timesheet).WithMany(t => t.PersonnelWorkdays).HasForeignKey(pwd => pwd.TimesheetID).WillCascadeOnDelete(false);
modelBuilder.Entity<PersonnelWorkRecord>().HasRequired(pwr => pwr.PersonnelWorkday).WithMany(pwd => pwd.PersonnelWorkRecords).HasForeignKey(pwr => pwr.PersonnelWorkdayID).WillCascadeOnDelete(false);
modelBuilder.Entity<PersonnelWorkRecord>().HasRequired(pwr => pwr.TimesheetActivity).WithMany(ta => ta.PersonnelWorkRecords).HasForeignKey(pwr => pwr.TimesheetActivityID).WillCascadeOnDelete(false);
modelBuilder.Entity<TimesheetActivity>().HasRequired(ta => ta.ProjectActivity).WithMany(a => a.TimesheetActivities).HasForeignKey(ta => ta.ProjectActivityCodeID).WillCascadeOnDelete(false);
modelBuilder.Entity<TimesheetActivity>().HasOptional(ta => ta.Facility).WithMany(f => f.TimesheetActivities).HasForeignKey(tf => tf.FacilityID).WillCascadeOnDelete(false);
modelBuilder.Entity<TimesheetActivity>().HasRequired(ta => ta.Timesheet).WithMany(t => t.TimesheetActivities).HasForeignKey(ta => ta.TimesheetID).WillCascadeOnDelete(false);
Remove Child Objects
Here is the code for the child objects method. I added this method to remove the objects from the timesheets' child objects related objects that are not foreign keys. For example I have a Crew object but I also have a CrewID foreign key, so I have set Crew = null so that EF does not try to insert it since it already exists.
private Timesheet RemoveChildObjects(Timesheet timesheet, DataContext db)
{
timesheet.Crew = null;
timesheet.Foreman = null;
timesheet.Location = null;
timesheet.Project = null;
timesheet.SigningProjectManager = null;
timesheet.TimesheetStatus = null;
timesheet.Creator = null;
timesheet.Modifier = null;
if (timesheet.TimesheetActivities != null)
{
foreach (TimesheetActivity tsa in timesheet.TimesheetActivities)
{
tsa.Creator = null;
if (tsa.EquipmentWorkRecords != null)
{
tsa.EquipmentWorkRecords = RemoveChildObjects(tsa.EquipmentWorkRecords, db);
}
tsa.Facility = null;
tsa.Modifier = null;
if (tsa.PersonnelWorkRecords != null)
{
tsa.PersonnelWorkRecords = RemoveChildObjects(tsa.PersonnelWorkRecords, db);
}
tsa.ProjectActivity = null;
tsa.Structures = null;
tsa.Timesheet = null;
}
}
if (timesheet.TimesheetEquipment != null)
{
foreach (TimesheetEquipment te in timesheet.TimesheetEquipment)
{
te.Equipment = null;
te.Timesheet = null;
}
}
if (timesheet.EquipmentWorkdays != null)
{
timesheet.EquipmentWorkdays = RemoveChildObjects(timesheet.EquipmentWorkdays, true, db);
}
if (timesheet.TimesheetPersonnel != null)
{
foreach (TimesheetPersonnel tp in timesheet.TimesheetPersonnel)
{
tp.Personnel = null;
tp.PersonnelWorkday = null;
if (tp.PersonnelWorkday != null)
{
tp.PersonnelWorkday = RemoveChildObjects(tp.PersonnelWorkday, db);
}
tp.Timesheet = null;
}
}
if (timesheet.PersonnelWorkdays != null)
{
timesheet.PersonnelWorkdays = RemoveChildObjects(timesheet.PersonnelWorkdays, true, db);
}
return timesheet;
}
Debug of values before EF save
From my understanding anything an dbContex.ObjectNameHere.Local will be added/modified/deleted when a dbContext.Save() is called. (Depending on what the entity State is set too.) Here is what EF is trying to save before I call the save() and get an SQL FK exception.
Then I get the FK exception.
The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_PersonnelWorkRecords_TimesheetActivities". The conflict occurred
in database "VPMTEST_GC", table "dbo.TimesheetActivities", column
'TimesheetActivityID'. The statement has been terminated.
Notes
Please let me know if there is anything I can post to help describe my question. I have looked around google / SO for answers but so far no solid answers, it looks like EF can not determine the order of inserting objects unless the Domain model is setup differently? I am not able to change the structure of most objects as they are used by another system. I can attempt to change my EF call, I would prefer not to use Raw SQL as the objects are quite a bit more extensive then the simplified versions I have posted here.
Similar questions: Self referencing entity and insert order
In your RemoveChildObjects method I see the line...
tsa.Timesheet = null;
So, apparently your are setting the inverse navigation property of Timesheet.TimesheetActivities to null. Are you doing the same with PersonnelWorkRecord.TimesheetActivity and PersonnelWorkRecord.PersonnelWorkday, i.e. do you set those properties to null as well in the nested RemoveChildObjects methods?
This could be a problem because you have two different paths from Timesheet to PersonnelWorkRecord, namely:
Timesheet -> TimesheetActivities -> PersonnelWorkRecords
Timesheet -> PersonnelWorkdays -> PersonnelWorkRecords
When you call db.Timesheets.Add(timesheet) I believe EF will traverse each branch in the object graph one by one and determine on the path which related objects ("nodes") are dependent and which are principal in a relationship to determine the order of insertion. timesheet itself is principal for all its relationships, therefore it is clear that it must be inserted first. Then EF starts to iterate through one of the collections Timesheet.TimesheetActivities or Timesheet.PersonnelWorkdays. Which one comes first doesn't matter. Apparently EF starts with Timesheet.PersonnelWorkdays. (It would not solve the problem if it would start with Timesheet.TimesheetActivities, you would get the same exception, but with PersonnelWorkRecord.PersonnelWorkday instead of PersonnelWorkRecord.TimesheetActivity.) PersonnelWorkday is only dependent on Timesheet which is already inserted. So, PersonnelWorkday can be inserted as well.
Then EF continues traversing with PersonnelWorkday.PersonnelWorkRecords. With respect to the PersonnelWorkday dependency of PersonnelWorkRecord there is again no problem because the PersonnelWorkday has already been inserted before. But when EF encounters the TimesheetActivity dependency of PersonnelWorkRecord it will see that this TimesheetActivity is null (because you've set it to null). It assumes now that the dependency is described by the foreign key property TimesheetActivityID alone which must refer to an existing record. It inserts the PersonnelWorkRecord and this violates a foreign key constraint.
If PersonnelWorkRecord.TimesheetActivity is not null EF would detect that this object hasn't been inserted yet but it is the principal for PersonnelWorkRecord. So, it can determine that this TimesheetActivity must be inserted before the PersonnelWorkRecord.
I would hope that your code works if you don't set the inverse navigation properties to null - or at least not the two navigation properties in PersonnelWorkRecord. (Setting the other navigation properties like tsa.Creator, tsa.Facility, etc. to null should not be a problem because those related objects really already exist in the database and you have set the correct FK property values for those.)
This may no longer be valid, however is it an option to use a transaction and adding each child object individually?
Note:
I think Slauma's solution is more complete, however a transaction call may still be an option for others with similar issues.
Related
I had a bit of difficulties with getting the right title for this problem so I hope my explanation below will make it a bit clearer.
I am using EntityFramework 6 and I am doing multiple inserts within a function.
There are 3 different tables which get updated / inserted: table EntityMethod, EntityRoom and EntityRoomMethod. The table EntityRoomMethod has a foreign key relationship with the table EntityMethod.
In some cases, a EntityMethod row is missing and this is newly created by adding the object with entity framework:
if (mn == null)
{
mn = new Method
{
ElementId = floorProgram.ElementId,
ActionId = m.ActionId,
ElementCount = m.ElementCount,
ColorId = m.ColorId,
IsBase = m.IsBase,
IsHccp = m.IsHccp,
TimeNorm = m.TimeNorm,
Frequency5Id = m.Frequency5Id,
MaterialId = m.MaterialId,
ProductId = m.ProductId,
MethodTypeId = m.MethodTypeId,
};
}
In another part of code the Method foreign key (MethodId) of the EntityRoomMethod table is being set:
roomMethodObject.RightId = mn.Id;
RightId is in this case the relationship with the EntityMethod table.
On a later point the other 2 table objects (EntityRoom and EntityRoomMethod) are also added (DBSet.Add) using EF.
The problem however is, that when the EntityMethod is newly added, it gets the Id value of 0, because SaveChanges() is not yet executed. The foreign key reference in the EntityRoomMethod is therefor also being set to 0.
When the function returns to the caller, the SaveChanges() is being executed and all 3 objects (representing the 3 tables) are being saved.
This however will generate a FK error (because Id 0 does not exist obviously).
I tried to fix this by calling SaveChanges() after Adding the new Method (so directly in the function). This however will cause some other problems.
In the end I have gotten multiple errors but I assume it all has to do with the same thing, the errors were the following:
Unable to determine the principal end of the 'Solution.Data.RoomMethod_Method' relationship. Multiple added entities may have the same primary key.
The property 'RightId' is part of the object's key information and cannot be modified.
Conflicting changes to the role x of the relationship y have been detected
So now the actual question:
Is there an (easy) way to call SaveChanges() after all 3 entities have been added with EF but also handling the FK errors? Does this mean I have to generate the Id's myself? Or was the first approach better (Calling SaveChanges directly after adding the EntityMethod object).
For now I have some not-nice-looking solution with doing a direct INSERT statement after adding a new EntityMethod (using Dapper). This kind-of works but I assume there is a better way wherein I can just use EF6.
P.S. calling SaveChanges() after adding the EntityMethod was basically the same by doing it with Dapper, however this generated some other errors while using Dapper it didn't generate those errors.
You can wrap several SaveChanges within a transaction, and EF (or probably SQL) will generate the required ids at each point, allowing you to reference them later on.
using(var transaction = _context.Database.BeginTransaction())
{
var p1 = new Something { Name = "Fred" };
_context.SaveChanges();
var a2 = new Dependency { SomethingId = p1.Id }; <-- p1.Id now has an Id value
_context.SaveChanges();
var b3 = new OtherDependency { DependencyId = a2.Id }; <-- a2.Id now has an Id value
_context.SaveChanges();
transaction.Commit(); <-- All 3 changes are fully committed to the db at this point.
}
I'm using Entity Framework 6, Code First approach. I'll try to present my problem with a simple piece of code:
public void ViewEntity(MyEntity Entity) // Want to read properties of my entity
{
using (var Db = new MyDbContext())
{
var DummyList = Db.MyEntities.ToList(); // Iteration on this DbSet
Db.MyEntities.Attach(Entity); // Exception
}
}
The exception message is: Attaching an entity of type 'MyProgram.MyEntity' failed because another entity of the same type already has the same primary key value.
From what I've read on MSDN it's an expected behaviour. But what I want on that last line is to first check if there is an entity with the same key already attached to a context; if it is, use it instead, and only otherwise attach my entity to context.
But I've failed to find a way to do so. There are many utility methods on ObjectContext instance (for example GetObjectByKey). I can't test them all 'cause they all ultimately need a qualifiedEntitySetName, and I don't have any in my real imlpementation, because this method should be on an abstract class and it should work for all entity types. Calling Db.Entity(this) is no use, there is no EntityKey which would have EntitySetName.
So all of this became complex really fast. And in my terms I just want to check if the object is already in "cache" (context), use it, otherwise use my object and attach it to this context.
To be clear, I have a detached object from a TreeNode.Tag in the first place, and I just want to use it again, or if it's impossible; if there already is one in the context), use that one instead. Maybe I'm missing some crucial concepts of EF6, I'm just starting out with EF.
I've found a solution for me. As I guessed correctly ObjectContext.GetObjectByKey method does what I need, but first I needed to construct qualifiedEntitySetName, and I found a way to do so. A tad bit cumbersome (using reflection, iterating properties of MyDbContext), but does not compare to a headache of a problem I made out of all this. Just in case, here's the patch of code that is a solution for me:
public SdsAbstractObject GetAttachedToContext()
{
var ObjContext = (SdsDbContext.Current as IObjectContextAdapter).ObjectContext;
var ExistingItem = ObjContext.GetObjectByKey(GetEntityKey()) as SdsAbstractObject;
if (ExistingItem != null)
return ExistingItem;
else
{
DbSet.Attach(this);
return this;
}
}
public EntityKey GetEntityKey()
{
string DbSetName = "";
foreach (var Prop in typeof(SdsDbContext).GetProperties())
{
if (Prop.PropertyType.IsGenericType
&& Prop.PropertyType.GenericTypeArguments[0] == ObjectContext.GetObjectType(GetType()))
DbSetName = Prop.Name;
}
if (String.IsNullOrWhiteSpace(DbSetName))
return null;
else
return new EntityKey("SdsDbContext." + DbSetName, "Id", Id);
}
An Entity can be in one of five stages : Added, Unchanged, Modified, Deleted, Detached.
public void ViewEntity(MyEntity entity) // Want to read properties of my entity
{
using (var Db = new MyDbContext())
{
var DummyList = Db.MyEntities.ToList(); // Iteration on this DbSet
// Set the Modified state of entity or you can write defensive code
// to check it before set the state.
if (Db.Entry(entity).State == EntityState.Modified) {
Db.Entry(entity).State = EntityState.Modified
}
// Attached it
Db.MyEntities.Attach(Entity);
Db.SaveChanges();
}
}
Since EF doesn't know which properties are different from those in the database, it will update them all.
I had a question more detailed earlier which I had no answer, I will have the same question with a simpler way:
I have an EF database with foreign key to another table.
I would like to UPDATE an ENTITY. But I need to this like this and I'll write the codes below:
Go to database and retrieve the Member by id, return EF Member object
Do some changes on the object OUTSIDE the EF Context
Send the MODIFED EF Member into a Save method
In BL layer save method uses the context and save changes.
1)
MemberManager currentMemberManager = new MemberManager();
Member NewMember = currentMemberManager.GetById(2);
2)
NewMember.FirstName = "NewFirstName";
NewMember.LanguageId = 1;
3)
currentMemberManager.Save(NewMember);
4)
public void Save2(Member newMember)
{
using (var Context = new NoxonEntities())
{
Member existingMember = Context.Member.First(c => c.Id == newMember.Id);
existingMember.FirstName = newMember.FirstName;
existingMember.Language = Context.Language.First(c => c.Id == newMember.LanguageId);
Context.SaveChanges();//In here I get the error below
}
}
The changes to the database were committed successfully, but an error
occurred while updating the object context. The ObjectContext might be
in an inconsistent state. Inner exception message: A referential
integrity constraint violation occurred: The property values that
define the referential constraints are not consistent between
principal and dependent objects in the relationship.
Note: You may suggest to SEND a different class (Ex: public class
MyMember) that has all the necessary properties and totally separated
from EF. But this requires much work to get all EF object converting
into my separate classes. Am I right?
I am hoping there is a way to Detach the entity just long enough for me to modify it and save the values into database. (Also, I tried the Detach method which updates no rows at all)
I've been trying to solve this for hours now.
Please, help me to understand it better, I really need a solution. Thank you so much to anyone how has some ideas.
Could you do something simple like detaching the entity, then attaching it to the context when you're ready to save?
MemberManager currentMemberManager = new MemberManager();
Member NewMember = currentMemberManager.GetById(2);
The get:
public Member GetById(int id)
{
var member = YourContext.Members.FirstOrDefault(m => m.id == id);
YourContext.Detach(member);
return member;
}
The save:
public void Save2(Member newMember)
{
using (var Context = new NoxonEntities())
{
Context.Attach(newMember);
Context.ObjectStateManager.ChangeObjectState(newMember, EntityState.Modified);
Context.SaveChanges();
}
}
UPDATE (2010-12-21): Completely rewrote this question based on tests that I've been doing. Also, this used to be a POCO specific question, but it turns out that my question isn't necessarily POCO specific.
I'm using Entity Framework and I've got a timestamp column in my database table that should be used to track changes for optimistic concurrency. I've set the concurrency mode for this property in the Entity Designer to "Fixed" and I'm getting inconsistent results. Here are a couple of simplified scenarios that demonstrate that concurrency checking works in one scenario but not in another.
Successfully throws OptimisticConcurrencyException:
If I attach a disconnected entity, then SaveChanges will throw an OptimisticConcurrencyException if there is a timestamp conflict:
[HttpPost]
public ActionResult Index(Person person) {
_context.People.Attach(person);
var state = _context.ObjectStateManager.GetObjectStateEntry(person);
state.ChangeState(System.Data.EntityState.Modified);
_context.SaveChanges();
return RedirectToAction("Index");
}
Does not throw OptimisticConcurrencyException:
On the other hand, if I retrieve a new copy of my entity from the database and I do a partial update on some fields, and then call SaveChanges(), then even though there is a timestamp conflict, I don't get an OptimisticConcurrencyException:
[HttpPost]
public ActionResult Index(Person person) {
var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
currentPerson.Name = person.Name;
// currentPerson.VerColm == [0,0,0,0,0,0,15,167]
// person.VerColm == [0,0,0,0,0,0,15,166]
currentPerson.VerColm = person.VerColm;
// in POCO, currentPerson.VerColm == [0,0,0,0,0,0,15,166]
// in non-POCO, currentPerson.VerColm doesn't change and is still [0,0,0,0,0,0,15,167]
_context.SaveChanges();
return RedirectToAction("Index");
}
Based on SQL Profiler, it looks like Entity Framework is ignoring the new VerColm (which is the timestamp property) and instead using the originally loaded VerColm. Because of this, it will never throw an OptimisticConcurrencyException.
UPDATE: Adding additional info per Jan's request:
Note that I also added comments to the above code to coincide with what I see in my controller action while working through this example.
This is the value of the VerColm in my DataBase prior to the update: 0x0000000000000FA7
Here is what SQL Profiler shows when doing the update:
exec sp_executesql N'update [dbo].[People]
set [Name] = #0
where (([Id] = #1) and ([VerColm] = #2))
select [VerColm]
from [dbo].[People]
where ##ROWCOUNT > 0 and [Id] = #1',N'#0 nvarchar(50),#1 int,#2 binary(8)',#0=N'hello',#1=1,#2=0x0000000000000FA7
Note that #2 should have been 0x0000000000000FA6, but it's 0x0000000000000FA7
Here is the VerColm in my DataBase after the update: 0x0000000000000FA8
Does anyone know how I can work around this problem? I'd like Entity Framework to throw an exception when I update an existing entity and there's a timestamp conflict.
Thanks
Explanation
The reason why you aren't getting the expected OptimisticConcurrencyException on your second code example is due to the manner EF checks concurrency:
When you retrieve entities by querying your db, EF remembers the value of all with ConcurrencyMode.Fixed marked properties by the time of querying as the original, unmodified values.
Then you change some properties (including the Fixed marked ones) and call SaveChanges() on your DataContext.
EF checks for concurrent updates by comparing the current values of all Fixed marked db columns with the original, unmodified values of the Fixed marked properties.
The key point here is that EF treats the update of you timestamp property as a normal data property update. The behavior you see is by design.
Solution/Workaround
To workaround you have the following options:
Use your first approach: Don't requery the db for your entity but Attach the recreated entity to your context.
Fake your timestamp value to be the current db value, so that the EF concurrency check uses your supplied value like shown below (see also this answer on a similar question):
var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
currentPerson.VerColm = person.VerColm; // set timestamp value
var ose = _context.ObjectStateManager.GetObjectStateEntry(currentPerson);
ose.AcceptChanges(); // pretend object is unchanged
currentPerson.Name = person.Name; // assign other data properties
_context.SaveChanges();
You can check for concurrency yourself by comparing your timestamp value to the requeried timestamp value:
var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
if (currentPerson.VerColm != person.VerColm)
{
throw new OptimisticConcurrencyException();
}
currentPerson.Name = person.Name; // assign other data properties
_context.SaveChanges();
Here is another approach that is a bit more generic and fits in the data layer:
// if any timestamps have changed, throw concurrency exception
var changed = this.ChangeTracker.Entries<>()
.Any(x => !x.CurrentValues.GetValue<byte[]>("Timestamp").SequenceEqual(
x.OriginalValues.GetValue<byte[]>("Timestamp")));
if (changed) throw new OptimisticConcurrencyException();
this.SaveChanges();
It just checks to see if the TimeStamp has changed and throws concurrency exception.
If it's EF Code first, then use code similar to below code. This will change the original TimeStamp loaded from db to the one from UI and will ensure OptimisticConcurrencyEception occurs.
db.Entry(request).OriginalValues["Timestamp"] = TimeStamp;
I have modified #JarrettV solution to work with Entity Framework Core. Right now it is iterating through all modified entries in context and looking for any mismatch in property marked as concurrency token. Works for TimeStamp (RowVersion) as well:
private void ThrowIfInvalidConcurrencyToken()
{
foreach (var entry in _context.ChangeTracker.Entries())
{
if (entry.State == EntityState.Unchanged) continue;
foreach (var entryProperty in entry.Properties)
{
if (!entryProperty.IsModified || !entryProperty.Metadata.IsConcurrencyToken) continue;
if (entryProperty.OriginalValue != entryProperty.CurrentValue)
{
throw new DbUpdateConcurrencyException(
$"Entity {entry.Metadata.Name} has been modified by another process",
new List<IUpdateEntry>()
{
entry.GetInfrastructure()
});
}
}
}
}
And we need only to invoke this method before we save changes in EF context:
public async Task SaveChangesAsync(CancellationToken cancellationToken)
{
ThrowIfInvalidConcurrencyToken();
await _context.SaveChangesAsync(cancellationToken);
}
Model #1 - This model sits in a database on our Dev Server.
Model #1 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png
Model #2 - This model sits in a database on our Prod Server and is updated each day by automatic feeds. alt text http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png
I have written what should be some simple code to sync my feed (Model #2) into my working DB (Model #1). Please note this is prototype code and the models may not be as pretty as they should. Also, the entry into Model #1 for the feed link data (mainly ClientID) is a manual process at this point which is why I am writing this simple sync method.
private void SyncFeeds()
{
var sourceList = from a in _dbFeed.Auto where a.Active == true select a;
foreach (RivWorks.Model.NegotiationAutos.Auto source in sourceList)
{
var targetList = from a in _dbRiv.Product where a.alternateProductID == source.AutoID select a;
if (targetList.Count() > 0)
{
// UPDATE...
try
{
var product = targetList.First();
product.alternateProductID = source.AutoID;
product.isFromFeed = true;
product.isDeleted = false;
product.SKU = source.StockNumber;
_dbRiv.SaveChanges();
}
catch (Exception ex)
{
string m = ex.Message;
}
}
else
{
// INSERT...
try
{
long clientID = source.Client.ClientID;
var companyDetail = (from a in _dbRiv.AutoNegotiationDetails where a.ClientID == clientID select a).First();
var company = companyDetail.Company;
switch (companyDetail.FeedSourceTable.ToUpper())
{
case "AUTO":
var product = new RivWorks.Model.Negotiation.Product();
product.alternateProductID = source.AutoID;
product.isFromFeed = true;
product.isDeleted = false;
product.SKU = source.StockNumber;
company.Product.Add(product);
break;
}
_dbRiv.SaveChanges();
}
catch (Exception ex)
{
string m = ex.Message;
}
}
}
}
Now for the questions:
In Model #2, the class structure for Auto is missing ClientID (see red circled area). Now, everything I have learned, EF creates a child class of Client and I should be able to find the ClientID in the child class. Yet, when I run my code, source.Client is a NULL object. Am I expecting something that EF does not do? Is there a way to populate the child class correctly?
Why does EF hide the child entity ID (ClientID in this case) in the parent table? Is there any way to expose it?
What else sticks out like the proverbial sore thumb?
TIA
1) The reason you are seeing a null for source.Client is because related objects are not loaded until you request them, or they are otherwise loaded into the object context. The following will load them explicitly:
if (!source.ClientReference.IsLoaded)
{
source.ClientReference.Load();
}
However, this is sub-optimal when you have a list of more than one record, as it sends one database query per Load() call. A better alternative is to the Include() method in your initial query, to instruct the ORM to load the related entities you are interested in, so:
var sourceList = from a in _dbFeed.Auto .Include("Client") where a.Active == true select a;
An alternative third method is to use something call relationship fix-up, where if, in your example for instance, the related clients had been queried previously, they would still be in your object context. For example:
var clients = (from a in _dbFeed.Client select a).ToList();
The EF will then 'fix-up' the relationships so source.Client would not be null. Obviously this is only something you would do if you required a list of all clients for synching, so is not relevant for your specific example.
Always remember that objects are never loaded into the EF unless you request them!
2) The first version of the EF deliberately does not map foreign key fields to observable fields or properties. This is a good rundown on the matter. In EF4.0, I understand foreign keys will be exposed due to popular demand.
3) One issue you may run into is the number of database queries requesting Products or AutoNegotiationContacts may generate. As an alternative, consider loading them in bulk or with a join on your initial query.
It's also seen as good practice to use an object context for one 'operation', then dispose of it, rather than persisting them across requests. There is very little overhead in initialising one, so one object context per SychFeeds() is more appropriate. ObjectContext implements IDisposable, so you can instantiate it in a using block and wrap the method's contents in that, to ensure everything is cleaned up correctly once your changes are submitted.