using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Linq; using DTS.Common.Storage; using DTS.Common.Utilities.Logging; // ReSharper disable InconsistentNaming // ReSharper disable once CheckNamespace namespace DTS.Slice.Users { public class Tags { /// /// represents a single tag, which is composed of an ID and a string of text /// we don't currently let you obsolete, delete, or edit tags, just add /// public class Tag: ICloneable { public Tag(string tagText, int tagId) { ID = tagId; Text = tagText; IsObsolete = false; } public const int INVALID_ID = -1; public int ID { get; set; } public string Text { get; set; } public bool IsObsolete { get; set; } public Tag(Tag copy) { ID = copy.ID; Text = copy.Text; IsObsolete = copy.IsObsolete; } public Tag(IDataRecord reader) { try { ID = Convert.ToInt32(reader[DbOperations.Tags.TagFields.TagId.ToString()]); IsObsolete = Convert.ToBoolean(reader[DbOperations.Tags.TagFields.Obsolete.ToString()]); Text = Convert.ToString(reader[DbOperations.Tags.TagFields.TagText.ToString()]); } catch (Exception ex) { APILogger.Log(ex); } } public Tag(DataRow dr) { try { IsObsolete = Convert.ToBoolean(dr[DbOperations.Tags.TagFields.Obsolete.ToString()]); ID = Convert.ToInt32(dr[DbOperations.Tags.TagFields.TagId.ToString()]); Text = Convert.ToString(dr[DbOperations.Tags.TagFields.TagText.ToString()]); } catch (Exception ex) { APILogger.Log(ex); } } public object Clone() { return new Tag(this); } } private static Tags _tagsInstance; public static Tags TagsInstance { get { if (null == _tagsInstance) { _tagsInstance = new Tags(); } return _tagsInstance; } } private static readonly object LOCK_OBJECT = new object(); public Tags() { _tagsLookup = new Dictionary(); UpdateList(); } /// /// holds a cached collection of tags. This collection however is currently only populated on startup /// and not updated except explicitly when a user adds a tag /// // ReSharper disable once RedundantDefaultMemberInitializer private readonly Dictionary _tagsLookup = null; /// /// Adds a tag if not present in memory cache to db /// /// /// public static bool AddTag(string tagText) { if (string.IsNullOrEmpty(tagText)) { return false; } // is it already in the dictionary? if (TagsInstance.ContainsTag(tagText)) return false; TagsInstance.Commit(new Tag(tagText, Tag.INVALID_ID)); return true; } /// /// Changes the ID of a tag during database migration /// /// /// public static bool MigrateTag(string tagText) { if (string.IsNullOrEmpty(tagText)) { return false; } TagsInstance.Commit(new Tag(tagText, Tag.INVALID_ID)); return true; } /// /// commits a tag to db if doesn't already exist in db /// /// private void Commit(Tag tag) { try { if (-1 == GetIDFromTagText(tag.Text)) { Insert(tag); } else { UpdateAll(tag); } lock (LOCK_OBJECT) { _tagsLookup[tag.Text] = tag; } } catch (Exception ex) { APILogger.Log(ex); } } private void UpdateAll(Tag tag) { //nothing to do currently? (we don't let you rename or edit, or obsolete, or delete ...) tag.ID = GetTagIdFromText(tag.Text); } /// /// inserts a tag into the db /// /// private void Insert(Tag tag) { using (var cmd = DbOperations.GetSQLCommand(true)) { try { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = DbOperationsEnum.StoredProcedure.sp_TagsInsert.ToString(); #region params cmd.Parameters.Add(new SqlParameter("@TagText", SqlDbType.NVarChar, 255) {Value = tag.Text}); cmd.Parameters.Add(new SqlParameter("@Obsolete", SqlDbType.Bit) {Value = tag.IsObsolete}); var newIdParam = new SqlParameter("@new_id", SqlDbType.Int) {Direction = ParameterDirection.Output}; cmd.Parameters.Add(newIdParam); var errorNumberParam = new SqlParameter("@errorNumber", SqlDbType.Int) {Direction = ParameterDirection.Output}; cmd.Parameters.Add(errorNumberParam); var errorMessageParam = new SqlParameter("@errorMessage", SqlDbType.NVarChar, 250) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(errorMessageParam); #endregion params cmd.ExecuteNonQuery(); if (int.Parse(errorNumberParam.Value.ToString()) != 0) { //errorMessageParam.Value } tag.ID = int.Parse(newIdParam.Value.ToString()); } finally { cmd.Connection.Dispose(); } } } /// /// retrieves a string text associated with an ID FROM CACHED copies /// /// /// private string GetTagTextFromId(int id) { lock (LOCK_OBJECT) { if (_tagsLookup == null) return null; var e = _tagsLookup.GetEnumerator(); while (e.MoveNext()) { if (e.Current.Value.ID != id) continue; var returnText = e.Current.Value.Text; e.Dispose(); return returnText; } } return null; } /// /// Gets an ID for a given tag text FROM DB /// returns InvalidID if not found /// /// /// private int GetTagIdFromText(string text) { using (var cmd = DbOperations.GetSQLCommand(true)) { try { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = DbOperationsEnum.StoredProcedure.sp_TagsGetId.ToString(); #region params cmd.Parameters.Add(new SqlParameter("@TagText", SqlDbType.NVarChar, 255) {Value = text}); var tagIdParam = new SqlParameter("@TagId", SqlDbType.Int) {Direction = ParameterDirection.Output}; cmd.Parameters.Add(tagIdParam); #endregion params cmd.ExecuteNonQuery(); if (DBNull.Value.Equals(tagIdParam.Value)) { return Tag.INVALID_ID; } var tagIdtemp = int.Parse(tagIdParam.Value.ToString()); return tagIdtemp == 0 ? Tag.INVALID_ID : tagIdtemp; } finally { cmd.Connection.Dispose(); } } } /// /// returns true if a given tag text is contained in cached in memory tags /// /// /// public bool ContainsTag(string text) { lock (LOCK_OBJECT) { return _tagsLookup.ContainsKey(text); } } /// /// adds multiple tags at once /// note that tags will have their start trimmed before commiting /// /// /// public static bool[] AddRange(string[] tagText) { List rv = new List(); if (null == tagText || 0 == tagText.Length) { return null; } foreach (string s in tagText) { var tag = s.TrimStart(); rv.Add(AddTag(tag)); } return rv.ToArray(); } /// /// gets an ID for a given tag text FROM DB /// /// /// public static int GetIDFromTagText(string tagText) { return TagsInstance.GetTagIdFromText(tagText); } /// /// gets an array of ids given an array of tag texts /// /// /// public static int[] GetIDsFromTagText(string[] tagText) { if (null == tagText || 0 == tagText.Length) { return null; } return tagText.Select(s => s.TrimStart()).Select(text => GetIDFromTagText(text)).Where(id => id != Tag.INVALID_ID).ToArray(); } /// /// returns a string for a given id from memory cache /// returns null if it doesn't exist or is an invalid id /// /// /// public static string GetTagTextFromID(int tagID) { if (0 > tagID || tagID == Tag.INVALID_ID) { // Not a valid ID return null; } return TagsInstance.GetTagTextFromId(tagID); } /// /// returns an array of tag text given an array of tag ids. /// skips invalid tags or tag text /// /// /// public static string[] GetTagTextFromIDs(int[] tagId) { if (null == tagId || 0 == tagId.Length) { return new string [0]; } return tagId.Where(i => i != Tag.INVALID_ID).Select(i => GetTagTextFromID(i)).Where(tag => !string.IsNullOrWhiteSpace(tag)).ToArray(); } /// /// retrieves all tags and updates the cached dictionary of tags /// public void UpdateList() { lock (LOCK_OBJECT) { _tagsLookup.Clear(); using (var cmd = DbOperations.GetSQLCommand(true)) { try { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = DbOperationsEnum.StoredProcedure.sp_TagsGet.ToString(); cmd.Parameters.Add(new SqlParameter("@TagId", SqlDbType.Int) {Value = null}); var reader = cmd.ExecuteReader(); while (reader.Read()) { var t = new Tag(reader); if (t.ID == 0) continue; _tagsLookup[t.Text] = t; } reader.Close(); } finally { cmd.Connection.Dispose(); } } } } } }