8

I am new to EF 4 and this is what I have done so far:

  • Create an edmx file based on my database
  • Create a code generation for my objects (POCO). Now I have a model1.tt, when expanded I see al my classes
  • Create a repository for each class, based on IRepository

Now, I am working with two objects, A and B. Object A has a property of type B. In my winform I have a combo filled with objects of type B. When the save button is pressed, a new instance of class A is created and all the properties are set. The object B property is set as follows:

objectA.myObjectB = (objectB)cmbBObjects.selectedItem;

Then I create a repository for objectA and call the save method. In this save method I have this code±

public bool Save(ObjectA obj)
{
  using(MyContext context = new MyContext())
  {
    context.objectAs.AddObject(obj);
    context.SaveChanges();
  }
}

This code, does save a new entry to the database, but it is also creating a new record for object B! I don't want this, because object B already exists in the database! (I have selected this one from the combobox).

This is how I fill my combobox:

In the objectB repository:

public IList<ObjectB> GetAll()
{
    using(MyContext context = new MyContext())
    {
        IList<ObjectB> objects = context.objectBs.ToList();
        return objects;
    }
}

In my form:

ObjectBRepository rep = new ObjectBRepository();
IList<ObjectB> objects = rep.GetAll;

cmbBObjects.Datasource = objects;
// etc..

So my question is, what do I have to do to save object A without creating a new record for objectB?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Martijn
  • 24,441
  • 60
  • 174
  • 261

2 Answers2

19

If you don't want insert objectB you must inform EF about it. When you call context.objectAs.AddObject(obj) for objectA you are saying: I want to insert objectA and all its dependencies. But obviously you don't want to save dependecies so you must either:

  • Load objectB from DB before adding it to objectA. In such case EF will know that objectB is existing object and it will not insert it again.
  • Attach objectB to context before adding it to objectA. ObjectB will be handled as existing but unchanged.
  • Set the state of objectB after inserting objectA. You can do that by calling: context.ObjectStateManager.ChangeObjectState(objectB, EntityState.Unchanged)

Example of the fist suggestion:

var id = objectB.Id;
objectA.myObjectB = context.ObjectBs.SingleOrDefault(o => o.Id == id);

Example of the second suggestion:

context.ObjectBs.Attach(objectB);
objectA.myObjectB = objectB;

Example of the third suggestion:

objectA.myObjectB = objectB;
context.ObjectAs.AddObject(objectA);
context.ObjectStateManager.ChangeObjectState(objectB, EntityState.Unchanged);
Luke
  • 22,826
  • 31
  • 110
  • 193
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • Thank you. I don't understand suggestion 1. The second suggestion: In the repository of objectA, do something like: `var obj = context.ObjectB.where(b=> b.Id == objectA.myObjectB.Id).firstOrDefault(); context.ObjectA.Attach(obj); ` Is this correct? – Martijn Mar 24 '11 at 08:31
  • @LadislavMrnka: Exactly, all possible options, and i suppose second option best one for above example. +1 – Andrew Orsich Mar 24 '11 at 08:45
  • I've tried your second solution, but slightly different. In my Save method of my ObjectA repository I had this: `context.objectBs.Attach(objectA.myObjectB); context.objectAs.AddObject(objectA); context.SaveChanges();` But here I got a key alreaydy exists exception at line `context.objectAs.AddObject(objectA);` Why was that? – Martijn Mar 24 '11 at 08:45
  • You must `Attach` `objectB` before you assing it to `objectA` otherwise you will attach both objects and you will not be able to add `objectA`. – Ladislav Mrnka Mar 24 '11 at 08:47
  • @Martijn: You should attach objectB to context after you get it from dataContext and only objectB not objectA. – Andrew Orsich Mar 24 '11 at 08:48
  • So, when I had done it this way: `var objB = objectA.objectB; context.objectBs.Attach(objB); //etc..` It had worked? – Martijn Mar 24 '11 at 08:50
  • The reason I am asking is because my Save method of objectA has one argument: Objecta: `public bool Save(objectA)` Otherwise I have to create a Save method with an argument for each object and I don't think this is the way to go.. – Martijn Mar 24 '11 at 08:52
  • @Martijn: no, *var b = (objectB)cmbBObjects.selectedItem;* than *context.objectBs.Attach(b);* than assign to objectA and call *Save* method. – Andrew Orsich Mar 24 '11 at 08:53
  • In case of using repositorie the best option is the first one. Otherwise you will have to modify your repositories to support these scenarios. – Ladislav Mrnka Mar 24 '11 at 08:55
  • Thnx. But is it desireable to have the context in the UI layer? I don't think so, because the UI layer is then directly talking to the data layer. So I have to solve this in my repository, right? – Martijn Mar 24 '11 at 09:00
  • Sure you will ask repositoryB to get the object and assign it to `objectA`. But be aware that you must share context among repositories. As you can see repository generally doesn't make things easier. – Ladislav Mrnka Mar 24 '11 at 09:08
  • And what is the best way to share the context between repositories? And if the repositories are shared, where is the context created for the first time? Who/what is responsible for that? – Martijn Mar 24 '11 at 09:11
  • Welcome to the word of architecture. The answers you are looking for are: unit of work and dependency injection / inversion of control. – Ladislav Mrnka Mar 24 '11 at 09:13
  • Hehe, thnx. Do you know a website that can point me at the right direction to start with? – Martijn Mar 24 '11 at 09:18
  • Actually no. I'm web / wcf developer so I don't know how this is usually handled in WinForms / WPF (ok I have an idea but it is just a theory). But you can ask another question about it and hopefully somebody else will help you. – Ladislav Mrnka Mar 24 '11 at 09:20
1

I suppose that problem in following row:

objectA.myObjectB = (objectB)cmbBObjects.selectedItem;

Because of result (objectB)cmbBObjects.selectedItem detached from datacontext entity framework create new instance. Instead this you can:

1.Assign objectB id to objectA

var b = (objectB)cmbBObjects.selectedItem;
objectA.myObjectBId = b.Id;

2.Or load objectB from dataContext and than assign to objectA:

var b = (objectB)cmbBObjects.selectedItem;
var dcB = context.ObjectBs.Single(x=> x.Id == b.Id);
objectA.myObjectB = dcB;

Just try my suggestions and come back with results, because i don't know exactly.

Hope this help.

Andrew Orsich
  • 52,935
  • 16
  • 139
  • 134
  • objectA does not have a objectB id directly, this must be set as follows (I suppose) `objectA..myObjectB.Id = b.Id` But, I think this will throw an argumentNull exception, because the `.myObjectB` property is not set. The second solution: I don't think I want my context in the winform, I'd like to keep that object in my repositories. – Martijn Mar 24 '11 at 08:26
  • @Martijn: Okay, than you should go second way suggested by me(load objectB from dataContext and assign to objectA). – Andrew Orsich Mar 24 '11 at 08:36