In order to get a sorted aggregated string, I wrote the CLR function below. However, it always returns empty instead of what I expected, just like "001, 002, 003". I tried to debug the CLR function in visual studio 2017, but threw the error message
The operation could not be completed. Unspecified error
Code:
[Serializable]
[SqlUserDefinedAggregate(
Format.UserDefined, //use clr serialization to serialize the intermediate result
Name = "CLRSortedCssvAgg", //aggregate name on sql
IsInvariantToNulls = true, //optimizer property
IsInvariantToDuplicates = false, //optimizer property
IsInvariantToOrder = false, //optimizer property
IsNullIfEmpty = false, //optimizer property
MaxByteSize = -1) //maximum size in bytes of persisted value
]
public class SortedCssvConcatenateAgg : IBinarySerialize
{
/// <summary>
/// The variable that holds all the strings to be aggregated.
/// </summary>
List<string> aggregationList;
StringBuilder accumulator;
/// <summary>
/// Separator between concatenated values.
/// </summary>
const string CommaSpaceSeparator = ", ";
/// <summary>
/// Initialize the internal data structures.
/// </summary>
public void Init()
{
accumulator = new StringBuilder();
aggregationList = new List<string>();
}
/// <summary>
/// Accumulate the next value, not if the value is null or empty.
/// </summary>
public void Accumulate(SqlString value)
{
if (value.IsNull || String.IsNullOrEmpty(value.Value))
{
return;
}
aggregationList.Add(value.Value);
}
/// <summary>
/// Merge the partially computed aggregate with this aggregate.
/// </summary>
/// <param name="other"></param>
public void Merge(SortedCssvConcatenateAgg other)
{
aggregationList.AddRange(other.aggregationList);
}
/// <summary>
/// Called at the end of aggregation, to return the results of the aggregation.
/// </summary>
/// <returns></returns>
public SqlString Terminate()
{
if (aggregationList != null && aggregationList.Count > 0)
{
aggregationList.Sort();
accumulator.Append(string.Join(CommaSpaceSeparator, aggregationList));
aggregationList.Clear();
}
return new SqlString(accumulator.ToString());
}
public void Read(BinaryReader r)
{
accumulator = new StringBuilder(r.ReadString());
}
public void Write(BinaryWriter w)
{
w.Write(accumulator.ToString());
}
}
You are close. Just need a few minor adjustments. Do the following and it will work (I tested it):
Remove all references to accumulator. It is not used.
Replace the Terminate(), Read(), and Write() methods with the following:
public SqlString Terminate()
{
string _Aggregation = null;
if (aggregationList != null && aggregationList.Count > 0)
{
aggregationList.Sort();
_Aggregation = string.Join(CommaSpaceSeparator, aggregationList);
}
return new SqlString(_Aggregation);
}
public void Read(BinaryReader r)
{
int _Count = r.ReadInt32();
aggregationList = new List<string>(_Count);
for (int _Index = 0; _Index < _Count; _Index++)
{
aggregationList.Add(r.ReadString());
}
}
public void Write(BinaryWriter w)
{
w.Write(aggregationList.Count);
foreach (string _Item in aggregationList)
{
w.Write(_Item);
}
}
That said, I'm not sure if this approach is faster or slower than the FOR XML approach, but a UDA certainly makes for a more readable query, especially if you need multiple aggregations.
Still, I should mention that starting in SQL Server 2017, this became a built-in function: STRING_AGG (which allows for sorting via the WITHIN GROUP (ORDER BY ... ) clause).
In your Accumulate and Merge, you're dealing with your aggregationList; in Read and Write you're dealing with accumulator. You should pick one or the other for all of them and use it. As I understand it, Read and Write are used when the engine needs to persist temporary results to a work table. For your case, when it does that, it's persisting only your empty StringBuilder.
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I'm not sure how I'm going to attack the traversing of my Huffman Tree. The tree is correct, I just have a hard time figuring out how to traverse it in a good way. For some reason, my traversing method gives no result...
UPDATE: Cleaned up the code, made it more Object Oriented
Node class:
public class Node
{
public int frekvens; //Frequency
public char tegn; //Symbol
public Node venstre; //Left child
public Node høyre; //Right child
public string s; //result string
public string resultat;
public Node (char c) // Node constructor containing symbol.
{
frekvens = 1;
tegn = c;
}
public Node (int f, Node venstre, Node høyre) // Node Constructor containing frequency and children
{
frekvens = f;
this.venstre = venstre;
this.høyre = høyre;
}
public Node (Node node) // Node constructor containing a node
{
frekvens = node.frekvens;
tegn = node.tegn;
this.venstre = venstre;
this.høyre = høyre;
}
public void ØkMed1() // Inkrement frequency by one
{
frekvens = frekvens + 1;
}
public char getVenstreTegn ()
{
return venstre.tegn;
}
public char getHøyreTegn ()
{
return venstre.tegn;
}
public int getVenstreFrekvens ()
{
return venstre.frekvens;
}
public int getHøyreFrekvens ()
{
return høyre.frekvens;
}
public int getFrekvens()
{
return frekvens;
}
public bool ErTegn(char c)
{
if ( c == tegn)
{
return false;
}
else
{
return true;
}
}
//Pretty sure this does not work as intended
public string traverser (Node n) //Traverse the tree
{
if (n.tegn != '\0') //If the node containes a symbol --> a leaf
{
resultat += s;
}
else
{
if (n.getVenstreTegn() == '\0') //If left child does not have a symbol
{
s += "0";
traverser(n.venstre);
}
if (n.getHøyreTegn() == '\0') //If right child does not have a symbol
{
s += "1";
traverser(n.høyre);
}
}
return resultat;
}
public string Resultat() //Used priviously to check if i got the correct huffman tree
{
string resultat;
resultat = "Tegn: " + Convert.ToString(tegn) +" frekvens: " + Convert.ToString(frekvens) + "\n";
return resultat;
}
}
Huffman_Tree Class:
public class Huffman_Tre
{
string treString;
List<Node> noder = new List<Node>();
public Node rot;
public void bygg (string input)
{
bool funnet; //Found
char karakter; //character
for (int i = 0; i < input.Length;i++) //Loops through string and sets character
//with coresponding freqeuncy in the node list
{
karakter = input[i];
funnet = false; //default
for (int j = 0; j< noder.Count; j++)
{
if (noder[j].ErTegn(karakter) == false) //if the character already exists
{
noder[j].ØkMed1(); //inkrement frequency by one
funnet = true;
break;
}
}
if (!funnet) //if the character does not exist
{
noder.Add(new Node(karakter)); //add the character to list
}
}
//Sorting node list acending by frequency
var sortertListe = noder.OrderBy(c => c.frekvens).ToList();
noder = sortertListe;
do
{
noder.Add(new Node((noder[0].frekvens + noder[1].frekvens), noder[0],noder[1]));
//Remove the leaf nodes
noder.RemoveAt(0);
noder.RemoveAt(0);
} while(noder.Count >= 2);
}
public Node getRot()
{
return rot;
}
public string visTre()
{
foreach (Node node in noder)
{
treString += node.Resultat();
}
return treString;
}
public bool erNull()
{
if (noder[0].tegn == '\0')
{
return true;
}
else
return false;
}
}
Main Program:
private void btnKomprimer_Click(object sender, System.Windows.RoutedEventArgs e)
{
string input; //The string input I want to compress
input = txtInput.Text; //initialize input to text input
input = input.ToLower();
txtOutput.Text = "";
Huffman_Tre tre = new Huffman_Tre();
tre.bygg(input);
Node rot = new Node(tre.getRot());
txtOutput.Text += rot.traverser(rot);
}
}
As I had a little bit of time left, I worked out an example of a Huffman tree, while playing with C# 6.0. It's not optimized (not even by far!), but it works fine as an example. And it will help you to look where your 'challenge' may arise. As my English is far better than my Scandinavian knowledge, I used English naming, I hope you don't mind.
First, let's start with the class that keeps the frequencies.
public sealed class HuffmanFrequencyTable
{
#region Properties
/// <summary>
/// Holds the characters and their corresponding frequencies
/// </summary>
public Dictionary<char, int> FrequencyTable { get; set; } = new Dictionary<char, int>();
#endregion
#region Methods
/// <summary>
/// Clears the internal frequency table
/// </summary>
public void Clear()
{
FrequencyTable?.Clear();
}
/// <summary>
/// Accepts and parses a new line (string) which is then
/// merged with the existing dictionary or frequency table
/// </summary>
/// <param name="line">The line to parse</param>
public void Accept(string line)
{
if (!string.IsNullOrEmpty(line))
{
line.GroupBy(ch => ch).
ToDictionary(g => g.Key, g => g.Count()).
ToList().
ForEach(x => FrequencyTable[x.Key] = x.Value);
}
}
/// <summary>
/// Performs a dump of the frequency table, ordering all characters, lowest frequency first.
/// </summary>
/// <returns>The frequency table in the format 'character [frequency]'</returns>
public override string ToString()
{
return FrequencyTable?.PrintFrequencies();
}
#endregion
}
Please note that the ToString() method uses an extension method that is able to 'dump' the contents of the Dictionary used. The extensions is located in a static class called Helpers and looks like this:
/// <summary>
/// Extension method that helps to write the contents of a generic Dictionary to a string, ordered by it's values and
/// printing the key and it's value between brackets.
/// </summary>
/// <typeparam name="TKey">Generic key</typeparam>
/// <typeparam name="TValue">Generic value type</typeparam>
/// <param name="dictionary">The dictionary</param>
/// <exception cref="ArgumentNullException">Throws an argument null exception if the provided dictionary is null</exception>
/// <returns></returns>
public static string PrintFrequencies<TKey, TValue>(this IDictionary<TKey, TValue> dictionary)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
var items = from kvp in dictionary
orderby kvp.Value
select kvp.Key + " [" + kvp.Value + "]";
return string.Join(Environment.NewLine, items);
}
Now, with this FrequencyTable in place, we can start looking on how to build up the Nodes. Huffman works with a binary tree, so it's best to generate a Node class having a left and right child node. I also took the liberty to perform the traversal algorithm here as well. This class is built up as following:
public sealed class HuffmanNode
{
#region Properties
/// <summary>
/// Holds the left node, if applicable, otherwise null
/// </summary>
public HuffmanNode Left { get; set; } = null;
/// <summary>
/// Holds the right node, if applicable, otherwise null
/// </summary>
public HuffmanNode Right { get; set; } = null;
/// <summary>
/// Holds the Character (or null) for this particular node
/// </summary>
public char? Character { get; set; } = null;
/// <summary>
/// Holds the frequency for this particular node, defaulted to 0
/// </summary>
public int Frequency { get; set; } = default(int);
#endregion
#region Methods
/// <summary>
/// Traverses all nodes recursively returning the binary
/// path for the corresponding character that has been found.
/// </summary>
/// <param name="character">The character to find</param>
/// <param name="data">The datapath (containing '1's and '0's)</param>
/// <returns>The complete binary path for a character within a node</returns>
public List<bool> Traverse(char? character, List<bool> data)
{
//Check the leafs for existing characters
if (null == Left && null == Right)
{
//We're at an endpoint of our 'tree', so return it's data or nothing when the symbol
//characters do not match
return (bool)character?.Equals(Character) ? data : null;
}
else
{
List<bool> left = null;
List<bool> right = null;
//TODO: If possible refactor with proper C# 6.0 features
if (null != Left)
{
List<bool> leftPath = new List<bool>(data);
leftPath.Add(false); //Add a '0'
left = Left.Traverse(character, leftPath); //Recursive traversal for child nodes within this left node.
}
if (null != Right)
{
List<bool> rightPath = new List<bool>(data);
rightPath.Add(true); //Add a '1'
right = Right.Traverse(character, rightPath); //Recursive traversal for childnodes within this right node
}
return (null != left) ? left : right;
}
}
#endregion
}
I use the Node class within the HuffmanTree class. As, logically, a tree is built up from nodes. The corresponding HuffmanTree is written this way:
public sealed class HuffmanTree
{
#region Fields
/// <summary>
/// Field for keeping the Huffman nodes in. Internally used.
/// </summary>
private List<HuffmanNode> nodes = new List<HuffmanNode>();
#endregion
#region Properties
/// <summary>
/// Holds the Huffman tree
/// </summary>
public HuffmanNode Root { get; set; } = null;
/// <summary>
/// Holds the frequency table for all parsed characters
/// </summary>
public HuffmanFrequencyTable Frequencies { get; private set; } = new HuffmanFrequencyTable()
/// <summary>
/// Holds the amount of bits after encoding the tree.
/// Primary usable for decoding.
/// </summary>
public int BitCountForTree { get; private set; } = default(int);
#endregion
#region Methods
/// <summary>
/// Builds the Huffman tree
/// </summary>
/// <param name="source">The source to build the Hufftree from</param>
/// <exception cref="ArgumentNullException">Thrown when source is null or empty</exception>
public void BuildTree(string source)
{
nodes.Clear(); //As we build a new tree, first make sure it's clean :)
if (string.IsNullOrEmpty(source))
throw new ArgumentNullException("source");
else
{
Frequencies.Accept(source);
foreach (KeyValuePair<char, int> symbol in Frequencies.FrequencyTable)
{
nodes.Add(new HuffmanNode() { Character = symbol.Key, Frequency = symbol.Value });
}
while (nodes.Count > 1)
{
List<HuffmanNode> orderedNodes = nodes.OrderBy(node => node.Frequency).ToList();
if (orderedNodes.Count >= 2)
{
List<HuffmanNode> takenNodes = orderedNodes.Take(2).ToList();
HuffmanNode parent = new HuffmanNode()
{
Character = null,
Frequency = takenNodes[0].Frequency + takenNodes[1].Frequency,
Left = takenNodes[0],
Right = takenNodes[1]
};
//Remove the childnodes from the original node list and add the new parent node
nodes.Remove(takenNodes[0]);
nodes.Remove(takenNodes[1]);
nodes.Add(parent);
}
}
Root = nodes.FirstOrDefault();
}
}
/// <summary>
/// Encodes a given string to the corresponding huffman encoding path
/// </summary>
/// <param name="source">The source to encode</param>
/// <returns>The binary huffman representation of the source</returns>
public BitArray Encode(string source)
{
if (!string.IsNullOrEmpty(source))
{
List<bool> encodedSource = new List<bool>();
//Traverse the tree for each character in the passed source (string) and add the binary path to the encoded source
encodedSource.AddRange(source.SelectMany(character =>
Root.Traverse(character, new List<bool>())
).ToList()
);
//For decoding, we might need the amount of bits to skip trailing bits.
BitCountForTree = encodedSource.Count;
return new BitArray(encodedSource.ToArray());
}
else return null;
}
/// <summary>
/// Decodes a given binary path to represent it's string value
/// </summary>
/// <param name="bits">BitArray for traversing the tree</param>
/// <returns></returns>
public string Decode(BitArray bits)
{
HuffmanNode current = Root;
string decodedString = string.Empty;
foreach (bool bit in bits)
{
//Find the correct current node depending on the bit set or not set.
current = (bit ? current.Right ?? current : current.Left ?? current);
if (current.IsLeaf())
{
decodedString += current.Character;
current = Root;
}
}
return decodedString;
}
#endregion
}
What is interesting in this code, is that I decided to use BitArrays that will hold the binary paths for the tree when it's build up. The public BitArray Encode(string source) method here contains a dirty hack. I keep track of the total amount of bits used for encoding and store this within the BitCountForTree property. When performing a decode, I'll use this property to remove any trailing bits that may arise. There is a way nicer way to perform this, but I'll leave that open for you to find out.
Also, this class makes use of an extension method written for the HuffmanNode. It's a simple one though:
/// <summary>
/// Determines whether a given Huffman node is a leaf or not.
/// A node is considered to be a leaf when it has no childnodes
/// </summary>
/// <param name="node">A huffman node</param>
/// <returns>True if no children are left, false otherwise</returns>
public static bool IsLeaf(this HuffmanNode node)
{
return (null == node.Left && null == node.Right);
}
This extension method is convenient to determine whether or not a given node is actually a leafnode. A leaf is a node which has no childnodes left and thus the end of a binary tree (or better a branch of that tree).
Now the interesting part, how do I make things work here. I have build a Windows Forms application having 3 textboxes. One for the actual input, one for the binary (encoded) output and the last for showing the compressed result.
I also placed two simple buttons, one to perform the Huffman encoding and one for the Huffman decoding.
The Huffman encoding method is written as following (just in the eventhandler of the encode button):
string input = tbInput.Text;
Tree.BuildTree(input); //Build the huffman tree
BitArray encoded = Tree.Encode(input); //Encode the tree
//First show the generated binary output
tbBinaryOutput.Text = string.Join(string.Empty, encoded.Cast<bool>().Select(bit => bit ? "1" : "0"));
//Next, convert the binary output to the new characterized output string.
byte[] bytes = new byte[(encoded.Length / 8) + 1];
encoded.CopyTo(bytes, 0);
tbOutput.Text = Encoding.Default.GetString(bytes); //Write the compressed output to the textbox.
Note that the encoded binary string does not have any trailing bits. I'll leave that up to the Encoding mechanisms of C#. The downside of this, is that I have to keep track of it when decoding.
The decoding is not too hard now as well. Although, for this example, I am making use of the compressed output generated by the encoding code placed above. Also, I am assuming that the Huffman tree (and it's frequency table!!!) are already built. Normally, the frequency table is stored within the compressed file, so that it can be rebuild.
//First convert the compressed output to a bit array again again and skip trailing bits.
bool[] boolAr = new BitArray(Encoding.Default.GetBytes(tbOutput.Text)).Cast<bool>().Take(Tree.BitCountForTree).ToArray();
BitArray encoded = new BitArray( boolAr );
string decoded = Tree.Decode(encoded);
MessageBox.Show(decoded, "Decoded result: ", MessageBoxButtons.OK, MessageBoxIcon.Information);
Please pay attention to the dirty hack I created, as the Encoding.Default.GetBytes(tbOutput.Text) surely generates a byte array, it may contain trailing bits which need not to be decoded. Hence that I only take the amount of bits that I will actually need, based upon the rebuild tree.
So when running, my example provides the following output, when using the 'world renown sentence' "The quick brown fox jumps over the lazy programmer":
After pressing the "Huff encode" button:
And after pressing the "Huff decode" button:
Now this code can really use some optimizations, as you might consider using Arrays instead of Dictionaries. There are more, but it's up to you for consideration.
I have a string array of some file paths:
path/to/folder/file.xxx
path/to/other/
path/to/file/file.xx
path/file.x
path/
How can I convert this list to a tree structure? So far I have the following:
/// <summary>
/// Enumerates types of filesystem nodes.
/// </summary>
public enum FilesystemNodeType
{
/// <summary>
/// Indicates that the node is a file.
/// </summary>
File,
/// <summary>
/// Indicates that the node is a folder.
/// </summary>
Folder
}
/// <summary>
/// Represents a file or folder node.
/// </summary>
public class FilesystemNode
{
private readonly ICollection<FilesystemNode> _children;
/// <summary>
/// Initializes a new instance of the <see cref="FilesystemNode"/> class.
/// </summary>
public FilesystemNode()
{
_children = new LinkedList<FilesystemNode>();
}
/// <summary>
/// Gets or sets the name of the file or folder.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the full path to the file or folder from the root.
/// </summary>
public string Path { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the node is a file or folder.
/// </summary>
public FilesystemNodeType Type { get; set; }
/// <summary>
/// Gets a list of child nodes of this node. The node type must be a folder to have children.
/// </summary>
public ICollection<FilesystemNode> Children
{
get
{
if (Type == FilesystemNodeType.Folder)
return _children;
throw new InvalidOperationException("File nodes cannot have children");
}
}
}
I'm just a bit at a loss at how to actually split up the paths and all. Any path that ends with a / is a directory, any one that doesn't, is not.
Also, while my input will always contain a path to the folder, how would I account for that situation if it did not?
For example, if I had the input:
path/to/file.c
path/file.c
path/
How would I account for the fact that path/to/ is not in the input?
Here is a solution that generates a nested dictionary of NodeEntry items (you can substitute your file info class as needed):
public class NodeEntry
{
public NodeEntry()
{
this.Children = new NodeEntryCollection();
}
public string Key { get; set; }
public NodeEntryCollection Children { get; set; }
}
public class NodeEntryCollection : Dictionary<string, NodeEntry>
{
public void AddEntry(string sEntry, int wBegIndex)
{
if (wBegIndex < sEntry.Length)
{
string sKey;
int wEndIndex;
wEndIndex = sEntry.IndexOf("/", wBegIndex);
if (wEndIndex == -1)
{
wEndIndex = sEntry.Length;
}
sKey = sEntry.Substring(wBegIndex, wEndIndex - wBegIndex);
if (!string.IsNullOrEmpty(sKey)) {
NodeEntry oItem;
if (this.ContainsKey(sKey)) {
oItem = this[sKey];
} else {
oItem = new NodeEntry();
oItem.Key = sKey;
this.Add(sKey, oItem);
}
// Now add the rest to the new item's children
oItem.Children.AddEntry(sEntry, wEndIndex + 1);
}
}
}
}
To use the above, create a new collection:
NodeEntryCollection cItems = new NodeEntryCollection();
then, for each line in your list:
cItems.AddEntry(sLine, 0);
I have been inspired from competent_tech's answer and replaced the Dictionary<string, NodeEntry> with a "simple" ObservableCollection<NodeEntry> as the "Key" information would be stored twice in this Dictionary: once as key of the Dictionary and once as public property in the NodeEntry class.
So my sample based on the "competent_tech" code looks like the following:
public class NodeEntryObservableCollection : ObservableCollection<NodeEntry>
{
public const string DefaultSeparator = "/";
public NodeEntryObservableCollection(string separator = DefaultSeparator)
{
Separator = separator; // default separator
}
/// <summary>
/// Gets or sets the separator used to split the hierarchy.
/// </summary>
/// <value>
/// The separator.
/// </value>
public string Separator { get; set; }
public void AddEntry(string entry)
{
AddEntry(entry, 0);
}
/// <summary>
/// Parses and adds the entry to the hierarchy, creating any parent entries as required.
/// </summary>
/// <param name="entry">The entry.</param>
/// <param name="startIndex">The start index.</param>
public void AddEntry(string entry, int startIndex)
{
if (startIndex >= entry.Length)
{
return;
}
var endIndex = entry.IndexOf(Separator, startIndex);
if (endIndex == -1)
{
endIndex = entry.Length;
}
var key = entry.Substring(startIndex, endIndex - startIndex);
if (string.IsNullOrEmpty(key))
{
return;
}
NodeEntry item;
item = this.FirstOrDefault(n => n.Key == key);
if (item == null)
{
item = new NodeEntry(Separator) { Key = key };
Add(item);
}
// Now add the rest to the new item's children
item.Children.AddEntry(entry, endIndex + 1);
}
}
public class NodeEntry
{
public string Key { get; set; }
public NodeEntryObservableCollection Children { get; set; }
public NodeEntry(string separator = NodeEntryObservableCollection.DefaultSeparator)
{
Children = new NodeEntryObservableCollection(separator);
}
}
This helps me in binding the data in a TreeView like this:
<TreeView Name="trvMyTreeView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:NodeEntry}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Key}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
With a sample code behind like this:
IList<string> pathes = new List<string>
{
"localhost",
"remotehost.levelD.levelDB",
"localhost.level1.level11",
"localhost.level1",
"remotehost.levelD.levelDA",
"localhost.level2.level22",
"remotehost.levelA",
"remotehost",
"remotehost.levelB",
"remotehost.levelD",
"localhost.level2",
"remotehost.levelC"
};
SortedSet<string> sortedPathes = new SortedSet<string>(pathes);
var obsCollection = new NodeEntryObservableCollection(".");
foreach (var p in sortedPathes) { obsCollection.AddEntry(p); }
trvMyTreeView.ItemsSource = obsCollection;
Split each line by the '/' character. If the string array is of length 5, then the first four items should be directories, and you have to test the last for an extension:
string.IsNullOrEmpty(new FileInfo("test").Extension)
If, like in your case, there is always a '/' even for the last directory, then the last item of the split string array is empty.
The rest is just about traversing your tree. When parsing a item, check if the first directory exists in the Children property of your root node. If it not exists, add it, if it does, use this one and go further.
I am trying to use a home grown web API to retrieve some data. The documentation is all written in PHP. The example I'm looking at is this:
$params = array(
'id' => 1
,'data' => array(
,'email' => 'example#hasoffers.com'
)
$url = "www.someapi.com/api?" . http_build_query( $params );
I'm using the C# WebClient class, but I can't figure out how to serialize the data parameter:
WebClient wc = new WebClient();
wc.QueryString["id"] = "1";
wc.QueryString["data"] = // I have no idea.
string json = wc.DownloadString(apiUrl);
I've tried a few variations:
wc.QueryString["data"] = "email=test#stackoverflow.com";
wc.QueryString["data"] = Uri.EscapeDataString("data[email]=test#stackoverflow.com");
wc.QueryString["data"] = Uri.EscapeDataString("email[0]=test#stackoverflow.com");
wc.QueryString["data"] = Uri.EscapeDataString("email=test#stackoverflow.com");
Of course, I don't have PHP setup anywhere to see what the http_build_query() is actually returning.
I finally figured it out. I guess posting this question rebooted my brain.
This is what worked:
wc.QueryString["data[email]"] = "test#stackoverflow.com";
Note, this question is very relevant to this answer, so I put this class as an answer there as well. That answer will receive any updates to this class.
As far as I know, there's nothing built in to do this. But, you can create your own class.
So I did:
/// <summary>
/// Helps up build a query string by converting an object into a set of named-values and making a
/// query string out of it.
/// </summary>
public class QueryStringBuilder
{
private readonly List<KeyValuePair<string, object>> _keyValuePairs
= new List<KeyValuePair<string, object>>();
/// <summary> Builds the query string from the given instance. </summary>
public static string BuildQueryString(object queryData, string argSeperator = "&")
{
var encoder = new QueryStringBuilder();
encoder.AddEntry(null, queryData, allowObjects: true);
return encoder.GetUriString(argSeperator);
}
/// <summary>
/// Convert the key-value pairs that we've collected into an actual query string.
/// </summary>
private string GetUriString(string argSeperator)
{
return String.Join(argSeperator,
_keyValuePairs.Select(kvp =>
{
var key = Uri.EscapeDataString(kvp.Key);
var value = Uri.EscapeDataString(kvp.Value.ToString());
return $"{key}={value}";
}));
}
/// <summary> Adds a single entry to the collection. </summary>
/// <param name="prefix"> The prefix to use when generating the key of the entry. Can be null. </param>
/// <param name="instance"> The instance to add.
///
/// - If the instance is a dictionary, the entries determine the key and values.
/// - If the instance is a collection, the keys will be the index of the entries, and the value
/// will be each item in the collection.
/// - If allowObjects is true, then the object's properties' names will be the keys, and the
/// values of the properties will be the values.
/// - Otherwise the instance is added with the given prefix to the collection of items. </param>
/// <param name="allowObjects"> true to add the properties of the given instance (if the object is
/// not a collection or dictionary), false to add the object as a key-value pair. </param>
private void AddEntry(string prefix, object instance, bool allowObjects)
{
var dictionary = instance as IDictionary;
var collection = instance as ICollection;
if (dictionary != null)
{
Add(prefix, GetDictionaryAdapter(dictionary));
}
else if (collection != null)
{
Add(prefix, GetArrayAdapter(collection));
}
else if (allowObjects)
{
Add(prefix, GetObjectAdapter(instance));
}
else
{
_keyValuePairs.Add(new KeyValuePair<string, object>(prefix, instance));
}
}
/// <summary> Adds the given collection of entries. </summary>
private void Add(string prefix, IEnumerable<Entry> datas)
{
foreach (var item in datas)
{
var newPrefix = String.IsNullOrEmpty(prefix)
? item.Key
: $"{prefix}[{item.Key}]";
AddEntry(newPrefix, item.Value, allowObjects: false);
}
}
private struct Entry
{
public string Key;
public object Value;
}
/// <summary>
/// Returns a collection of entries that represent the properties on the object.
/// </summary>
private IEnumerable<Entry> GetObjectAdapter(object data)
{
var properties = data.GetType().GetProperties();
foreach (var property in properties)
{
yield return new Entry()
{
Key = property.Name,
Value = property.GetValue(data)
};
}
}
/// <summary>
/// Returns a collection of entries that represent items in the collection.
/// </summary>
private IEnumerable<Entry> GetArrayAdapter(ICollection collection)
{
int i = 0;
foreach (var item in collection)
{
yield return new Entry()
{
Key = i.ToString(),
Value = item,
};
i++;
}
}
/// <summary>
/// Returns a collection of entries that represent items in the dictionary.
/// </summary>
private IEnumerable<Entry> GetDictionaryAdapter(IDictionary collection)
{
foreach (DictionaryEntry item in collection)
{
yield return new Entry()
{
Key = item.Key.ToString(),
Value = item.Value,
};
}
}
}
Like so:
// Age=19&Name=John%26Doe&Values%5B0%5D=1&Values%5B1%5D=2&Values%5B2%5D%5Bkey1%5D=value1&Values%5B2%5D%5Bkey2%5D=value2
QueryStringBuilder.BuildQueryString(new
{
Age = 19,
Name = "John&Doe",
Values = new object[]
{
1,
2,
new Dictionary<string, string>()
{
{ "key1", "value1" },
{ "key2", "value2" },
}
},
});
// 0=1&1=2&2%5B0%5D=one&2%5B1%5D=two&2%5B2%5D=three&3%5Bkey1%5D=value1&3%5Bkey2%5D=value2
QueryStringBuilder.BuildQueryString(new object[]
{
1,
2,
new object[] { "one", "two", "three" },
new Dictionary<string, string>()
{
{ "key1", "value1" },
{ "key2", "value2" },
}
}
);
I manage my application-settings using the setting-designer in VS2008.
"The exact path of the user.config
files looks something like this:"
<Profile Directory>\<Company Name>\
<App Name>_<Evidence Type>_<Evidence Hash>\<Version>\user.config
Is there to a way to customize this path? I would prefer something like this:
<Profile Directory>\<Company Name>\
<App Name>\<Version>\user.config
I noticed that white-spaces were replaced by underscores in the "Company Name" in the new created folder ("Test Company" --> "Test_Company"). I really wish to turn off this behavior.
You know, I could write a new XML-based setting-handler, but I would like to use the setting-designer.
It hasn't been easy to find good info on implementing a custom settings provider so I'm including a complete implementation below (bottom.) The format of the user.config file is retained, as well as the functionality within the .settings designer. I'm certain there are parts that can be cleaned up a bit, so don't hassle me :)
Like others, I wanted to change the location of the user.config file and still get the fun and fanciness of working with the .settings files in the designer, including creating default values for new installations. Importantly, our app also already has other saved settings objects at a path (appData\local\etc) in which we have already decided, and we didn't want artifacts in multiple locations.
The code is much longer than I'd like it to be, but there is no SHORT answer that I could find. Though it seems somewhat painful just to be able to control the path, creating a custom settings provider in itself is still pretty powerful. One could alter the follwing implementation to store the data just about anywhere including a custom encrypted file, database, or interact with a web serivice.
From what I've read, Microsoft does not intend on making the path to the config file configurable. I'll take their word for it when they say allowing that would be scary. See (this) post. Alas, if you want to do it yourself you must implement your own SettingsProvider.
Here goes..
Add a reference in your project to System.Configuration, you'll need it to implement the SettingsProvider.
Easy bit...Create a class which implements SettingsProvider, use ctrl+. to help you out.
class CustomSettingsProvider : SettingsProvider
Another easy bit...Go to the code behind of your .settings file (there is a button in the designer) and decorate the class to point it to your implementation. This must be done to override the built in functionality, but it does not change how the designer works.(sorry the formatting here is weird)
[System.Configuration.SettingsProvider(typeof(YourCompany.YourProduct.CustomSettingsProvider))]
public sealed partial class Settings
{
//bla bla bla
}
Getting the path: There is a property called "SettingsKey" (e.g. Properties.Settings.Default.SettingsKey) I used this to store the path. I made the following property.
/// <summary>
/// The key this is returning must set before the settings are used.
/// e.g. <c>Properties.Settings.Default.SettingsKey = #"C:\temp\user.config";</c>
/// </summary>
private string UserConfigPath
{
get
{
return Properties.Settings.Default.SettingsKey;
}
}
Storing the settings values. I chose to use a dictionary. This will get used extensively in a bit. I created a struct as a helper.
/// <summary>
/// In memory storage of the settings values
/// </summary>
private Dictionary<string, SettingStruct> SettingsDictionary { get; set; }
/// <summary>
/// Helper struct.
/// </summary>
internal struct SettingStruct
{
internal string name;
internal string serializeAs;
internal string value;
}
The magic. You must override 2 methods, GetPropertyValues and SetPropertyValues. GetPropertyValues recieves as a parameter what you see in the designer, you have to opportunity to update the values and return a new collection. SetPropertyValues is called when the user saves any changes to the values made at runtime, this is where I update the dictionary and write out the file.
/// <summary>
/// Must override this, this is the bit that matches up the designer properties to the dictionary values
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
/// <returns></returns>
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
//load the file
if (!_loaded)
{
_loaded = true;
LoadValuesFromFile();
}
//collection that will be returned.
SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();
//iterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
foreach (SettingsProperty setting in collection)
{
SettingsPropertyValue value = new SettingsPropertyValue(setting);
value.IsDirty = false;
//need the type of the value for the strong typing
var t = Type.GetType(setting.PropertyType.FullName);
if (SettingsDictionary.ContainsKey(setting.Name))
{
value.SerializedValue = SettingsDictionary[setting.Name].value;
value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
}
else //use defaults in the case where there are no settings yet
{
value.SerializedValue = setting.DefaultValue;
value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
}
values.Add(value);
}
return values;
}
/// <summary>
/// Must override this, this is the bit that does the saving to file. Called when Settings.Save() is called
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
//grab the values from the collection parameter and update the values in our dictionary.
foreach (SettingsPropertyValue value in collection)
{
var setting = new SettingStruct()
{
value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
name = value.Name,
serializeAs = value.Property.SerializeAs.ToString()
};
if (!SettingsDictionary.ContainsKey(value.Name))
{
SettingsDictionary.Add(value.Name, setting);
}
else
{
SettingsDictionary[value.Name] = setting;
}
}
//now that our local dictionary is up-to-date, save it to disk.
SaveValuesToFile();
}
Complete solution. So here's the entire class which includes the constructor, Initialize, and helper methods. Feel free to cut/paste slice and dice.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Reflection;
using System.Xml.Linq;
using System.IO;
namespace YourCompany.YourProduct
{
class CustomSettingsProvider : SettingsProvider
{
const string NAME = "name";
const string SERIALIZE_AS = "serializeAs";
const string CONFIG = "configuration";
const string USER_SETTINGS = "userSettings";
const string SETTING = "setting";
/// <summary>
/// Loads the file into memory.
/// </summary>
public CustomSettingsProvider()
{
SettingsDictionary = new Dictionary<string, SettingStruct>();
}
/// <summary>
/// Override.
/// </summary>
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
base.Initialize(ApplicationName, config);
}
/// <summary>
/// Override.
/// </summary>
public override string ApplicationName
{
get
{
return System.Reflection.Assembly.GetExecutingAssembly().ManifestModule.Name;
}
set
{
//do nothing
}
}
/// <summary>
/// Must override this, this is the bit that matches up the designer properties to the dictionary values
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
/// <returns></returns>
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
//load the file
if (!_loaded)
{
_loaded = true;
LoadValuesFromFile();
}
//collection that will be returned.
SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();
//itterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
foreach (SettingsProperty setting in collection)
{
SettingsPropertyValue value = new SettingsPropertyValue(setting);
value.IsDirty = false;
//need the type of the value for the strong typing
var t = Type.GetType(setting.PropertyType.FullName);
if (SettingsDictionary.ContainsKey(setting.Name))
{
value.SerializedValue = SettingsDictionary[setting.Name].value;
value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
}
else //use defaults in the case where there are no settings yet
{
value.SerializedValue = setting.DefaultValue;
value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
}
values.Add(value);
}
return values;
}
/// <summary>
/// Must override this, this is the bit that does the saving to file. Called when Settings.Save() is called
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
//grab the values from the collection parameter and update the values in our dictionary.
foreach (SettingsPropertyValue value in collection)
{
var setting = new SettingStruct()
{
value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
name = value.Name,
serializeAs = value.Property.SerializeAs.ToString()
};
if (!SettingsDictionary.ContainsKey(value.Name))
{
SettingsDictionary.Add(value.Name, setting);
}
else
{
SettingsDictionary[value.Name] = setting;
}
}
//now that our local dictionary is up-to-date, save it to disk.
SaveValuesToFile();
}
/// <summary>
/// Loads the values of the file into memory.
/// </summary>
private void LoadValuesFromFile()
{
if (!File.Exists(UserConfigPath))
{
//if the config file is not where it's supposed to be create a new one.
CreateEmptyConfig();
}
//load the xml
var configXml = XDocument.Load(UserConfigPath);
//get all of the <setting name="..." serializeAs="..."> elements.
var settingElements = configXml.Element(CONFIG).Element(USER_SETTINGS).Element(typeof(Properties.Settings).FullName).Elements(SETTING);
//iterate through, adding them to the dictionary, (checking for nulls, xml no likey nulls)
//using "String" as default serializeAs...just in case, no real good reason.
foreach (var element in settingElements)
{
var newSetting = new SettingStruct()
{
name = element.Attribute(NAME) == null ? String.Empty : element.Attribute(NAME).Value,
serializeAs = element.Attribute(SERIALIZE_AS) == null ? "String" : element.Attribute(SERIALIZE_AS).Value,
value = element.Value ?? String.Empty
};
SettingsDictionary.Add(element.Attribute(NAME).Value, newSetting);
}
}
/// <summary>
/// Creates an empty user.config file...looks like the one MS creates.
/// This could be overkill a simple key/value pairing would probably do.
/// </summary>
private void CreateEmptyConfig()
{
var doc = new XDocument();
var declaration = new XDeclaration("1.0", "utf-8", "true");
var config = new XElement(CONFIG);
var userSettings = new XElement(USER_SETTINGS);
var group = new XElement(typeof(Properties.Settings).FullName);
userSettings.Add(group);
config.Add(userSettings);
doc.Add(config);
doc.Declaration = declaration;
doc.Save(UserConfigPath);
}
/// <summary>
/// Saves the in memory dictionary to the user config file
/// </summary>
private void SaveValuesToFile()
{
//load the current xml from the file.
var import = XDocument.Load(UserConfigPath);
//get the settings group (e.g. <Company.Project.Desktop.Settings>)
var settingsSection = import.Element(CONFIG).Element(USER_SETTINGS).Element(typeof(Properties.Settings).FullName);
//iterate though the dictionary, either updating the value or adding the new setting.
foreach (var entry in SettingsDictionary)
{
var setting = settingsSection.Elements().FirstOrDefault(e => e.Attribute(NAME).Value == entry.Key);
if (setting == null) //this can happen if a new setting is added via the .settings designer.
{
var newSetting = new XElement(SETTING);
newSetting.Add(new XAttribute(NAME, entry.Value.name));
newSetting.Add(new XAttribute(SERIALIZE_AS, entry.Value.serializeAs));
newSetting.Value = (entry.Value.value ?? String.Empty);
settingsSection.Add(newSetting);
}
else //update the value if it exists.
{
setting.Value = (entry.Value.value ?? String.Empty);
}
}
import.Save(UserConfigPath);
}
/// <summary>
/// The setting key this is returning must set before the settings are used.
/// e.g. <c>Properties.Settings.Default.SettingsKey = #"C:\temp\user.config";</c>
/// </summary>
private string UserConfigPath
{
get
{
return Properties.Settings.Default.SettingsKey;
}
}
/// <summary>
/// In memory storage of the settings values
/// </summary>
private Dictionary<string, SettingStruct> SettingsDictionary { get; set; }
/// <summary>
/// Helper struct.
/// </summary>
internal struct SettingStruct
{
internal string name;
internal string serializeAs;
internal string value;
}
bool _loaded;
}
}
You would have to implement your own SettingsProvider to customize the path.
See this Client Settings FAQ
Q: Why is the path so obscure? Is there any way to change/customize it?
A: The path construction algorithm has to meet certain rigorous requirements in terms of security, isolation and robustness. While we tried to make the path as easily discoverable as possible by making use of friendly, application supplied strings, it is not possible to keep the path totally simple without running into issues like collisions with other apps, spoofing etc.
The LocalFileSettingsProvider does not provide a way to change the files in which settings are stored. Note that the provider itself doesn't determine the config file locations in the first place - it is the configuration system. If you need to store the settings in a different location for some reason, the recommended way is to write your own SettingsProvider. This is fairly simple to implement and you can find samples in the .NET 2.0 SDK that show how to do this. Keep in mind however that you may run into the same isolation issues mentioned above .
Here is an easier, briefer alternative to creating a custom settings class: change your app's evidence so that the "url" part is a constant rather than being based on the location of executable. To do this, you need to modify the default AppDomain when the program starts. There are two parts: setting app.config to use your AppDomainManager, and creating an AppDomainManager and HostSecurityManager to customize the Url evidence. Sounds complicated but it's much simpler than creating a custom settings class. This only applies to unsigned assemblies. If you have a signed assembly, it's going to use that evidence instead of the Url. But the good news there is your path will always be constant anyway (as long as the signing key doesn't change).
You can copy the code below and just replace the YourAppName bits.
DefaultAppDomainManager.cs:
using System;
using System.Security;
using System.Security.Policy;
namespace YourAppName
{
/// <summary>
/// A least-evil (?) way of customizing the default location of the application's user.config files.
/// </summary>
public class CustomEvidenceHostSecurityManager : HostSecurityManager
{
public override HostSecurityManagerOptions Flags
{
get
{
return HostSecurityManagerOptions.HostAssemblyEvidence;
}
}
public override Evidence ProvideAssemblyEvidence(System.Reflection.Assembly loadedAssembly, Evidence inputEvidence)
{
if (!loadedAssembly.Location.EndsWith("YourAppName.exe"))
return base.ProvideAssemblyEvidence(loadedAssembly, inputEvidence);
// override the full Url used in Evidence to just "YourAppName.exe" so it remains the same no matter where the exe is located
var zoneEvidence = inputEvidence.GetHostEvidence<Zone>();
return new Evidence(new EvidenceBase[] { zoneEvidence, new Url("YourAppName.exe") }, null);
}
}
public class DefaultAppDomainManager : AppDomainManager
{
private CustomEvidenceHostSecurityManager hostSecurityManager;
public override void InitializeNewDomain(AppDomainSetup appDomainInfo)
{
base.InitializeNewDomain(appDomainInfo);
hostSecurityManager = new CustomEvidenceHostSecurityManager();
}
public override HostSecurityManager HostSecurityManager
{
get
{
return hostSecurityManager;
}
}
}
}
app.config excerpt:
<runtime>
<appDomainManagerType value="YourAppName.DefaultAppDomainManager" />
<appDomainManagerAssembly value="DefaultAppDomainManager, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</runtime>
Building on Chucks excellent answer:
Implement a new partial class based on Settings.Designer.cs, so the Settings Designer does not wipe out the attribute when changes are made:
namespace Worker.Properties
{
[System.Configuration.SettingsProvider(
typeof(SettingsProviders.DllFileSettingsProvider<Settings>))]
internal sealed partial class Settings
{
}
}
I created the following class that can be put in an external project. It allows the config to be project.dll.config. By Inheriting and Overriding ConfigPath allows any path you like. I also added support for reading System.Collections.Specialized.StringCollection. This version is for Application settings.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Reflection;
using System.Xml.Linq;
using System.IO;
//http://stackoverflow.com/questions/2265271/custom-path-of-the-user-config
namespace SettingsProviders
{
public class DllFileSettingsProvider<Properties_Settings> : SettingsProvider where Properties_Settings : new()
{
const string NAME = "name";
const string SERIALIZE_AS = "serializeAs";
const string CONFIG = "configuration";
const string APPLICATION_SETTINGS = "applicationSettings";
const string SETTING = "setting";
/// <summary>
/// Loads the file into memory.
/// </summary>
public DllFileSettingsProvider()
{
SettingsDictionary = new Dictionary<string, SettingStruct>();
}
/// <summary>
/// Override.
/// </summary>
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
base.Initialize(ApplicationName, config);
}
/// <summary>
/// Override.
/// </summary>
public override string ApplicationName
{
get
{
return System.Reflection.Assembly.GetExecutingAssembly().ManifestModule.Name;
}
set
{
//do nothing
}
}
/// <summary>
/// Must override this, this is the bit that matches up the designer properties to the dictionary values
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
/// <returns></returns>
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
//load the file
if (!_loaded)
{
_loaded = true;
LoadValuesFromFile();
}
//collection that will be returned.
SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();
//itterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
foreach (SettingsProperty setting in collection)
{
SettingsPropertyValue value = new SettingsPropertyValue(setting);
value.IsDirty = false;
//need the type of the value for the strong typing
var t = Type.GetType(setting.PropertyType.FullName);
if (setting.PropertyType == typeof(System.Collections.Specialized.StringCollection))
{
var xml = SettingsDictionary[setting.Name].value;
var stringReader = new System.IO.StringReader(xml);
var xmlreader = System.Xml.XmlReader.Create(stringReader);
var ser = new System.Xml.Serialization.XmlSerializer(typeof(System.Collections.Specialized.StringCollection));
var obj = ser.Deserialize(xmlreader);
var col = (System.Collections.Specialized.StringCollection)obj;
value.PropertyValue = col;
}
else if (SettingsDictionary.ContainsKey(setting.Name))
{
value.SerializedValue = SettingsDictionary[setting.Name].value;
value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
}
else //use defaults in the case where there are no settings yet
{
value.SerializedValue = setting.DefaultValue;
value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
}
values.Add(value);
}
return values;
}
/// <summary>
/// Must override this, this is the bit that does the saving to file. Called when Settings.Save() is called
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
//grab the values from the collection parameter and update the values in our dictionary.
foreach (SettingsPropertyValue value in collection)
{
var setting = new SettingStruct()
{
value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
name = value.Name,
serializeAs = value.Property.SerializeAs.ToString()
};
if (!SettingsDictionary.ContainsKey(value.Name))
{
SettingsDictionary.Add(value.Name, setting);
}
else
{
SettingsDictionary[value.Name] = setting;
}
}
//now that our local dictionary is up-to-date, save it to disk.
SaveValuesToFile();
}
/// <summary>
/// Loads the values of the file into memory.
/// </summary>
private void LoadValuesFromFile()
{
if (!File.Exists(ConfigPath))
{
//if the config file is not where it's supposed to be create a new one.
throw new Exception("Config file not found: " + ConfigPath);
}
//load the xml
var configXml = XDocument.Load(ConfigPath);
//get all of the <setting name="..." serializeAs="..."> elements.
var settingElements = configXml.Element(CONFIG).Element(APPLICATION_SETTINGS).Element(typeof(Properties_Settings).FullName).Elements(SETTING);
//iterate through, adding them to the dictionary, (checking for nulls, xml no likey nulls)
//using "String" as default serializeAs...just in case, no real good reason.
foreach (var element in settingElements)
{
var newSetting = new SettingStruct()
{
name = element.Attribute(NAME) == null ? String.Empty : element.Attribute(NAME).Value,
serializeAs = element.Attribute(SERIALIZE_AS) == null ? "String" : element.Attribute(SERIALIZE_AS).Value ,
value = element.Value ?? String.Empty
};
if (newSetting.serializeAs == "Xml")
{
var e = (XElement)element.Nodes().First();
newSetting.value = e.LastNode.ToString() ?? String.Empty;
};
SettingsDictionary.Add(element.Attribute(NAME).Value, newSetting);
}
}
/// <summary>
/// Creates an empty user.config file...looks like the one MS creates.
/// This could be overkill a simple key/value pairing would probably do.
/// </summary>
private void CreateEmptyConfig()
{
var doc = new XDocument();
var declaration = new XDeclaration("1.0", "utf-8", "true");
var config = new XElement(CONFIG);
var userSettings = new XElement(APPLICATION_SETTINGS);
var group = new XElement(typeof(Properties_Settings).FullName);
userSettings.Add(group);
config.Add(userSettings);
doc.Add(config);
doc.Declaration = declaration;
doc.Save(ConfigPath);
}
/// <summary>
/// Saves the in memory dictionary to the user config file
/// </summary>
private void SaveValuesToFile()
{
//load the current xml from the file.
var import = XDocument.Load(ConfigPath);
//get the settings group (e.g. <Company.Project.Desktop.Settings>)
var settingsSection = import.Element(CONFIG).Element(APPLICATION_SETTINGS).Element(typeof(Properties_Settings).FullName);
//iterate though the dictionary, either updating the value or adding the new setting.
foreach (var entry in SettingsDictionary)
{
var setting = settingsSection.Elements().FirstOrDefault(e => e.Attribute(NAME).Value == entry.Key);
if (setting == null) //this can happen if a new setting is added via the .settings designer.
{
var newSetting = new XElement(SETTING);
newSetting.Add(new XAttribute(NAME, entry.Value.name));
newSetting.Add(new XAttribute(SERIALIZE_AS, entry.Value.serializeAs));
newSetting.Value = (entry.Value.value ?? String.Empty);
settingsSection.Add(newSetting);
}
else //update the value if it exists.
{
setting.Value = (entry.Value.value ?? String.Empty);
}
}
import.Save(ConfigPath);
}
/// <summary>
/// The setting key this is returning must set before the settings are used.
/// e.g. <c>Properties.Settings.Default.SettingsKey = #"C:\temp\user.config";</c>
/// </summary>
public virtual string ConfigPath
{
get
{
var name = new Properties_Settings().GetType().Module.Name + ".config";
if (System.IO.File.Exists(name)==false)
{
System.Diagnostics.Trace.WriteLine("config file NOT found:" + name);
}
System.Diagnostics.Trace.WriteLine("config file found:" + name);
return name;
// return Properties.Settings.Default.SettingsKey;
}
}
/// <summary>
/// In memory storage of the settings values
/// </summary>
internal Dictionary<string, SettingStruct> SettingsDictionary { get; set; }
/// <summary>
/// Helper struct.
/// </summary>
internal struct SettingStruct
{
internal string name;
internal string serializeAs;
internal string value;
}
bool _loaded;
}
}
Make sure dependent projects include the project.dll.config file by add a post-build event:
copy $(SolutionDir)Worker\$(OutDir)Worker.dll.config $(TargetDir) /y
I have a method which in essence converts a datatable to a list of objects which I call 'Bags'. This code is called many times per session, with many sessions concurrently running and with sometimes thousands of rows. Because of this I need it to be as quick as possible. I have an xml file which contains the DataColumn to Property mappings. The main method to optimize is ConvertRowToBag - the type parameter passed in is a type which derives from BagBase.
It's a long bit of code, but any tips would be much appreciated.
public class BagBase
{
/// <summary>
/// Dictionary of properties and names
/// </summary>
private static Dictionary<string, PropertyInfo> propertyDictionary = new Dictionary<string, PropertyInfo>();
/// <summary>
/// Table of column/property mappings
/// </summary>
private static DataTable mappings = new DataTable("Mappings");
/// <summary>
/// Returns true if the map exists
/// </summary>
/// <param name="columnName"></param>
/// <param name="type"></param>
/// <returns></returns>
private static bool MappingExists(string columnName, Type type)
{
DataRow [] rows = BagBase.mappings.Select(String.Format("Type = '{0}' and ColumnName = '{1}'", type.Name, columnName));
return (rows != null && rows.Length > 0);
}
/// <summary>
/// Converts the table to bags.
/// </summary>
/// <param name="table">The table.</param>
/// <param name="outputType">Type of the output.</param>
/// <returns></returns>
protected static List<BagBase> ConvertTableToBags(DataTable table, Type outputType)
{
Trace.TraceInformation(String.Format("ConvertTableToBags : table={0} Type={1}", table.TableName, outputType.Name));
// Create an empty list
List<BagBase> result = new List<BagBase>();
// Iterate through the rows
foreach (DataRow row in table.Rows)
{
// Add to the list
result.Add(ConvertRowToBag(outputType, row));
}
Trace.TraceInformation("ConvertTableToBags Finished.");
return result;
}
/// <summary>
/// Converts the row to bag.
/// </summary>
/// <param name="outputType">Type of the output.</param>
/// <param name="row">The row.</param>
/// <returns></returns>
protected static BagBase ConvertRowToBag(Type outputType, DataRow row)
{
// Create an instance of the child class and store in the base
BagBase bag = Activator.CreateInstance(outputType) as BagBase;
// Iterate through the columns
foreach (DataColumn column in row.Table.Columns)
{
// If this column has been mapped
if (BagBase.MappingExists(column.ColumnName, outputType))
{
PropertyInfo property;
string columnProperty = String.Format("{0}={1}", column.ColumnName, outputType.Name);
// Get the property as defined in the map
if (!propertyDictionary.ContainsKey(columnProperty))
{
// Get the property
property = outputType.GetProperty(BagBase.GetColumnMapping(column.ColumnName, outputType));
// Add the property to the dictionary
propertyDictionary.Add(columnProperty, property);
}
else
{
property = propertyDictionary[columnProperty];
}
if (property != null)
{
if (!row.IsNull(column))
{
// Set the value to the in the table
if (property.PropertyType.BaseType != null && property.PropertyType.BaseType == typeof(Enum))
{
if (column.DataType != typeof(String))
{
property.SetValue(bag, Enum.ToObject(property.PropertyType, row[column]), null);
}
else
{
property.SetValue(bag, Enum.ToObject(property.PropertyType, Convert.ToChar(row[column])), null);
}
}
else if (property.PropertyType == typeof(DateTime?))
{
property.SetValue(bag, (DateTime?)row[column], null);
}
else
{
property.SetValue(bag, Convert.ChangeType(row[column], property.PropertyType), null);
}
}
else // No nulls
{
if (column.DataType == typeof(String))
{
property.SetValue(bag, String.Empty, null);
}
}
// Generate the unique class.property name
string propertyKey = String.Format("{0}.{1}", outputType.Name, property.Name);
if (!columnCaptions.ContainsKey(propertyKey))
{
// Add to the caption map
columnCaptions.Add(propertyKey, column.Caption);
}
}
}
else
{
// If this column isn't mapped, add it to Other information
if (bag.OtherInformation == null)
{
bag.OtherInformation = new Dictionary<string, string>();
}
bag.OtherInformation.Add(column.ColumnName, !row.IsNull(column) ? row[column].ToString() : String.Empty);
}
}
return bag;
}
}
Use a profiler. There's no way for us to know what actually takes the most time in your code.
There's just really no use trying to optimize line-by-line and many people seem to not know this. Computers are always waiting for a resource, sometimes it's CPU or disk IO, and often it's the user. To make any piece of code faster, find the bottlenecks using a profiler and work on making that code faster.
Aside from the general advise of "use a profiler", there is probably not one bottleneck but either a series of slow calls or the very structure of the procedure is creating unnecessary iterations. At a glance:
The Select against a datatable is generally not very performant.
Reflection carries with it a lot of overhead, it looks like you are dependent on it but if you could limit its scope you will probably get better overall performance.