/// <summary>
        /// Sets the value for a parameter in a query.
        /// </summary>
        /// <param name="parameterName">Name of the parameter</param>
        /// <param name="value">Value of the parameter.</param>
        /// <exception cref="ArgumentNullException"><paramref name="parameterName"/> is a null reference.</exception>
        /// <exception cref="InvalidOperationException">Invalid
        ///     <paramref name="value"/> value.</exception>
        /// <remarks>
        /// See the <Typ>LearningStoreQuery</Typ> documentation for more
        /// information about creating and executing queries.
        /// <p/>
        /// <paramref name="value"/> must contain a value
        ///     that is valid based on the type of the parameter specified by
        ///     <paramref name="parameterName"/>:
        /// <ul>
        /// <li><b>Boolean:</b> A System.Boolean, or an object that can be converted into
        ///     a System.Boolean using <Typ>/System.IConvertible</Typ>.</li>
        /// <li><b>ByteArray</b> A System.Byte array.</li>
        /// <li><b>DateTime:</b> A System.DateTime, or an object that can be converted into
        ///     a System.DateTime using <Typ>/System.IConvertible</Typ>.</li>
        /// <li><b>Double:</b> A System.Double, or an object that can be converted into
        ///     a System.Double using <Typ>/System.IConvertible</Typ>.</li>
        /// <li><b>Enumeration:</b> An System.Int32, or an object that can be converted into
        ///     a System.Int32 using <Typ>/System.IConvertible</Typ>.</li>
        /// <li><b>Guid</b> A System.Guid or a string representing a Guid.</li>
        /// <li><b>Item identifier:</b> A <Typ>LearningStoreItemIdentifier</Typ>
        ///     of the associated item type.</li>
        /// <li><b>Int32:</b> A System.Int32, or an object that can be converted into
        ///     a System.Int32 using <Typ>/System.IConvertible</Typ>.</li>
        /// <li><b>Single:</b> A System.Single, or an object that can be converted into
        ///     a System.Single using <Typ>/System.IConvertible</Typ>.</li>
        /// <li><b>String:</b> A System.String, or an object that can be converted into
        ///     a System.String using <Typ>/System.IConvertible</Typ>.</li>
        /// <li><b>Xml:</b> A <Typ>LearningStoreXml</Typ>.</li>
        /// </ul>
        /// <p/>
        /// <paramref name="value"/> must not contain a <Typ>LearningStoreItemIdentifier</Typ> returned
        ///     from <Mth>../LearningStoreJob.AddItem</Mth>.
        /// </remarks>
        public void SetParameter(string parameterName, object value)
        {
            // Check input parameters
            if (parameterName == null)
            {
                throw new ArgumentNullException("parameterName");
            }

            // Find the parameter in the view
            LearningStoreViewParameter parameter = null;

            if (!m_view.TryGetParameterByName(parameterName, out parameter))
            {
                throw new InvalidOperationException(
                          String.Format(CultureInfo.CurrentCulture, LearningStoreStrings.ParameterNotFound, parameterName));
            }

            // Cast the comparison value to the associated property type
            value = parameter.CastValue(value, m_locale,
                                        delegate(string reason, Exception innerException)
            {
                return(new InvalidOperationException(
                           String.Format(CultureInfo.CurrentCulture, LearningStoreStrings.InvalidParameterValueWithDescription,
                                         reason), innerException));
            }
                                        );

            // Verify special rules for LearningStoreItemIdentifier properties
            LearningStoreItemIdentifier idValue = value as LearningStoreItemIdentifier;

            if ((idValue != null) &&
                (!idValue.HasKey))
            {
                throw new InvalidOperationException(
                          LearningStoreStrings.InvalidIdParameterValue);
            }

            // Add the value
            int existingParameterValueIndex = m_parameterValues.FindIndex(delegate(LearningStoreQueryParameterValue existingParameterValue) {
                return(Object.ReferenceEquals(existingParameterValue.Parameter, parameter));
            });

            if (existingParameterValueIndex == -1)
            {
                m_parameterValues.Add(new LearningStoreQueryParameterValue(parameter, value));
            }
            else
            {
                m_parameterValues[existingParameterValueIndex] = new LearningStoreQueryParameterValue(parameter, value);
            }
        }
        /// <summary>
        /// Create a LearningStoreItemIdentifier from a result within a LogableSqlCommand
        /// </summary>
        /// <param name="command">The LogableSqlCommand.  On exit, the entire
        ///     current result has been read.</param>
        /// <param name="itemType">Information about the item type that should be read.</param>
        /// <returns>The<Typ>LearningStoreItemIdentifier</Typ></returns>
        /// <remarks>Assumes that the result has one rows.  Assumes that the
        ///     columns within the row contain exactly the correct data
        ///     for the <Mth>ReadItemIdentifierColumns</Mth> method.</remarks>
        public static LearningStoreItemIdentifier ReadItemIdentifierResult(LogableSqlCommand command, LearningStoreItemType itemType)
        {
            // Check input parameters
            if (command == null)
            {
                throw new LearningComponentsInternalException("LSTR1000");
            }
            if (itemType == null)
            {
                throw new LearningComponentsInternalException("LSTR1010");
            }

            if (!command.Read())
            {
                throw new LearningComponentsInternalException("LSTR1020");
            }

            // Start at the first column
            int startingColumn = 0;

            // Read the item
            LearningStoreItemIdentifier id = ReadItemIdentifierColumns(command,
                                                                       ref startingColumn, itemType);

            // Verify that the correct number of values are in the row
            if (command.GetFieldCount() != startingColumn)
            {
                throw new LearningComponentsInternalException("LSTR1030");
            }

            if (command.Read())
            {
                throw new LearningComponentsInternalException("LSTR1040");
            }

            return(id);
        }
