1

I have an Entity called Cost, which has a required property of CostType

The Cost class has a GetNew() method which sets all the Cost's defaults:

public static GetNew()
{
    Cost cost = new Cost ();
    foo.CostType = Lists.CostTypes.FirstOrDefault();
    // Other Default Values

    return foo;
}

The Lists.CostTypes is a static list which is pulled from EF at startup and used in ComboBoxes

I am having problems setting the CostType within my code, after first setting it in the GetNew() method.

For example, the following code reads an excel file, and sets the default type based on a column in the Excel file, or null if it can't find a match

Cost cost = Cost.GetNew();
cost.CostType = Lists.CostTypes.FirstOrDefault(t => t.Name == row[0].ToString());

My problem is, during the Save operation I get the following error:

The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.

My Add Operation looks like this:

public static void AddObject(EntityObject obj, string entitySetName)
{
    context.AddObject(entitySetName, obj);
    context.SaveChanges();
}
  • If I remove the line of code that manually sets the Cost when it reads the excel file, the save works fine.
  • If I change the line of code to read Lists.Costs[2], it saves fine.
  • If I remove the line of code in GetNew() which sets the default, I get an error telling me that I violated the PK rule of CostTypes, meaning it's trying to insert the Cost Type.
  • Changing the ComboBox showing Type to something else still gives the same error.
  • After loading costs from the excel file, my regular Add/Edit forms throw the same error when I change the Type and try and save. If I don't load an excel file, they work fine.

I'm still learning Entity Framework, but so far it has been nothing but a frustration and a headache to use. Does someone know what my problem is and how I can fix it?

EDIT

Here's the info requested by Slauma. I am keeping it simple and excluding unrelated objects

  • Costs are in one table and CostTypes are in another table. In the database, the Costs.TypeId column is not allowed to be null, and is a Foreign Key to CostTypes. The Id field for both tables is auto-generated.

  • My EF model is just a generic one with the two database tables added. The only change I made to it was to rename some fields and remove the CostTypes.Costs Navigation Property.

  • The Excel file that gets imported maps most costs to their matching CostType.Name, however it IS possible that the string in the excel file doesn't match a CostType, so Lists.CostTypes.FirstOrDefault(t => t.Name == row[0].ToString()) can assign aNULLvalue to theCost.Typeproperty. That doesn't seem to be a problem though, because the form still comes up with the list of costs and their default selected items. Item's with aNULLCostType do not have an item selected in the CostTypeComboBox` and trigger a validation error that must be corrected before saving.

The code to load the CostType list is

public static List<T> GetList<T>(string sortProperty)
    where T : EntityObject
{
    using (var context = new TContext())
    {
        return ApplyOrder<T>(context.CreateObjectSet<T>(), sortProperty, "OrderBy").ToList();
    }
}

The ApplyOrder code can be found here.

The GetList method is called from

public static class Lists
{
    public static List<CostType> CostTypes { get; private set; }

    static Lists()
    {
        CostTypes = DAL<CostEntities>.GetList<CostType>("Name");
    }
}
Community
  • 1
  • 1
