示例#1
0
        private async Task SyncSubject(MyPurdueSubject subject)
        {
            Console.WriteLine("\tSynchronizing " + subject.SubjectCode + " / " + subject.SubjectName + " ...");
            var dbSubjectId = await EnsureSubject(subject.SubjectCode, subject.SubjectName);

            // Prepare a cache of all courses
            Dictionary <Tuple <string, string>, Course> dbCourseCache;

            using (var db = new ApplicationDbContext())
            {
                dbCourseCache = db.Courses.AsNoTracking().Where(c => c.SubjectId == dbSubjectId)
                                .ToDictionary(c => new Tuple <string, string>(c.Number, c.Title));
            }
            Console.WriteLine("\t\tCached " + dbCourseCache.Count + " courses from database.");

            // Prepare a cache of all sections
            Dictionary <string, Section> dbSectionCache;

            using (var db = new ApplicationDbContext())
            {
                db.Configuration.ProxyCreationEnabled = false;
                dbSectionCache = db.Sections.AsNoTracking().Include(s => s.Class).Include(s => s.Meetings.Select(m => m.Room.Building))
                                 .Where(s => s.Class.TermId == TermId && s.Class.Course.SubjectId == dbSubjectId)
                                 .ToDictionary(s => s.CRN);
            }
            Console.WriteLine("\t\tCached " + dbSectionCache.Count + " sections from database.");

            // Fetch sections from MyPurdue
            Dictionary <string, MyPurdueSection> sectionsByCrn = null;

            sectionsByCrn = await Api.FetchSections(TermCode, subject.SubjectCode);

            Console.WriteLine("\t\tFetched " + sectionsByCrn.Count + " sections from MyPurdue.");

            // Group sections into classes by matching links
            var sectionFlatList = new List <MyPurdueSection>(sectionsByCrn.Values);
            var classGroups     = GroupSections(sectionFlatList);

            // Process and insert each class
            foreach (var classGroup in classGroups)
            {
                // All of the foreign keys we need to find in order to hook sections up
                Guid?dbCampusId = null;
                Guid?dbCourseId = null;
                Guid?dbClassId  = null;

                // Find the section that'll tell us course number and title (all of them should, but to be safe...)
                var sectionWithCourseInfo = classGroup.FirstOrDefault(cg => cg.Number.Length > 0 && cg.Title.Length > 0);
                if (sectionWithCourseInfo == null)
                {
                    Console.WriteLine("\t\t** WARNING: No course information (title / number) found for CRNs " + string.Join(", ", classGroup.Select(c => c.Crn)));
                    continue;
                }
                Console.WriteLine("\t\t" + subject.SubjectCode + sectionWithCourseInfo.Number + " - " + sectionWithCourseInfo.Title + " ...");

                // Do we have any of the sections cached?
                var cachedSection = classGroup.Where(s => dbSectionCache.ContainsKey(s.Crn))
                                    .Select(s => dbSectionCache[s.Crn]).FirstOrDefault();

                if (cachedSection != null)
                {
                    dbCampusId = cachedSection.Class.CampusId;
                    dbCourseId = cachedSection.Class.CourseId;
                    dbClassId  = cachedSection.ClassId;
                }

                // Find or create any elements we don't have ...

                if (dbCampusId == null)
                {
                    // Find / create the campus
                    var sectionWithCampus = classGroup.FirstOrDefault(cg => cg.CampusCode.Length > 0);
                    if (sectionWithCampus == null)
                    {
                        dbCampusId = await EnsureCampus("", ""); // No campus
                    }
                    else
                    {
                        dbCampusId = await EnsureCampus(sectionWithCampus.CampusCode, sectionWithCampus.CampusName);
                    }
                }

                if (dbCourseId == null)
                {
                    // Find / create the course
                    var courseKey = new Tuple <string, string>(sectionWithCourseInfo.Number, sectionWithCourseInfo.Title);
                    if (dbCourseCache.ContainsKey(courseKey))
                    {
                        dbCourseId = dbCourseCache[courseKey].CourseId;
                    }
                    else
                    {
                        using (var db = new ApplicationDbContext())
                        {
                            var dbCourse = new Course()
                            {
                                CourseId    = Guid.NewGuid(),
                                SubjectId   = dbSubjectId,
                                Title       = sectionWithCourseInfo.Title,
                                Number      = sectionWithCourseInfo.Number,
                                Description = sectionWithCourseInfo.Description,
                                CreditHours = classGroup.OrderByDescending(c => c.CreditHours).FirstOrDefault().CreditHours,
                                Classes     = new List <Class>()
                            };
                            db.Courses.Add(dbCourse);
                            await db.SaveChangesAsync();

                            dbCourseCache[courseKey] = dbCourse;
                            dbCourseId = dbCourse.CourseId;
                        }
                    }
                }

                if (dbClassId == null)
                {
                    // Find / create the class
                    using (var db = new ApplicationDbContext())
                    {
                        var dbClass = new Class()
                        {
                            ClassId  = Guid.NewGuid(),
                            CampusId = (Guid)dbCampusId,
                            CourseId = (Guid)dbCourseId,
                            TermId   = TermId,
                            Sections = new List <Section>()
                        };
                        db.Classes.Add(dbClass);
                        await db.SaveChangesAsync();

                        dbClassId = dbClass.ClassId;
                    }
                }

                // Update / create each section
                var updatedSections    = new List <Section>();
                var newSections        = new List <Section>();
                var instructorEntities = new Dictionary <Guid, Instructor>();
                var newMeetings        = new List <Meeting>();
                var deleteMeetings     = new List <Meeting>();
                foreach (var section in classGroup)
                {
                    Section dbSection;
                    if (dbSectionCache.ContainsKey(section.Crn))
                    {
                        dbSection = dbSectionCache[section.Crn];
                        var sectionChanged = false;
                        // Check for changes
                        if (dbSection.ClassId != dbClassId ||
                            dbSection.Type != section.Type ||
                            dbSection.Capacity != section.Capacity ||
                            dbSection.Enrolled != section.Enrolled ||
                            dbSection.RemainingSpace != section.RemainingSpace ||
                            dbSection.WaitlistCapacity != section.WaitlistCapacity ||
                            dbSection.WaitlistCount != section.WaitlistCount ||
                            dbSection.WaitlistSpace != section.WaitlistSpace)
                        {
                            dbSection.ClassId          = (Guid)dbClassId;
                            dbSection.Type             = section.Type;
                            dbSection.Capacity         = section.Capacity;
                            dbSection.Enrolled         = section.Enrolled;
                            dbSection.RemainingSpace   = section.RemainingSpace;
                            dbSection.WaitlistCapacity = section.WaitlistCapacity;
                            dbSection.WaitlistCount    = section.WaitlistCount;
                            dbSection.WaitlistSpace    = section.WaitlistSpace;
                            sectionChanged             = true;
                        }

                        // Update meetings
                        // First, delete any meetings that don't exist in the latest MyPurdue pull
                        foreach (var meeting in dbSection.Meetings.ToList())
                        {
                            // TODO: compare instructors
                            var matches = section.Meetings.Where(m =>
                                                                 m.Type == meeting.Type &&
                                                                 m.StartTime == meeting.StartTime &&
                                                                 m.EndTime == meeting.StartTime.Add(meeting.Duration) &&
                                                                 m.DaysOfWeek == meeting.DaysOfWeek &&
                                                                 m.BuildingCode == meeting.Room.Building.ShortCode &&
                                                                 m.RoomNumber == meeting.Room.Number
                                                                 );
                            if (matches.Count() <= 0)
                            {
                                dbSection.Meetings.Remove(meeting);
                                deleteMeetings.Add(meeting);
                                sectionChanged = true;
                            }
                        }
                        // Add all of the meetings that don't exist.
                        var existingMeetings = dbSection.Meetings.ToList(); // copy of list to avoid changes during loop
                        foreach (var meeting in section.Meetings)
                        {
                            var matches = existingMeetings.Where(m =>
                                                                 m.Type == meeting.Type &&
                                                                 m.StartTime == meeting.StartTime &&
                                                                 m.Duration == meeting.EndTime.Subtract(meeting.StartTime) &&
                                                                 m.DaysOfWeek == meeting.DaysOfWeek &&
                                                                 m.Room.Building.ShortCode == meeting.BuildingCode &&
                                                                 m.Room.Number == meeting.RoomNumber
                                                                 );
                            if (matches.Count() <= 0)
                            {
                                // make sure instructors exist
                                var instructors = new List <Instructor>();
                                foreach (var inst in meeting.Instructors)
                                {
                                    var instructorId = await EnsureInstructor(inst.Item2, inst.Item1);

                                    if (!instructorEntities.ContainsKey(instructorId))
                                    {
                                        instructorEntities[instructorId] = new Instructor()
                                        {
                                            InstructorId = instructorId
                                        };
                                    }
                                    instructors.Add(instructorEntities[instructorId]);
                                }

                                // make sure the building exists
                                var dbBuildingId = await EnsureBuilding((Guid)dbCampusId, meeting.BuildingCode, meeting.BuildingName);

                                // make sure the room exists
                                var dbRoomId = await EnsureRoom(dbBuildingId, meeting.RoomNumber);

                                // Create the actual meeting object
                                var newMeeting = new Meeting()
                                {
                                    MeetingId   = Guid.NewGuid(),
                                    Type        = meeting.Type,
                                    StartTime   = meeting.StartTime,
                                    Duration    = meeting.EndTime.Subtract(meeting.StartTime),
                                    Instructors = instructors,
                                    RoomId      = dbRoomId,
                                    DaysOfWeek  = meeting.DaysOfWeek,
                                    SectionId   = dbSection.SectionId,
                                    StartDate   = meeting.StartDate,
                                    EndDate     = meeting.EndDate
                                };
                                dbSection.Meetings.Add(newMeeting);
                                newMeetings.Add(newMeeting);
                                sectionChanged = true;
                            }
                        }

                        // If we've made any changes, flag this for committing to the DB later
                        if (sectionChanged)
                        {
                            foreach (var m in dbSection.Meetings.ToList())
                            {
                                m.Room = null;      // Prevent EF from trying to attach the entity
                            }
                            dbSection.Class = null; // Prevent EF from trying to find the entity
                            if (section.Meetings.Count > 0)
                            {
                                var startDate = section.Meetings.OrderBy(m => m.StartDate).Select(m => m.StartDate).First();
                                var endDate   = section.Meetings.OrderByDescending(m => m.EndDate).Select(m => m.EndDate).First();
                                dbSection.StartDate = startDate;
                                dbSection.EndDate   = endDate;
                            }
                            updatedSections.Add(dbSection);
                        }
                    }
                    else
                    {
                        // Section isn't cached. Create a new one.
                        dbSection = new Section()
                        {
                            SectionId        = Guid.NewGuid(),
                            CRN              = section.Crn,
                            ClassId          = (Guid)dbClassId,
                            Meetings         = new List <Meeting>(),
                            Type             = section.Type,
                            Capacity         = section.Capacity,
                            Enrolled         = section.Enrolled,
                            RemainingSpace   = section.RemainingSpace,
                            WaitlistCapacity = section.WaitlistCapacity,
                            WaitlistCount    = section.WaitlistCount,
                            WaitlistSpace    = section.WaitlistSpace
                        };

                        foreach (var meeting in section.Meetings)
                        {
                            // make sure instructors exist
                            var instructors = new List <Instructor>();
                            foreach (var inst in meeting.Instructors)
                            {
                                var instructorId = await EnsureInstructor(inst.Item2, inst.Item1);

                                if (!instructorEntities.ContainsKey(instructorId))
                                {
                                    instructorEntities[instructorId] = new Instructor()
                                    {
                                        InstructorId = instructorId
                                    };
                                }
                                instructors.Add(instructorEntities[instructorId]);
                            }

                            // make sure the building exists
                            var dbBuildingId = await EnsureBuilding((Guid)dbCampusId, meeting.BuildingCode, meeting.BuildingName);

                            // make sure the room exists
                            var dbRoomId = await EnsureRoom(dbBuildingId, meeting.RoomNumber);

                            var newMeeting = new Meeting()
                            {
                                MeetingId   = Guid.NewGuid(),
                                Type        = meeting.Type,
                                StartTime   = meeting.StartTime,
                                Duration    = meeting.EndTime.Subtract(meeting.StartTime),
                                Instructors = instructors,
                                RoomId      = dbRoomId,
                                DaysOfWeek  = meeting.DaysOfWeek,
                                Section     = dbSection,
                                StartDate   = meeting.StartDate,
                                EndDate     = meeting.EndDate
                            };
                            dbSection.Meetings.Add(newMeeting);
                            // We don't need to add to newMeetings here because this is a new section
                            // and EF will take care of adding new related entities.
                        }

                        if (section.Meetings.Count > 0)
                        {
                            var startDate = section.Meetings.OrderBy(m => m.StartDate).Select(m => m.StartDate).First();
                            var endDate   = section.Meetings.OrderByDescending(m => m.EndDate).Select(m => m.EndDate).First();
                            dbSection.StartDate = startDate;
                            dbSection.EndDate   = endDate;
                        }
                        newSections.Add(dbSection);
                    }
                }
                using (var db = new ApplicationDbContext())
                {
                    db.Configuration.ProxyCreationEnabled = false;
                    Console.WriteLine("\t\t\t{0} inserted / {1} updated sections", newSections.Count, updatedSections.Count);
                    // Attach all instructors as unchanged
                    foreach (var i in instructorEntities.Values)
                    {
                        db.Instructors.Attach(i);
                        db.Entry(i).State = EntityState.Unchanged;
                    }
                    foreach (var s in updatedSections)
                    {
                        db.Entry(s).State = EntityState.Modified;
                    }
                    foreach (var m in deleteMeetings)
                    {
                        //db.Meetings.Remove(db.Meetings.Find(m.MeetingId)); // Requires hitting DB twice.
                        var mid = new Meeting()
                        {
                            MeetingId = m.MeetingId
                        };
                        db.Meetings.Attach(mid);
                        db.Meetings.Remove(mid);
                    }
                    foreach (var m in newMeetings)
                    {
                        db.Meetings.Add(m);
                    }
                    foreach (var s in newSections)
                    {
                        db.Sections.Add(s);
                    }
                    await db.SaveChangesAsync();
                }
            }
        }