/// <summary> /// Initializes a new instance of the <Typ>LearningStoreTransactionScope</Typ> class. LearningStore /// jobs executed within the scope use the specified transaction. /// </summary> /// <param name="transaction">Transaction used by LearningStore operations within the scope.</param> /// <exception cref="ArgumentNullException"><paramref name="transaction"/> is a null reference.</exception> /// <exception cref="InvalidOperationException">A transaction already exists in this scope (i.e., /// this scope is nested inside another).</exception> /// <remarks> /// See <Typ>LearningStoreTransactionScope</Typ> for more information. /// </remarks> public LearningStoreTransactionScope(Transaction transaction) { // Check parameters if (transaction == null) { throw new ArgumentNullException("transaction"); } // Fail if there's already a scope if (s_currentScope != null) { throw new InvalidOperationException(LearningStoreStrings.TransactionAlreadyExists); } m_scopeThread = Thread.CurrentThread; // Use the transaction passed to us m_dependentTransaction = transaction.DependentClone(DependentCloneOption.RollbackIfNotComplete); m_transaction = transaction; m_createdConnections = new Dictionary <string, SqlConnection>(); m_connections = m_createdConnections; // Enter the scope s_currentScope = this; }
public AddPackageResult AddPackage(PackageReader packageReader, PackageEnforcement packageEnforcement) { Utilities.ValidateParameterNonNull("packageReader", packageReader); Utilities.ValidateParameterNonNull("packageEnforcement", packageEnforcement); AddPackageReferenceResult refResult; string packageLocation = null; try { TransactionOptions options = new TransactionOptions(); options.IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead; using (LearningStoreTransactionScope scope = new LearningStoreTransactionScope(options)) { // Add the reference to the package refResult = AddPackageReference(packageReader, "$Invalid Value$", packageEnforcement); // Add the resource files and determine the location of them. The location is derived from the packageId. packageLocation = ImportFiles(refResult.PackageId, packageReader); // Update the package location in LearningStore UpdatePackageLocation(refResult.PackageId, packageLocation); scope.Complete(); } } catch (Exception) { try { // Delete files if necessary if (!String.IsNullOrEmpty(packageLocation)) { DeleteFiles(packageLocation); } } catch { // If the deletion failed, make sure the original exception is thrown to the caller } throw; } return(new AddPackageResult(refResult)); }
/// <summary> /// Initializes a new instance of the <Typ>LearningStoreTransactionScope</Typ> class with /// the specified options. /// </summary> /// <param name="transactionOptions">The transaction options to use if a new transaction is created (i.e., /// if this is the outermost scope). If a new transaction is not created (i.e., this scope is nested /// inside another), then the isolation level must either be unspecified or identical to the /// currently-existing transaction, and the timeout value must be zero. /// </param> /// <exception cref="System.InvalidOperationException">The transaction options specified are invalid.</exception> /// <remarks> /// See <Typ>LearningStoreTransactionScope</Typ> for more information. /// </remarks> public LearningStoreTransactionScope(TransactionOptions transactionOptions) { m_priorScope = s_currentScope; m_scopeThread = Thread.CurrentThread; if (m_priorScope == null) { // Create new transaction m_committableTransaction = new CommittableTransaction(transactionOptions); m_transaction = m_committableTransaction; m_createdConnections = new Dictionary <string, SqlConnection>(); m_connections = m_createdConnections; } else { // Transaction already exists // Verify that the passed-in options are compatible if ((transactionOptions.IsolationLevel != System.Transactions.IsolationLevel.Unspecified) && (transactionOptions.IsolationLevel != m_priorScope.Transaction.IsolationLevel)) { throw new InvalidOperationException(LearningStoreStrings.MismatchedIsolationLevel); } if (transactionOptions.Timeout != TimeSpan.Zero) { throw new InvalidOperationException(LearningStoreStrings.TimeoutMustBeZero); } m_dependentTransaction = m_priorScope.m_transaction.DependentClone(DependentCloneOption.RollbackIfNotComplete); m_transaction = m_priorScope.m_transaction; m_connections = m_priorScope.m_connections; } // Enter the scope if (m_priorScope != null) { m_priorScope.m_nextScope = this; } s_currentScope = this; }
/// <summary> /// Creates a new <Typ>ExecuteNavigator</Typ> object in memory and its representation in the database. /// </summary> /// <param name="store">A <Typ>LearningStore</Typ> object that references the database to use.</param> /// <param name="rootActivityId">The database identifier for the root activity (i.e. organization) of the activity tree to attempt.</param> /// <param name="learnerId">The database identifier for the learner information.</param> /// <param name="learnerAssignmentId">The database identifier for the learner assignment information. Only used in SLK.</param> /// <param name="loggingFlags">Flags specifying which actions to log.</param> /// <returns>A new <Typ>ExecuteNavigator</Typ> object.</returns> /// <exception cref="LearningStoreItemNotFoundException">Thrown if the learner ID or root activity ID is invalid, or if the package information cannot be found.</exception> static public ExecuteNavigator CreateExecuteNavigator(LearningStore store, long rootActivityId, long learnerId, long learnerAssignmentId, LoggingOptions loggingFlags) { ExecuteNavigator eNav = new ExecuteNavigator(); LearningStoreJob job = store.CreateJob(); // first add security Dictionary <string, object> securityParameters = new Dictionary <string, object>(); securityParameters[Schema.CreateAttemptRight.RootActivityId] = new LearningStoreItemIdentifier(Schema.ActivityPackageItem.ItemTypeName, rootActivityId); securityParameters[Schema.CreateAttemptRight.LearnerId] = new LearningStoreItemIdentifier(Schema.UserItem.ItemTypeName, learnerId); job.DemandRight(Schema.CreateAttemptRight.RightName, securityParameters); job.DisableFollowingSecurityChecks(); // first query for all information about the learner LearningStoreQuery query = store.CreateQuery(Schema.UserItem.ItemTypeName); query.AddColumn(Schema.UserItem.Id); query.AddColumn(Schema.UserItem.Name); query.AddColumn(Schema.UserItem.Language); query.AddColumn(Schema.UserItem.AudioCaptioning); query.AddColumn(Schema.UserItem.AudioLevel); query.AddColumn(Schema.UserItem.DeliverySpeed); query.AddCondition(Schema.UserItem.Id, LearningStoreConditionOperator.Equal, new LearningStoreItemIdentifier(Schema.UserItem.ItemTypeName, learnerId)); job.PerformQuery(query); // then query the package information query = store.CreateQuery(Schema.SeqNavActivityPackageView.ViewName); LearningStoreItemIdentifier rootId = new LearningStoreItemIdentifier(Schema.ActivityPackageItem.ItemTypeName, rootActivityId); query.AddColumn(Schema.SeqNavActivityPackageView.PackageId); query.AddColumn(Schema.SeqNavActivityPackageView.PackageFormat); query.AddColumn(Schema.SeqNavActivityPackageView.PackagePath); query.AddCondition(Schema.SeqNavActivityPackageView.Id, LearningStoreConditionOperator.Equal, rootId); job.PerformQuery(query); // then query for the activity tree query = store.CreateQuery(Schema.SeqNavActivityTreeView.ViewName); query.AddColumn(Schema.SeqNavActivityTreeView.DataModelCache); query.AddColumn(Schema.SeqNavActivityTreeView.ParentActivityId); query.AddColumn(Schema.SeqNavActivityTreeView.Id); query.AddColumn(Schema.SeqNavActivityTreeView.ObjectivesGlobalToSystem); query.AddCondition(Schema.SeqNavActivityTreeView.RootActivityId, LearningStoreConditionOperator.Equal, new LearningStoreItemIdentifier(Schema.ActivityPackageItem.ItemTypeName, rootActivityId)); job.PerformQuery(query); DataTable d; // used to store results of query ReadOnlyCollection <object> ids; // for this transaction we need to read from the activitypackageitem table and write new records to // the activityattemptitem and attemptitem tables TransactionOptions options = new TransactionOptions(); options.IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead; using (LearningStoreTransactionScope scope = new LearningStoreTransactionScope(options)) { // execute the query ReadOnlyCollection <object> c = job.Execute(); Utilities.Assert(c.Count == 3); if (((DataTable)c[0]).Rows.Count < 1) { throw new LearningStoreItemNotFoundException(Resources.InvalidLearnerId); } if (((DataTable)c[1]).Rows.Count < 1) { throw new LearningStoreItemNotFoundException(Resources.InvalidRootActivityId); } if (((DataTable)c[2]).Rows.Count < 1) { throw new LearningStoreItemNotFoundException(Resources.InvalidRootActivityId); } d = (DataTable)c[0]; // save learner information string learnerName = (string)d.Rows[0][Schema.UserItem.Name]; AudioCaptioning learnerCaption = (AudioCaptioning)d.Rows[0][Schema.UserItem.AudioCaptioning]; float learnerAudioLevel = (float)d.Rows[0][Schema.UserItem.AudioLevel]; float learnerDeliverySpeed = (float)d.Rows[0][Schema.UserItem.DeliverySpeed]; string learnerLanguage = (string)d.Rows[0][Schema.UserItem.Language]; d = (DataTable)c[1]; // save package information // we need to create the activity tree within the transaction because it may affect the data written // by means of the selection and randomization processes. eNav.m_packageFormat = (PackageFormat)d.Rows[0][Schema.SeqNavActivityPackageView.PackageFormat]; eNav.m_packageId = ((LearningStoreItemIdentifier)d.Rows[0][Schema.SeqNavActivityPackageView.PackageId]).GetKey(); eNav.m_packageLocation = (string)d.Rows[0][Schema.SeqNavActivityPackageView.PackagePath]; eNav.m_store = store; eNav.m_learnerId = learnerId; eNav.m_loggingFlags = loggingFlags; // we must set this here so that the Activity constructor doesn't try and load missing info eNav.m_attemptItemInformationIsValid = true; d = (DataTable)c[2]; // save data to create activity tree later, when we are done with sql processing eNav.AddActivities(d, rootActivityId, learnerName, learnerLanguage, learnerCaption, learnerAudioLevel, learnerDeliverySpeed); /* * // create activity objects and store them in a big dictionary * foreach(DataRow row in d.Rows) * { * Activity act = new Activity(eNav, ((LearningStoreItemIdentifier)row[Schema.SeqNavActivityTreeView.Id]).GetKey(), * ConvertLearningStoreXmlToXPathNavigator(row[Schema.SeqNavActivityTreeView.DataModelCache] as LearningStoreXml, true), * null, null, null, eNav.WrapAttachment, eNav.WrapAttachmentGuid, -1, * (bool)row[Schema.SeqNavActivityTreeView.ObjectivesGlobalToSystem], eNav.m_learnerId.ToString(System.Globalization.CultureInfo.InvariantCulture), * learnerName, learnerLanguage, learnerCaption, learnerAudioLevel, learnerDeliverySpeed); * eNav.m_activities[act.ActivityId] = act; * if(act.ActivityId == rootActivityId) * { * eNav.RootActivity = act; * if(!(row[Schema.SeqNavActivityTreeView.ParentActivityId] is DBNull)) * { * throw new LearningStoreItemNotFoundException(Resources.InvalidRootActivityId); * } * } * } * * // now that we have all the activities in a big dictionary, find all the parents to build the tree * // in theory this could be done in the first loop if I sort the query by parentid, but that's making a lot * // of assumptions about the structure of the database that I don't think are safe to make * foreach(DataRow row in d.Rows) * { * LearningStoreItemIdentifier parentId = row[Schema.SeqNavActivityTreeView.ParentActivityId] as LearningStoreItemIdentifier; * if (parentId != null) * { * long id = ((LearningStoreItemIdentifier)row[Schema.SeqNavActivityTreeView.Id]).GetKey(); * eNav.m_activities[id].Parent = eNav.m_activities[parentId.GetKey()]; * eNav.m_activities[parentId.GetKey()].AddChild(eNav.m_activities[id]); * } * } * * // make sure children of each parent are in the right order, and set Previous and Next pointer correctly. * eNav.SortActivityTree(); * * // step through the activity tree searching for selection and randomization instructions * // this must be done before the ActivityAttemptItems are saved because they may reorder * // and potentially even remove children. * Random rand = new Random(); * foreach(Activity a in eNav.Traverse) * { * if(!a.IsLeaf) * { * // if there's a valid selection instruction, remove excess * // child activities randomly * if(a.Sequencing.SelectionTiming == RandomizationTiming.Once && * a.Sequencing.RandomizationSelectCount > 0 && * a.Sequencing.RandomizationSelectCount < a.Children.Count) * { * int selCount = a.Sequencing.RandomizationSelectCount; * for(int i = 0 ; i < a.Children.Count ; ++i) * { * if(rand.Next(a.Children.Count) < selCount) * { * --selCount; * } * else * { * a.RemoveChild(i); * } * } * a.SortChildren(); * } * * // if there's a valid randomization instruction, randomize order of the children * // of this activity * if((a.Sequencing.RandomizationTiming == RandomizationTiming.Once || * a.Sequencing.RandomizationTiming == RandomizationTiming.OnEachNewAttempt) && * a.Sequencing.ReorderChildren) * { * List<Activity> randlist = new List<Activity>(); * while(a.Children.Count > 0) * { * int i = rand.Next(a.Children.Count); * randlist.Add((Activity)a.Children[i]); * a.RemoveChild(i); * } * for(int i = 0 ; i < randlist.Count ; ++i) * { * randlist[i].RandomPlacement = i; * a.AddChild(randlist[i]); * } * a.SortChildren(); * } * } * } * */ // create the attemptitem // fill in fields with no defaults job = store.CreateJob(); job.DisableFollowingSecurityChecks(); Dictionary <string, object> attempt = new Dictionary <string, object>(); attempt[Schema.AttemptItem.RootActivityId] = new LearningStoreItemIdentifier(Schema.ActivityPackageItem.ItemTypeName, rootActivityId); attempt[Schema.AttemptItem.LearnerId] = new LearningStoreItemIdentifier(Schema.UserItem.ItemTypeName, learnerId); if (learnerAssignmentId != 0) { attempt[Schema.AttemptItem.LearnerAssignmentId] = new LearningStoreItemIdentifier("LearnerAssignmentItem", learnerAssignmentId); } attempt[Schema.AttemptItem.PackageId] = new LearningStoreItemIdentifier(Schema.PackageItem.ItemTypeName, eNav.m_packageId); eNav.m_attemptStartTime = DateTime.UtcNow; attempt[Schema.AttemptItem.StartedTimestamp] = eNav.m_attemptStartTime; attempt[Schema.AttemptItem.LogDetailSequencing] = ((loggingFlags & LoggingOptions.LogDetailSequencing) == LoggingOptions.LogDetailSequencing); attempt[Schema.AttemptItem.LogFinalSequencing] = ((loggingFlags & LoggingOptions.LogFinalSequencing) == LoggingOptions.LogFinalSequencing); attempt[Schema.AttemptItem.LogRollup] = ((loggingFlags & LoggingOptions.LogRollup) == LoggingOptions.LogRollup); LearningStoreItemIdentifier attemptId = job.AddItem(Schema.AttemptItem.ItemTypeName, attempt, true); // create activityattemptitems for each activity in the tree foreach (Activity a in eNav.Traverse) { Dictionary <string, object> activity = new Dictionary <string, object>(); // fill in everything that has no default activity[Schema.ActivityAttemptItem.AttemptId] = attemptId; activity[Schema.ActivityAttemptItem.ActivityPackageId] = new LearningStoreItemIdentifier(Schema.ActivityPackageItem.ItemTypeName, a.ActivityId); if (a.RandomPlacement >= 0) { activity[Schema.ActivityAttemptItem.RandomPlacement] = a.RandomPlacement; // sibling activities are ordered by RandomPlacement first, // then by OriginalPlacment; RandomPlacement is -1 for all siblings if not randomization occurs } job.AddItem(Schema.ActivityAttemptItem.ItemTypeName, activity, true); } ids = job.Execute(); scope.Complete(); } // fill in some vital data for the navigator eNav.m_attemptId = ((LearningStoreItemIdentifier)ids[0]).GetKey(); int index = 1; foreach (Activity a in eNav.Traverse) { a.InternalActivityId = ((LearningStoreItemIdentifier)ids[index++]).GetKey(); } return(eNav); }
/// <summary> /// Ends the transaction scope. /// </summary> /// <exception cref="InvalidOperationException">A thread other than the creating thread /// is disposing the object.</exception> /// <remarks> /// See <Typ>LearningStoreTransactionScope</Typ> for more information. /// </remarks> public void Dispose() { // Skip if already disposed if (m_disposed) { return; } // Fail if this is being called on a different thread if (m_scopeThread != Thread.CurrentThread) { throw new InvalidOperationException(LearningStoreStrings.ScopeDisposedInInvalidThread); } if (m_nextScope != null) { // There are scopes "above" this one -- which means the user called // Dispose out-of-order. // First dispose the scope(s) above this one m_nextScope.Dispose(); if (m_nextScope != null) { throw new LearningComponentsInternalException("LSTR3100"); } if (s_currentScope != this) { throw new LearningComponentsInternalException("LSTR3110"); } } // Leave the scope if (m_priorScope != null) { m_priorScope.m_nextScope = null; } s_currentScope = m_priorScope; // Mark as disposed m_disposed = true; // Dispose the connections if necessary if (m_createdConnections != null) { foreach (SqlConnection connection in m_createdConnections.Values) { connection.Dispose(); } m_createdConnections.Clear(); } try { // Handle the dependent transaction if necessary if (m_dependentTransaction != null) { if (m_complete) { m_dependentTransaction.Complete(); } else { m_dependentTransaction.Rollback(); } } // Handle the committable transaction if necessary if (m_committableTransaction != null) { if (m_complete) { m_committableTransaction.Commit(); } else { m_committableTransaction.Rollback(); } } } finally { if (m_dependentTransaction != null) { m_dependentTransaction.Dispose(); } if (m_committableTransaction != null) { m_committableTransaction.Dispose(); } } }