using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Linq; using DTS.Common.Utilities.Logging; // ReSharper disable InconsistentNaming // ReSharper disable once CheckNamespace namespace DTS.Common.Classes { 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["TagId"]); IsObsolete = Convert.ToBoolean(reader["Obsolete"]); Text = Convert.ToString(reader["TagText"]); } catch (Exception ex) { APILogger.Log(ex); } } public Tag(DataRow dr) { try { IsObsolete = Convert.ToBoolean(dr["Obsolete"]); ID = Convert.ToInt32(dr["TagId"]); Text = Convert.ToString(dr["TagFields"]); } 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; // } //} public static Tags GetTagsInstance(GetSqlCommandDelegate getSqlCommand) { if( null == _tagsInstance) { _tagsInstance = new Tags(getSqlCommand); } return _tagsInstance; } private static readonly object LOCK_OBJECT = new object(); public Tags(GetSqlCommandDelegate getSqlCommand) { _tagsLookup = new Dictionary(); UpdateList(getSqlCommand); } /// /// 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, GetSqlCommandDelegate getSqlCommand) { if (string.IsNullOrEmpty(tagText)) { return false; } // is it already in the dictionary? var tags = Tags.GetTagsInstance(getSqlCommand); if (tags.ContainsTag(tagText)) return false; tags.Commit(new Tag(tagText, Tag.INVALID_ID), getSqlCommand); return true; } /// /// Changes the ID of a tag during database migration /// /// /// public static bool MigrateTag(string tagText, GetSqlCommandDelegate getSqlCommand) { if (string.IsNullOrEmpty(tagText)) { return false; } Tags.GetTagsInstance(getSqlCommand).Commit(new Tag(tagText, Tag.INVALID_ID), getSqlCommand); return true; } /// /// commits a tag to db if doesn't already exist in db /// /// private void Commit(Tag tag, GetSqlCommandDelegate getSqlCommand) { try { if (-1 == GetIDFromTagText(tag.Text, getSqlCommand)) { Insert(tag, getSqlCommand); } else { UpdateAll(tag, getSqlCommand); } lock (LOCK_OBJECT) { _tagsLookup[tag.Text] = tag; } } catch (Exception ex) { APILogger.Log(ex); } } private void UpdateAll(Tag tag, GetSqlCommandDelegate getSqlCommand) { //nothing to do currently? (we don't let you rename or edit, or obsolete, or delete ...) tag.ID = GetTagIdFromText(tag.Text, getSqlCommand); } public delegate SqlCommand GetSqlCommandDelegate(bool bNewConnection); /// /// inserts a tag into the db /// /// private void Insert(Tag tag, GetSqlCommandDelegate getSqlCommand) { using (var cmd = getSqlCommand(true)) { try { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "sp_TagsInsert"; #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, GetSqlCommandDelegate getSqlCommand) { using (var cmd = getSqlCommand(true)) { try { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "sp_TagsGetId"; #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, GetSqlCommandDelegate getSqlCommand) { 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, getSqlCommand)); } return rv.ToArray(); } /// /// gets an ID for a given tag text FROM DB /// /// /// public static int GetIDFromTagText(string tagText, GetSqlCommandDelegate getSqlCommand) { return GetTagsInstance(getSqlCommand).GetTagIdFromText(tagText, getSqlCommand); } /// /// gets an array of ids given an array of tag texts /// /// /// public static int[] GetIDsFromTagText(string[] tagText, GetSqlCommandDelegate getSqlCommand) { if (null == tagText || 0 == tagText.Length) { return null; } return tagText.Select(s => s.TrimStart()).Select(text => GetIDFromTagText(text, getSqlCommand)).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, GetSqlCommandDelegate getSqlCommand) { if (0 > tagID || tagID == Tag.INVALID_ID) { // Not a valid ID return null; } return GetTagsInstance(getSqlCommand).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, GetSqlCommandDelegate getSqlCommand) { if (null == tagId || 0 == tagId.Length) { return new string [0]; } return tagId.Where(i => i != Tag.INVALID_ID).Select(i => GetTagTextFromID(i,getSqlCommand)).Where(tag => !string.IsNullOrWhiteSpace(tag)).ToArray(); } /// /// retrieves all tags and updates the cached dictionary of tags /// public void UpdateList(GetSqlCommandDelegate getSqlCommand) { lock (LOCK_OBJECT) { _tagsLookup.Clear(); using (var cmd = getSqlCommand(true)) { try { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "sp_TagsGet"; 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(); } } } } } }