views:

166

answers:

1

I have found many questions here on SO and articles all over the internet but none really tackled my problem.

My model looks like this (I striped all non essential Properties): alt text

Everyday or so "Play" gets updated (via a XML-file containing the information).

internal Play ParsePlayInfo(XDocument doc)
{
    Play play = (from p in doc.Descendants("Play")
    select new Play
    {
        Theatre = new Theatre()
        {
            //Properties
        },
        //Properties
        LastUpdate = DateTime.Now
    }).SingleOrDefault();

    var actors = (from a in doc.XPathSelectElement(".//Play//Actors").Nodes()
    select new Lecturer()
    {
        //Properties
    });

    var parts = (from p in doc.XPathSelectElement(".//Play//Parts").Nodes()
    select new Part()
    {
        //Properties
    }).ToList();

    foreach (var item in parts)
    {
        play.Parts.Add(item);
    }

    var reviews = (from r in doc.XPathSelectElement(".//Play//Reviews").Nodes()
    select new Review
    {
        //Properties
    }).ToList();

    for (int i = 0; i < reviews.Count(); i++)
    {
        PlayReviews pR = new PlayReviews()
        {
            Review = reviews[i],
            Play = play,
            //Properties
        };
        play.PlayReviews.Add(pR);
    }
    return play;
}

If I add this "play" via Add() every Childobject of Play will be inserted - regardless if some exist already. Since I need to update existing entries I have to do something about that. I guess the problem here is that I have no Id in the XML whatsoever.

As far as I can tell I have the following options:

  1. add/update the child entities in my PlayRepositories Add-Method
  2. restructure and rewrite ParsePlayInfo() so that get all the child entities first, add or update them and then create a new Play. The only problem I have here is that I wanted ParsePlayInfo() to be persistence ignorant, I could work around this by
  3. creating multiple parse methods (eg ParseActors() ) and assign them to play in my controller (I'm using ASP.net MVC) after everything was parsed and added

Currently I am implementing option 1 - but it feels wrong.

What I want to do is update entities that are already in the database and insert new ones that aren't. Do I have to call SaveChanges() before I Attach/Add Play? There must be a (relatively) easy solution. I'd appreciate it if someone could guide me in the right direction on this one.

+1  A: 

Well, since there is no answer yet I'll write one myself.

For those who are wondering, I got it to work - the code looks ugly as hell and I guess performance is even worse. But since there wont be many users and this method will only be called once a day at night anyway I am fine with it for now.

What did I do?

Well, I went with option 2 and 3.

private Play UpdatePlay()
{
    using (RepositoryContext context = new RepositoryContext())
    {
        HttpRequest http = new HttpRequest();
        PlayRepository rep = new PlayRepository(context);

        ActorRepository actRep = new ActorRepository(context);
    ReviewsRepository revRep = new ReviewsRepository(context);
        TheatreRepository insRep = new TheatreRepository(context);
        PartRepository partRep = new PartRepository(context);

        Parser p = new Parser();

        XDocument doc = http.GetPlayInfo();

        Theatre theatre = p.ParseTheatreInfo(doc);
        List<Actor> actors = p.ParseActorInfo(doc);
        List<PlayReviews> playReviews = p.ParseReviewsInfo(doc);

        for (int i = 0; i < actors.Count; i++)
        {
            actors[i] = actRep.AddOrUpdate(actors[i]);
        }
        for (int i = 0; i < playReviews.Count; i++)
        {
            playReviews[i].Reviews = revRep.AddOrUpdate(playReviews[i].Reviews);
        }

        theatre = insRep.AddOrUpdate(theatre);

        Play play = p.ParsePlayInfo(doc);

        List<Part> parts = GetParts(play);

        for (int i = 0; i < parts.Count; i++)
        {
            List<Actor> lec = (List<Actor>)parts[i].Actors;
            for (int j = 0; j < lec.Count; j++)
            {
                lec[j] = actRep.AddOrUpdate(lec[j]);
            }

        }

        play = rep.AddOrUpdate(play);
        context.LoadProperty(play, o => o.Theatre);
        context.LoadProperty(play, o => o.Actors);
        context.LoadProperty(play, o => o.PlayReviewss);
        context.LoadProperty(play, o => o.Parts);

        rep.Save();

        if (play.Theatre != theatre)
            play.Theatre = theatre;

        play = rep.AddParts(parts, play);

        play = rep.AddActor(actors, play);

        for (int i = 0; i < playReviews.Count; i++)
        {
            playReviews[i].Play = play;
            playReviews[i] = revRep.AddPlayInformation(playReviews[i]);
        }

        rep.Save();
        return play;
    }
}

(And on a side note, I just realized that I previously forgot to post this part of my code...)

As you can see, Save() gets called twice - and it gets worse when you consider whats going on in AddOrUpdate():

public Actor AddOrUpdate(Actor entity)
{
    Actor cur = context.Actors.Where(l => l.Name == entity.Name && l.Last_Name == entity.Last_Name).FirstOrDefault();
    if (cur == null)
    {
        context.Actors.AddObject(entity);
        return entity;
    }
    else
    {
        if (!entity.Mail.IsNullOrEmptyOrWhitespace() && cur.Mail != entity.Mail)
            cur.Mail = entity.Mail;
    //there are more of these...

        return cur;
    }
}

I can't believe that this is "the right" way to do this. It feels and looks just wrong. Maybe EF is to blame too, take

FirstEntityType first = new FirstEntityType();
first.Id = 2;
List<SecondType> list = CreateList(); //Let's say this returns a List with 10 elements
context.FirstEntityType.AddObject(first);

for (int i = 0; i < list.Count; i++)
{
    list[i].First = first;
}

//just for arguments sake a second for
for (int i = 0; i < list.Count; i++)
{
    context.SecondType.AddObject(list);
}

context.SaveChanges();

I haven't tested this particular piece of code but from what I experienced is that I'll end up with 10 new entries for SecondType and if I am not wrong 11 for FirstEntityType.

Why? Why isn't there a mechanism in EF that says "Hey, wait a minute - those are the same!"

Am I that wrong thinking that EF should behave like if I was using the db directly? In my example, I added "first" so I could assume that whenever I use "first" it is referenced. (I really hope my example works as described - don't have the time nor the desire to test it)

Christina Mayers
No, you won't get 11 entries for FirstEntityType, just one in your database. As long as you are within one ObjectContext identities are preserved when you add an object to the context. But when you execute this code a second time (within a new context) you would indeed get a second FirstEntityType (or a dababase exception when FirstEntityType.id is a non-autoincrementing primary key). So: Entity Framework actually says: "Those are the same!", but not thanks to the same id-field but because AddObject created a (temporary) EntityKey which identifies the object uniquely within one ObjectContext.
Slauma
I wish I still had that code because I was doing something like this and ended up with many entries. And since everything was nicely wrapped in a using ( context ) {} I am pretty sure I used the same context.Maybe it was something different that caused this behavior or I messed up somewhere. I was really frustrated after a while and wanted the job done.Maybe I'll do some testing if I have time.And nice to see that someone read it all :)
Christina Mayers