Rachel
  • 122,023
  • 59
  • 287
  • 465
  • `ClassA class` doesn't actually compile, does it? I hope this isn't actual code. – Henk Holterman Aug 22 '11 at 19:19
  • Does the `Type` entity refer back to `ClassA` (one-to-one relationship?)? You are not setting `class.Type` to `null`, so I guess the exception isn't complaining about the FK being `null` which belongs to `ClassA.Type` but some other FK. Are the entities in `Lists.Types` attached to the context? – Slauma Aug 22 '11 at 19:30
  • @Slauma The `Lists.Types` are attached, and the code runs fine if I comment out the line that sets the Type a 2nd time. It is a 1:1 relationship, and I removed the `Type.ClassA` navigation property. And @Henk lol no it's not actual code – Rachel Aug 22 '11 at 19:33
  • 1
    Is `Type.ClassA` a required navigation property? And does the property setter for `ClassA.Type` have relationship fixup code (would be the case by default with EF 4 POCOs generated from T4 template)? – Slauma Aug 22 '11 at 19:39
  • Just a note, I find pseudo-code with such glaring errors harder to read. Call them Foo and Bar, not class. And anyway, `class` is not a class but an object. – Henk Holterman Aug 22 '11 at 19:43
  • Renamed the objects. The `MyClass.Type` is a required property, and I am not using any kind of code generation other than EF defaults so there is no relationship fixup code. `Type.ClassA` does not exist since I deleted it. – Rachel Aug 22 '11 at 19:52
  • Ah, didn't read well that `Type.ClassA` doesn't exist, sorry. Are you sure that the problem isn't related to your specific `Lists.Bars[2]` entity? For example: If there is already a `Foo` in the DB which refers to `Lists.Bars[2]` it would have to be reset when you insert a new `Foo` which refers to this `Bar` now (because of 1:1 relationship). I could imagine that this might cause the exception because the relationship is required. You could test this by commenting out `foo.Bar = Lists.Bars.FirstOrDefault()` instead of `foo.Bar = Lists.Bars[2]`. Does it throw an exception then? – Slauma Aug 22 '11 at 20:13
  • @Slauma I updated my post with the actual code giving me trouble, and what I know about it. Also, I wrote that incorrectly. It's a `1:*` relationship, not a `1:1` – Rachel Aug 22 '11 at 20:18
  • Hm, the thing with the PK violation of `CostType` is very strange now. The `Lists.CostTypes` in your `GetNew` and your "pick from Excel file" line are really the same and still attached to the same context? Does fetching the `Lists.CostTypes` and later adding and saving changes happen in the *same context* which didn't get disposed in between? The insertion of a CostType can only happen if it isn't attached to the context where you save the changes in later. – Slauma Aug 22 '11 at 20:38
  • @Slauma Yes it's the same context. The DAL layer is a static class with a single Context which I don't dispose of, although the code that reads from the excel file runs asynchronously. It returns a `List`, which the main thread displays and allows the user to make modifications to, then the Save is executed on the main thread. `Lists.CostTypes` is pulled at startup, so it should already exist by the time the async thread gets to it. – Rachel Aug 23 '11 at 12:35
  • when you step through does cost.CostType = Lists.CostTypes.FirstOrDefault(t => t.Name == row[0].ToString()); actually set the nav property to an entity? Also, using a single static context is not recommended. http://stackoverflow.com/questions/888185/entity-framework-context-as-static – agradl Aug 23 '11 at 14:13
  • @shiznit123 Yes the `CostType` gets set correctly. I was aware that a single static context isn't recommended, but I was having issues working with detached entities and this is supposed to be a simple, small program for a single user that simply reads/writes data from a one database table, so I figured I'd just use a static one. – Rachel Aug 23 '11 at 14:43
  • My guess is the issue is related to your static list and/or static context. Have you ruled them out? – agradl Aug 23 '11 at 14:56
  • I found that if I import the excel records to a different class, let the user modify the classes, then on save move the object data into a new Cost, it works fine. Actually, it was giving me a PK error again but I found that was because I was looping through the new Costs, adding them to the Context, and calling `SaveChanges()` after each one, and calling `SaveChanges()` the 1st time was saving all the objects (due to the `CostType` nav property), so the 2nd time I called AddObject/SaveChanges it was trying to add an object that already existed – Rachel Aug 23 '11 at 15:01
  • @Shiznit123 I have not tried with a non-static context because I was having issues with detached entities. I don't see why it would be causing the problem though. – Rachel Aug 23 '11 at 15:02
  • @Rachel: A few points: 1) Can you please write down your `Cost` and `CostType` class, especially with their navigation properties. 2) Are the keys in `Cost` and `CostType` autogenerated identities or not? 3) In your line `cost.CostType = Lists.CostTypes.FirstOrDefault(t => t.Name == row[0].ToString());` are you sure that never `null` is returned? You said that the `Cost.CostType` property is **required**, so it must not happen that `null` gets assigned. If yes, you'll get the relationship exception in your question. 4) Can you also show the code how you load the `CostType` list at startup. – Slauma Aug 23 '11 at 16:11
  • @Slauma I added the requested information to my question – Rachel Aug 23 '11 at 16:27

2 Answers2

2

I figured it out.... it was a mix of a few different things

Creating a new Cost and setting the Type was adding the cost to the shared data context. If that Cost wasn't included in the list of costs to save, or it failed it's validation error, or the user cancelled out of the Import dialog, the cost still existed in context.ObjectStateManager._addedObjects, even though I never called AddObject or AttachObject. Once I realized that I started callling DeleteObject on costs that were not going to be saved and it cleared up the 1st error I was getting.

The 2nd error I was getting (duplicate PK) was because I was looping through my new Costs and calling AddObject and SaveChanges on each one. Since setting Cost.Type to an attached CostType was automatically adding my Cost to the context, the first cost to get saved was actually adding all the new Costs to the database while the 2nd cost was trying to call AddObject/SaveChanges on what EF saw as an object that already existed

Rachel
  • 122,023
  • 59
  • 287
  • 465
  • The relationship management in the property setters of `EntityObject` derived entities makes things so hard to follow. Good that you solved the problem finally. – Slauma Aug 23 '11 at 20:31
1

Here is not really a satisfying answer but a mix of guesses and open questions based on your infos in the question and in the comments to your question:

  • First of all: Your list Lists.CostTypes contains obviously entities which are detached from the context where you are later adding and saving new objects in. Because you have a using block: using (var context = new TContext()) you are retrieving your CostType entities in another context.

  • To tell EF that these CostType entities already exist in the database you must attach the entities to your second context (context.CostTypes.Attach(costType)) where you save your changes in (or use the same context in your method where you retrieve the list). I don't see in your code that you do this. (CostType is a navigation reference property, not a foreign key property, right?)

  • On the other hand when the CostType entities are not attached you should get duplicated CostTypes in your database because EF will consider them as new objects (to insert in the DB) when you call AddObject for your Cost entity since EF will always put the whole object graph of detached entities into Added state. Do you get duplicated CostTypes in the DB in your working examples? If not, something important is missing in your code snippets.

  • The last paragraph assumes that the key for CostType is autogenerated in the DB, as you said. If not, you would get a PK constraint violation instead of duplicated entities.

  • If the keys for CostType and Cost are really autogenerated identities, I am wondering where the PK violation you mentioned can come from. Every insertion would create a new unique primary key. There could never a PK violaton occur. Can you show the exception message in detail?

  • Did you check that all Cost entities you want to save really have a non-null CostType property (after the user has fixed all validation errors)? I cannot see any other possible reason in your code why you would get your "Relationship-could-not-be-changed-exception", except that at least for one of the Cost objects CostType is null.

Slauma
  • 167,754
  • 56
  • 385
  • 407
  • I think I figured it out. It is a range of problems that come from my inexperience with EF. I'll post the in an answer below. As far as the `using` statement, at the time I copy/pasted that code I was trying to use a non-static/shared context. Normally the `Lists.CostTypes` is attached to the context. – Rachel Aug 23 '11 at 19:24