Search code examples
databaselinq-to-sqlwindows-phone-7.1datacontextnotsupportedexception

How do I SubmitChanges on multiple tables that are related in LinqToSql?


I'm making a Windows Phone 7.1 application, and I'm having a lot of trouble submitting changes to my database. Here is the structure of the tables in my database:

Day <-1-----*-> TrainingSession <-many-----1-> Sport

So, a single day can have many training sessions, and a training session has one sport. A single sport can naturally be in many different training sessions.

The primary keys look like this: Day - DateTime TrainingSession - int (DB generated) Sport - nvarchar(200)

Sports will simply have attributes sportName, and an iconFileName.

I've set up Associations by putting EntitySet in both Day and Sport, and TrainingSession has EntityRef and EntityRef. I'm not 100% sure if Sport needs the EntitySet, so please correct me if I'm wrong. For the moment, I just hard-coded some sports in my Sport class for testing, and you'll see me retrieving an ObservableCollection to get those out.

Here is how I am trying to create a collection of days with training sessions, each training session having different sports:

    public void CreateDay(DateTime date)
    {
        FitPlanDataContext calendarDatabase = new FitPlanDataContext(FitPlanDataContext.ConnectionString);
        DateTime firstDate = new DateTime(date.Year, date.Month, 1);

        DayItem dayItem = new DayItem();
        dayItem.DateTime = firstDate;

        fillTestDayItemWithRandomData(dayItem);

        calendarDatabase.DayItems.InsertOnSubmit(dayItem);
        calendarDatabase.SubmitChanges();
    }

    private void fillTestDayItemWithRandomData(DayItem dayItem)
    {
        ObservableCollection<SportArt> sportArtCollection = SportArtController.GetAllSports();

        dayItem.TrainingSessions = new EntitySet<TrainingSession>();
        ObservableCollection<TrainingSession> trainingSessionCollection = new ObservableCollection<TrainingSession>();

        TrainingSession trainingSession1 = new TrainingSession();
        trainingSession1.DayItem = dayItem;
        trainingSession1.SportArt = sportArtCollection[1];
        trainingSessionCollection.Add(trainingSession1);

        TrainingSession trainingSession2 = new TrainingSession();
        trainingSession2.DayItem = dayItem;
        trainingSession2.SportArt = sportArtCollection[2];
        trainingSessionCollection.Add(trainingSession2);

        FitPlanDataContext calendarDatabase = new FitPlanDataContext(FitPlanDataContext.ConnectionString);
        calendarDatabase.TrainingSessions.InsertAllOnSubmit<TrainingSession>(trainingSessionCollection);
    }

This code is not working for me, and it is giving me the following error:

NotSupportedException was Unhandled: An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported.

Before I got this error, I was also getting NullReferenceExceptions.

I've been looking around for a solution, and I saw some people used Detach or workarounds with Attach, but I havent figured out how I could implement it to my code. Could anyone give me a helping hand with this?

Also, I thought the NullReferenceException could be coming from the fact that I'm not saving any sports to the database, could this be so?


Solution

  • So I messed around with it a lot, and today I finally found the solution I was looking for.

    It seems I asked the question wrong. I didn't include the query from the database, which is probably important to add. I actually omitted a lot of the code to keep things simple in my question, but looks like I omitted too much.

    Anyways, it turned out the way I setup the database structure was correct, and nothing had to be changed there.

    So here's what I did to get it working:
    -The call to the method that fills the day with training sessions needed to go after submitting changes about the day. This is because days have training sessions, and I cant save training sessions without the day already in the database.

    -I added using statements around the places where I need to use the datacontext instead of just creating an instance of the datacontext with a local variable. This ensures that the datacontext lives only in the scope of the using statment.

    (I changed the DateTime of the day to be the date given as the parameter to the method)

    public void CreateDay(DateTime date)
    {
        DayItem dayItem = new DayItem();
        dayItem.DateTime = date;
    
            using (FitPlanDataContext calendarDatabase = new FitPlanDataContext(FitPlanDataContext.ConnectionString))
            {
                calendarDatabase.DayItems.InsertOnSubmit(dayItem);
                calendarDatabase.SubmitChanges();                 
            }
    
        fillTestDayItemWithRandomData(dayItem);
    }
    

    Then, the changes to the method that fills the day with training sessions go like this:

    -I open a using statement where I instantiate a new datacontext. Then I access the database to retrieve a list of all the sports, and also the day that I need to update. I find the day I need to update by dayItemParameter. (Remember that retrieving from the database will give you a collection.)

    -I create my new training sessions and fill their properties. Note that the day I retrieved from the database is the value of a training session's property because the training session is a child of day, and needs to know who its parent day is.

    -I removed the instantiation of EntitySet because I realized that I already instantiate it in the constructor of the DayItem class.

    -Lastly, I add all the new training sessions into a collection, and save them all to the database at once using InsertAllOnSubmit(collection).

    private void fillTestDayItemWithRandomData(DayItem dayItemParameter)
    {
            using (FitPlanDataContext calendarDatabase = new FitPlanDataContext(FitPlanDataContext.ConnectionString))
            {
                ObservableCollection<SportArt> sportArtCollection;          
                var sportArts = (from SportArt sportArt in calendarDatabase.SportArts
                                     select sportArt);
                sportArtCollection = new ObservableCollection<SportArt>(sportArts);
    
                ObservableCollection<DayItem> dayItemCollection;
                var dayItems = (from DayItem dayItem in calendarDatabase.DayItems
                                where dayItem.DateTime == dayItemParameter.DateTime
                                select dayItem);
                dayItemCollection = new ObservableCollection<DayItem>(dayItems);
    
                DayItem foundDayItem = dayItemCollection[0];
    
                ObservableCollection<TrainingSession> trainingSessionCollection = new ObservableCollection<TrainingSession>();
    
                TrainingSession trainingSession1 = new TrainingSession();
                trainingSession1.DayItem = foundDayItem;
                trainingSession1.SportArt = sportArtCollection[1];
                trainingSessionCollection.Add(trainingSession1);
    
                TrainingSession trainingSession2 = new TrainingSession();
                trainingSession2.DayItem = foundDayItem;
                trainingSession2.SportArt = sportArtCollection[2];
                trainingSessionCollection.Add(trainingSession2);
    
    
                calendarDatabase.TrainingSessions.InsertAllOnSubmit<TrainingSession>(trainingSessionCollection);
                calendarDatabase.SubmitChanges();
            }
    }
    

    Conclusion:

    The main problem I was having was that I was trying to save training sessions to a day that wasn't submitted to the database. The next big problem (that I think many others have) is that reading and updating of an entity has to be in the same datacontext. So, you can't create a datacontext to retrieve a day, then use another datacontext to add a training session to that day (even if you saved the value of the day to a local variable). You need to retrieve the day and save training sessions to it all in the same data context.

    At the moment, my application is working, but it is quite sluggish. In this question, I'm asking about just one day, but in my actual program, I'm creating hundreds of days, which means a lot of opening and closing of the database. If anyone has suggestions to how I can optimize the process, I'm open ears.

    I realize and apologize that this post got so long, but writing it helped me to understand the situation with more depth, and I really hope that it'll help others too.