예제 #3
0
        /// <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);
        }
예제 #4
0
        /// <summary>
        /// Adds all the activities to the navigator, and performs initial setup.
        /// </summary>
        /// <param name="d">DataTable containing the activities to add.</param>
        /// <param name="rootActivityId">Root activity identifier.</param>
        /// <param name="learnerName">Name of the learner.</param>
        /// <param name="learnerLanguage">Preferred language used by the learner.</param>
        /// <param name="learnerCaption">Preferred captioning setting used by the learner.</param>
        /// <param name="learnerAudioLevel">Preferred audio level setting for the learner.</param>
        /// <param name="learnerDeliverySpeed">Preferred delivery speed for the learner.</param>
        /// <remarks>
        /// This is only called by ExecuteNavigator().  Removed from the middle of that function to get
        /// FxCop to shut up.
        /// </remarks>
        private void AddActivities(DataTable d, long rootActivityId, string learnerName, string learnerLanguage,
                                   AudioCaptioning learnerCaption, float learnerAudioLevel, float learnerDeliverySpeed)
        {
            // create activity objects and store them in a big dictionary
            foreach (DataRow row in d.Rows)
            {
                Activity act = new Activity(this, ((LearningStoreItemIdentifier)row[Schema.SeqNavActivityTreeView.Id]).GetKey(),
                                            ConvertLearningStoreXmlToXPathNavigator(row[Schema.SeqNavActivityTreeView.DataModelCache] as LearningStoreXml, true),
                                            null, null, null, WrapAttachment, WrapAttachmentGuid, -1,
                                            (bool)row[Schema.SeqNavActivityTreeView.ObjectivesGlobalToSystem],
                                            DataModelWriteValidationMode.AllowWriteOnlyIfActive,
                                            m_learnerId.ToString(System.Globalization.CultureInfo.InvariantCulture),
                                            learnerName, learnerLanguage, learnerCaption, learnerAudioLevel, learnerDeliverySpeed);
                m_activities[act.ActivityId] = act;
                if (act.ActivityId == rootActivityId)
                {
                    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();
                    m_activities[id].Parent = m_activities[parentId.GetKey()];
                    m_activities[parentId.GetKey()].AddChild(m_activities[id]);
                }
            }

            // make sure children of each parent are in the right order, and set Previous and Next pointer correctly.
            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.
#if DEBUG
            Random rand;
            if (NavigatorData.Random != null)
            {
                rand = NavigatorData.Random;
            }
            else
            {
                rand = new Random();
            }
#else
            Random rand = new Random();
#endif
            foreach (Activity a in 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;
                        while (a.Children.Count > selCount)
                        {
                            a.RemoveChild(rand.Next(a.Children.Count));
                        }
                        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();
                    }
                }
            }
        }
        /// <summary>
        /// Add a condition that restricts the data returned in a query.
        /// </summary>
        /// <param name="columnName">Name of the column to be compared</param>
        /// <param name="conditionOperator">Condition operator</param>
        /// <param name="conditionValue">Value to be compared against.</param>
        /// <exception cref="ArgumentNullException"><paramref name="columnName"/> is a null reference.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="conditionOperator"/> is invalid.</exception>
        /// <exception cref="InvalidOperationException">
        ///     Invalid comparison, the column name wasn't found in the view, or an invalid
        ///     <paramref name="conditionValue"/> value.</exception>
        /// <remarks>
        /// See the <Typ>LearningStoreQuery</Typ> documentation for more
        /// information about creating and executing queries.
        /// <p/>
        /// <paramref name="conditionValue"/> must contain a value
        ///     that is valid based on the type of the column specified by
        ///     <paramref name="columnName"/>:
        /// <ul>
        /// <li><b>Boolean:</b> A System.Boolean, or an object that can be converted into
        ///     a System.Boolean using <Typ>/System.IConvertible</Typ>.</li>
        /// <li><b>ByteArray</b> A System.Byte array.</li>
        /// <li><b>DateTime:</b> A System.DateTime, or an object that can be converted into
        ///     a System.DateTime using <Typ>/System.IConvertible</Typ>.</li>
        /// <li><b>Double:</b> A System.Double, or an object that can be converted into
        ///     a System.Double using <Typ>/System.IConvertible</Typ>.</li>
        /// <li><b>Enumeration:</b> An System.Int32, or an object that can be converted into
        ///     a System.Int32 using <Typ>/System.IConvertible</Typ>.</li>
        /// <li><b>Guid</b> A System.Guid or a string representing a Guid.</li>
        /// <li><b>Item identifier:</b> A <Typ>LearningStoreItemIdentifier</Typ>
        ///     of the associated item type.</li>
        /// <li><b>Int32:</b> A System.Int32, or an object that can be converted into
        ///     a System.Int32 using <Typ>/System.IConvertible</Typ>.</li>
        /// <li><b>Single:</b> A System.Single, or an object that can be converted into
        ///     a System.Single using <Typ>/System.IConvertible</Typ>.</li>
        /// <li><b>String:</b> A System.String, or an object that can be converted into
        ///     a System.String using <Typ>/System.IConvertible</Typ>.</li>
        /// <li><b>Xml:</b> A <Typ>LearningStoreXml</Typ>.</li>
        /// </ul>
        /// <p/>
        /// <paramref name="conditionValue"/> must not contain a <Typ>LearningStoreItemIdentifier</Typ> returned
        ///     from <Mth>../LearningStoreJob.AddItem</Mth>.
        /// <p/>
        /// If <paramref name="columnName"/> refers to an XML column, then the following restrictions apply:
        /// <ul>
        /// <li><paramref name="conditionOperator"/> must be <Fld>../LearningStoreConditionOperator.Equal</Fld> or
        ///     <Fld>../LearningStoreConditionOperator.NotEqual</Fld></li>
        /// <li><paramref name="conditionValue"/> must be null.</li>
        /// </ul>
        /// <p/>
        /// If <paramref name="columnName"/> refers to an Guid column, then the following restrictions apply:
        /// <ul>
        /// <li><paramref name="conditionOperator"/> must be <Fld>../LearningStoreConditionOperator.Equal</Fld> or
        ///     <Fld>../LearningStoreConditionOperator.NotEqual</Fld></li>
        /// </ul>
        /// <p/>
        /// If <paramref name="conditionOperator"/> is <Fld>../LearningStoreConditionOperator.GreaterThan</Fld>,
        ///     <Fld>../LearningStoreConditionOperator.GreaterThanEqual</Fld>,
        ///     <Fld>../LearningStoreConditionOperator.LessThan</Fld>, or
        ///     <Fld>../LearningStoreConditionOperator.LessThanEqual</Fld>, then
        ///     <paramref name="conditionValue"/> must not contain null.
        /// <p/>
        /// Care must be taken when null is involved in a comparison.  In
        /// particular:<ul>
        /// <li>null == null: True</li>
        /// <li>null != null: False</li>
        /// <li>null == any other value: False</li>
        /// <li>null != any other value: True</li>
        /// <li>null &lt; any other value: False</li>
        /// <li>null &gt; any other value: False</li>
        /// </ul>
        /// </remarks>
        /// <example>
        /// The following code creates a new query that returns the id and name
        /// of the users with a name less than "Joe":
        /// <code language="C#">
        /// LearningStoreQuery query = store.CreateQuery("UserItem");
        /// query.AddColumn("Id");
        /// query.AddColumn("Name");
        /// query.AddCondition("Name", LearningStoreConditionOperator.LessThan, "Joe");
        /// </code>
        /// </example>
        public void AddCondition(string columnName, LearningStoreConditionOperator conditionOperator, object conditionValue)
        {
            // Check input parameters
            if (columnName == null)
            {
                throw new ArgumentNullException("columnName");
            }
            else if ((conditionOperator != LearningStoreConditionOperator.Equal) &&
                     (conditionOperator != LearningStoreConditionOperator.GreaterThan) &&
                     (conditionOperator != LearningStoreConditionOperator.GreaterThanEqual) &&
                     (conditionOperator != LearningStoreConditionOperator.LessThan) &&
                     (conditionOperator != LearningStoreConditionOperator.LessThanEqual) &&
                     (conditionOperator != LearningStoreConditionOperator.NotEqual))
            {
                throw new ArgumentOutOfRangeException("conditionOperator");
            }

            // Find the column in the view
            LearningStoreViewColumn column = null;

            if (!m_view.TryGetColumnByName(columnName, out column))
            {
                throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, LearningStoreStrings.ColumnNotFound, columnName));
            }

            // Cast the comparison value to the associated property type
            conditionValue = column.CastValue(conditionValue, m_locale,
                                              delegate(string reason, Exception innerException)
            {
                return(new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, LearningStoreStrings.InvalidConditionValueWithDescription, reason), innerException));
            }
                                              );

            // Fail if operator isn't compatible with null
            if ((conditionValue == null) &&
                (conditionOperator != LearningStoreConditionOperator.Equal) &&
                (conditionOperator != LearningStoreConditionOperator.NotEqual))
            {
                throw new InvalidOperationException(LearningStoreStrings.InvalidOperatorForNullConditionValue);
            }

            // Verify special rules for Xml properties
            if ((column.IsXml) && (conditionValue != null))
            {
                throw new InvalidOperationException(LearningStoreStrings.InvalidConditionValueForXmlColumn);
            }

            // Verify special rules for Guid properties
            if ((column.IsGuid) &&
                (conditionOperator != LearningStoreConditionOperator.Equal) &&
                (conditionOperator != LearningStoreConditionOperator.NotEqual))
            {
                throw new InvalidOperationException(LearningStoreStrings.InvalidConditionOperatorForGuidColumn);
            }

            // Verify special rules for LearningStoreItemIdentifier properties
            LearningStoreItemIdentifier idConditionValue = conditionValue as LearningStoreItemIdentifier;

            if ((idConditionValue != null) && (!idConditionValue.HasKey))
            {
                throw new InvalidOperationException(LearningStoreStrings.InvalidIdConditionValue);
            }

            // Add the condition
            LearningStoreCondition condition = new LearningStoreCondition(column, conditionOperator, conditionValue);

            m_conditions.Add(condition);
        }