0

I am not sure exactly how to word the Title so I would also appreciate any thoughts on that part and will update this accordingly.

Using C#, standard console program.

I have a database object with a number of fields, any or all of them may be being changed. I have another object with the same fields that contains the new data. I can go through one by one assigning them and it work, but if i just do the entire thing at once it does not work.

Probably clearer with some code. the code that DOES work first: I have all of the data that I need updated in newStop.

I go out and get the data from the database and store it in findStop2. I then can go through all the fields one by one (only one is shown here) and assign the values from newStop to findStop2. When I do a db.SaveChanges everything works.

 var findStop2 = (from s in db.stop_details 
         where s.customer_id == customer_id && s.stop_id == newStop.stop_id 
         select s)
     .First();

 //if it did not error out it found it
 findStop2.ship_date = newStop.ship_date;

But there are a lot of fields. Why can I not just use:

findStop2 = newStop;

When I do this and do a SaveChanges there is no error thrown but the database also is not updated.

Solution -- Final changed code that works:

var findStop2 = (from s in db.stop_details where
                 s.customer_id == customer_id && s.stop_id == newStop.stop_id
                                 select s).First();
                //if it did not error out it found it
                newStop.id = findStop2.id;
                db.Entry(findStop2).CurrentValues.SetValues(newStop);
Joe Ruder
  • 2,122
  • 2
  • 23
  • 52
  • Take a look at this link. This is what you are looking for. http://stackoverflow.com/questions/13314666/using-automapper-to-update-an-existing-entity-poco – Brian P Jan 22 '16 at 19:32
  • When editing a question, please don't one-line code so it scrolls off the page. – Drew Kennedy Jan 22 '16 at 19:33
  • @ Brian -- read through the link twice and tried a couple of things but made no headway. The link is referring to ASP.net MVC - I am using a standard C# console program, is that the difference? – Joe Ruder Jan 22 '16 at 19:39

3 Answers3

1

If all your values are scalar, you can use (in EF6):

db.Entry(findStop2).CurrentValues.SetValues(newStop);

This will correctly track changes and everything

Jcl
  • 27,696
  • 5
  • 61
  • 92
0

If I understood your question right, you need to cast one object to another. To do this, you can write:

var myObject = db.stop_details
    .Where(sd => sd.customer_id == customer_id && sd.stop_id == newStop.stop_id)
    .Select(entity => new MyObject 
    {
        // Your properties here
    })
    .First();

Alternatively, if you perform such kind of operations a lot of time (and I guess you do) you can use some mapping libraries, for example AutoMapper. If you are using mapper all that you need is to write all possible configurations of how to map one object of concrete type to another and vice versa. The in all places you just need to call something like

var myObject = Mapper.Map<OutObjectType>(inputObject)

and that it. Mapper will return to you newly created object with all fields set up.

P.S. If you decide to use Automapper it works very good with inheritance, interfaces and other stuff which can be tricky to configure

Andrew
  • 1,474
  • 4
  • 19
  • 27
  • I don't think `newStop` would be a different type. Automapper will work (if you have defined a map) between two entities of the same type, definitely, but it's a dependency that makes no sense in this scenario, IMO – Jcl Jan 22 '16 at 19:51
0

The reason why you can't just assign an in-memory object to one retrieved from Entity Framework and then save it to the database is that you lose the hidden state that EF adds to all of the objects it creates. So after your findStop2 = newStop;, findStop2 is no longer tracked by your DB Context.

What you can do is set the state on your object directly before saving your changes.

context.Entry(newStop).State = EntityState.Modified;

Be careful when you do this as it will overwrite everything in the database to the properties on your newStop object. (You will have to set the ID, or else it will add another record for example.)

EDIT

As Jcl mentioned in the comics, you will need to do something with findStop2 as it is also being tracked and will therefore create a separate record in the database anyway. If you don't need any properties from it at all, you don't need to retrieve it from the database.

You can either call a context.Stops.Remove(findStop2), or do something similar to the above:

context.Entry(findStop2).State = EntityState.Detached;
krillgar
  • 12,596
  • 6
  • 50
  • 86
  • You'd need to remove `firstStop` from the database first if you set the same `Id`. – Jcl Jan 22 '16 at 19:51
  • Updated with a couple options. Thanks, @Jcl – krillgar Jan 22 '16 at 19:55
  • Even with your updates... I assume `findStop2` is a record which is in the database already. If you commit to the database with a newly added record with the same `Id` (even if it's detached from the context), the database will complain. You either need to completely delete it beforehand (which is risky if there are foreign keys), or just update its values, as I did on my answer, which seems what the OP wants (your `Remove` would do, but not the detaching... and also you may want to check the cascade deletions on foreign keys, etc.) – Jcl Jan 22 '16 at 19:58
  • What about if you didn't load it from the database? Wouldn't setting it as `Modified` make it do an update as opposed to an insert? (I didn't see your answer previously, and am unaware of that functionality, which is nice.) – krillgar Jan 22 '16 at 20:01
  • Uhm... I'm not sure, but yes, you are right, that could work actually – Jcl Jan 22 '16 at 20:02
  • yes, the database is complaining with "The property 'id' is part of the object's key information and cannot be modified." when trying to use the db.Entry method. I will try some more options in a bit, I gotta jump in a meeting. -- thanks -- JR – Joe Ruder Jan 22 '16 at 20:12
  • If you need to load it from the database, then go with Jcl's answer. If you just want to force it into the database, then setting the EntryState will work for you with the least work. Not pulling anything out of the database would definitely be more efficient. – krillgar Jan 22 '16 at 20:13
  • I don't need to load it from the database for any reason except to check if it exists, I am just needing to update the database record with whatever fields are changed. By changed, all the fields can be overwritten - findStop2 has all of the fields populated. But the record may not exist in the database yet, so that is why I am loading it first. If it finds it then I just want to update it, if it does not then I am creating a new record. I am thinking it may be eaiser to just step through all the fields one by one and assign them? – Joe Ruder Jan 22 '16 at 20:20
  • In that case, you can just do a `.Any()` instead of `.First()`. That should retrieve just a scalar int result from the database instead of all of the columns. Save you some processing that way. – krillgar Jan 22 '16 at 20:25
  • Perhaps to confirm that it doesn't load the entire object, you could change your query to `select s.Id`. That way it will only grab the one column. – krillgar Jan 22 '16 at 20:28
  • When changing it to Any() I get a compile time error of "The type 'bool' must be a reference type in order to use it as parameter 'TEntity' in the generic type or method 'System.Data.Entity.DbContext.Entry(TEntity)'" – Joe Ruder Jan 22 '16 at 20:35
  • If you do that instead, you're no longer receiving an object, but a single bool value. Therefore, you don't need to set its EntryState because there is nothing for the context to be aware of. – krillgar Jan 22 '16 at 20:36
  • 2
    @MostlyLucid for my answer to work, both object's Id field should be the same. The function I wrote is generally used to "update" the db entry with updated values from a dto (which is detached from the context). You could just set newStop's id to firstStop2 id and then SetValues – Jcl Jan 22 '16 at 20:40
  • @ JCL - that makes sense and works perfectly for what I am trying to do. Thank you all for sticking with me until I got this working. – Joe Ruder Jan 22 '16 at 20:45