Encapsulation with multiple classes C# - c#

So my question is over basic encapsulation. I know I am setting up my getters and setters right (I actually have a question later about this) but I have multiple classes. I have another class and I understand that I am making pieces of my code view-able to my outside class by making certain things public. So I think I set up my first code file right. (Some background, I have a class that is connecting to a database and then another that is encapsulating all the data. The first code section posted is the encapsulating part, I then post my three methods I was messing up on.)
I feel okay with the getting and setting, I feel a little unsure of my constructor. I feel like I put my variables in the parameter list so that I put values in them from my outside class? Right? or should I be putting public forms of my private variables in my other code file and then passing those into my constructor in that same file?
/ this my first code file
using System;
public class clsEncapsulate
{
private int mID;
private string mName;
private string mClassification;
private DateTime mConvocationDate;
private string mLocation;
public int ID
{
get
{
return mID;
}
set
{
mID = value;
}
}
public string Name
{
get
{
return mName;
}
set
{
mName = value;
}
}
public string Classification
{
get
{
return mName;
}
set
{
mName = value;
}
}
private DateTime ConvocationDate
{
get
{
return mConvocationDate;
}
set
{
mConvocationDate = value;
}
}
private string Location
{
get
{
return mLocation;
}
set
{
mLocation = value;
}
}
public clsEncapsulate(int id, string name, string classification, DateTime convocationDate, string location)
{
bool running = false;
while(running == false)
{
ID = mID;
Name = mName;
Classification = mClassification;
ConvocationDate = mConvocationDate;
Location = mLocation;
running = true;
}
}
}
In my second code file I am just going to put the methods that I am having trouble with.
private void refreshbox()
{
string formattedConvocation;
string formattedDateTime;
string formattedConvocationName;
lstConvocations.Items.Clear();
foreach (clsEncapsulate currentConvocation in mConvocationAL)
{
formattedConvocationName = currentConvocation.Name;
formattedConvocationName = truncateString(formattedConvocationName, 30);
formattedConvocation = formattedConvocationName.PadRight(33);
formattedConvocation += currentConvocation.Classification.PadRight(17);
formattedDateTime = currentConvocation.ConvocationDate.ToShortDateString().PadRight(10)
+ currentConvocation.ConvocationDate.ToShortTimeString().PadLeft(8);
formattedConvocation += formattedDateTime;
lstConvocations.Items.Add(formattedConvocation);
}
}
Alright, so in order for my second code file to manipulate the variables in the first code file, I need to expose them to this method. I didn't know if I should be putting my public variables in the constructor, or if I should be declaring them somewhere in my first code file. I was very unsure of how to expose these variables to this method. I've fiddle around with it but my book doesn't address this situation exactly and I was having trouble figuring it out.
If someone does answer this question please break down why you're going to put what you're going to put! I want to understand why, say, I put my public variables in one place, and not another. Or why I declare an object of my encapsulate class in one place and not another. I was trying to declare an encapsulate object in my method so it would give this method access to the variables, but it wasn't working! Please tell me what I was doing wrong or if you want me to post more of my code.
Below are the two other methods I was messing up on.
/ second method from my second code file I was messing up on:
private void displayProperties(int index)
{
if (index == -1)
{
return;
}
clsEncapsulate selectedValue = (clsEncapsulate)mConvocationAL[index];
txtConvocationName.Text = selectedValue.Name;
txtConvocationClassification.Text = selectedValue.Classification;
txtConvocationDate.Text = selectedValue.ConvocationDate.ToShortDateString();
txtConvocationTime.Text = selectedValue.ConvocationDate.ToShortTimeString();
txtConvocationLocation.Text = selectedValue.Location;
txtID.Text = selectedValue.ID.ToString();
}
/ last method I was messing up on:
private void readConvocations(string filterConstraint, string sortField, string sortOrder)
{
OleDbConnection connection = null;
OleDbDataReader reader = null;
try
{
connection = new OleDbConnection();
connection.ConnectionString = mConnectionString;
connection.Open();
string statement = "SELECT ID, Name, Classification, Location, Date FROM Convocations ";
if(filterConstraint != "")
{
statement += "WHERE Name LIKE " + toSQL(filterConstraint, true) + " ";
}
string statement2 = statement;
statement = string.Concat(new string[]
{
statement2, "ORDER BY ", sortField, " ", sortOrder
});
OleDbCommand oleDbCommand = new OleDbCommand(statement, connection);
reader = oleDbCommand.ExecuteReader();
mConvocationAL.Clear();
while(reader.Read())
{
clsEncapsulteconvocation = new clsEncapsulate();
convocation.ID = (int)reader["ID"];
convocation.Name = (string)reader["Name"];
convocation.Classification = (string)reader["Classification"];
convocation.Location = (string)reader["Location"];
convocation.ConvocationDate = (DateTime)reader["Date"];
mConvocationAL.Add(convocation);
}
}
finally
{
if (reader != null)
{
reader.Close();
}
if (connection != null)
{
connection.Close();
}
}
}
Tell me if you need me to elaborate more to help you understand my situation. I am new at learning vocabulary and want to understand this! Thank you for helping. :)

The code you provided is one public object and a bunch of private methods so its difficult to get the overall picture of how your code it working together but there are a few principles you can apply to make your code better structured, now and in the future.
Have a read about SOLID (http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)). The S and the D would apply well to your example.
Also you mentioned construtors and private properties. Try looking into Imutable types. That means once the object is created you cannot change it. For your clsEncapsulate class that would mean making your fields read only and remove the public setters.
Good luck.

Related

c# Method not working as expected Address book project

I'm new to c# and working on my first project - a console app. I am having some trouble understanding why my code will not return false when an entry in the address book already exists. The following methods are all part of the AddressBook class CheckEntry(), AddEntry(), RemoveEntry().
Ok, so the boolean method CheckEntry() is used by two other methods - AddEntry() and RemoveEntry() - both are looking to verify if the user entry exists before performing their respective duties. In AddEntry() it is supposed to see if the contact already exists before adding another contact and shouldn't create the contact if it does (but it's adding duplicates). RemoveEntry() is supposed to check if it exists and uses the updated value of the stored variable in CheckEntry() to remove the current contact (but does nothing).
I know I'm probably either missing something simple or have way over thought the whole process. My assumption is that checkEntry() is not working correctly since both of the functions tied to it are faulting. Anyone have any ideas?? Let me know if I need to explain anything further.
NOTE: I know that using a list would be more efficient/easier to work with. It was my goal to use an array for learning purposes. Switching from learning Javascript to C# has been a bit of a challenge and I want to make sure I'm learning each thing before moving onto the next.
Here is all of my code. Each class is separated by "//--------" thank you in advance for your help.
namespace AddressBook {
class Contact {
public string Name;
public string Address;
public Contact(string name, string address) {
Name = name;
Address = address;
}
}
}
//------------------------------------------------------------------------
using System;
namespace AddressBook {
class AddressBook {
public readonly Contact[] contacts;
public AddressBook() {
contacts = new Contact[2]; ;
}
public void AddEntry(string name, string address) {
Contact AddContact = new Contact(name, address);
if (CheckEntry(name)) {
for (int i = 0; i < contacts.Length; i++) {
if (contacts[i] == null) {
contacts[i] = AddContact;
Console.WriteLine("Address Book updated. {0} has been added!", name);
break;
}
}
}
}
private string existingContact = "";
private bool CheckEntry(string name) {
foreach(Contact contact in contacts) {
if (contact == null) {
break;
}
else if (contact != null && contact.ToString() != name) {
continue;
}
else if (contact.ToString() == name) {
existingContact = contact.ToString();
return false;
}
}
return true;
}
public void RemoveEntry(string name) {
if( !(CheckEntry(name)) ) {
existingContact = null;
Console.WriteLine("{0} removed from contacts", name);
}
}
public string View() {
string contactList = "";
foreach(Contact contact in contacts) {
if(contact == null) {
break;
}
contactList += String.Format("Name: {0} -- Address: {1}" + Environment.NewLine, contact.Name, contact.Address);
}
return contactList;
}
}
}
//------------------------------------------------------------------------
using System;
namespace AddressBook {
class Program {
static void Main(string[] args) {
AddressBook addressBook = new AddressBook();
PromptUser();
void Menu() {
Console.WriteLine("TYPE:");
Console.WriteLine("'Add' to add a contact: ");
Console.WriteLine("'Remove' to select and remove a contact: ");
Console.WriteLine("'Quit' to exit: ");
}
void UpdateAddressBook(string userInput) {
string name = "";
string address = "";
switch ( userInput.ToLower() ) {
case "add":
Console.Write("Enter a name: ");
name = Console.ReadLine();
Console.Write("Enter an address: ");
address = Console.ReadLine();
addressBook.AddEntry(name, address);
break;
case "remove":
Console.Write("Enter a name to remove: ");
name = Console.ReadLine();
addressBook.RemoveEntry(name);
break;
case "view":
Console.WriteLine(addressBook.View());
break;
}
}
void PromptUser() {
Menu();
string userInput = "";
while (userInput != "quit") {
Console.WriteLine("What would you like to do?");
userInput = Console.ReadLine();
UpdateAddressBook(userInput);
}
}
}
}
}
Here is what I came up with - tested changes
Now I can't add duplicate names and I can remove entries.
public void AddEntry(string name, string address) {
Contact AddContact = new Contact(); //changed
AddContact.Name = name; //changed
AddContact.Address = address; //changed
if (CheckEntry(name)) {
for(int i = 0; i < contacts.Length; i++) {
if (contacts[i] == null) {
contacts[i] = AddContact;
Console.WriteLine("Address Book updated. {0} has been added!", name);
break;
}
}
}
}
//changed - removed variable and all instances of...
private bool CheckEntry(string name) {
foreach(Contact contact in contacts) {
if (contact == null) {
break;
}
else if (contact != null && contact.Name != name) {
continue;
}
else if (contact.Name == name) {
return false;
}
}
return true;
}
//changed - instead of passing checkentry() as a check I just took care of it here
public void RemoveEntry(string name) {
for(int i = 0; i < contacts.Length; i++) {
if(contacts[i].Name == name) {
contacts[i] = null;
break;
}
}
Console.WriteLine("{0} removed from contacts", name);
}
In CheckEntry you are testing an object of type Contact with your param of type string. This cannot work as you are testing 2 different types.
It could work the way you wrote it if you override the ToString method in your Contact class (so that it gives the attribute contact.name).
You can modify your code like this (add using System.Linq):
private bool CheckEntry(string name)
{
return contacts.Any(a => a.Name == name);
}
First of, I am assuming your strategy is to find the first empty slot in the array and insert a new Contact in it for AddEntry. To remove an entry, you want to simply mark that array location as empty. As you realize, this means that the array does not grow dynamically with the request i.e. you can have an ArrayFull situation that you need to handle. Also, you are doing linear search a.k.a. scan of the array - I assume you don't want to focus on that aspect in this sample.
Below are my comments for your existing code:
Shouldn't AddEntry update the Address if Address is different even though the Name matches?
Also returning a bool to indicate if the address was added/updated (true) versus nothing was done (false)
You should also handle the 'ArrayFull` condition
I do not understand why you need the variable existingContact
Don't use functions named CheckXXX if you plan to return bool. ContainxXXX is better understood method name to use with a bool return value.
You should not break from your foreach loop in CheckEntry. What if 2 entries were added, then the first one removed and then request to add the second one comes again? You would just break out since first entry is null
You seem to have 3 different ifs to check in your foreach where only one suffices.
Below is my relevant code with comments. I tried to keep them similar to what you have. You can use LINQ in multiple places instead of looping.
class Contact {
public string Name { get; private set; } // use a property with a private setter, instead of a public member
public string Address { get; private set; } // use a property with a private setter, instead of a public member
public Contact(string name, string address) {
Name = name;
Address = address;
}
}
//------------------------------------------------------------------------
class AddressBook {
public readonly Contact[] contacts;
public AddressBook() {
contacts = new Contact[2]; // I am assuming you kept the size 2 for testing
}
public bool AddEntry(string name, string address) {
if (!ContainsEntry(name)) {
Contact AddContact = new Contact(name, address);
for (int i = 0; i < contacts.Length; i++) {
if (contacts[i] == null) {
contacts[i] = AddContact;
Console.WriteLine("Address Book updated. {0} has been added!", name);
return true;
}
}
Console.WriteLine($"Cannot add name ({name}) to Address Book since it is full!");
// TODO: Throw some exception or specific return values to indicate the same to the caller
} else {
Console.WriteLine($"Name ({name}) already exists in Address Book!");
// TODO: Update the address?
}
return false;
}
private int GetEntryIndex(string name) {
for (int i = 0; i < contacts.Length; i++) {
if (contacts[i] != null && contacts[i].Name == name)
return i;
}
return -1;
}
private bool ContainsEntry(string name) {
return GetEntryIndex(name) != -1;
}
public void RemoveEntry(string name) {
var index = GetEntryIndex(name);
if (index != -1) {
contacts[index] = null;
Console.WriteLine("{0} removed from contacts", name);
}
}
public string View() {
string contactList = "";
foreach (Contact contact in contacts) {
if (contact == null) {
continue; // Don't break, but simply continue to look further
}
contactList += String.Format("Name: {0} -- Address: {1}" + Environment.NewLine, contact.Name, contact.Address);
}
return contactList;
}
}
EDIT: Answering some of the questions the OP has
Q: What should I do with the return value of AddEntry
A: Right now, you are writing code for practice, but once you start writing industry standard code, you will soon realize that you wouldn't know who call's your function. Never assume (unless the method is private) that only you will call this function. Currently, you don't need to do anything with this return value, but time might come when you want to know if your call did indeed modify some values or just returned without changes. It is for that time. It is a pretty standard practice. Some folks even return an enum {NoChange, Added, Updated, Deleted} instead of a bool
Q: How to reduce the CheckEntry function.
A:
Below I have written your function with a slightly different formatting
private bool CheckEntry(string name) {
foreach (Contact contact in contacts) {
if (contact == null) { // First if
continue;
} else {
if (contact != null && contact.Name != name) { // Second if
continue;
} else {
if (contact.Name == name) { // Third if
return false;
}
}
}
}
return true;
}
For the first if statement, the else part is redudant. The second if statement will hit only when contact is not null due to the continue statement, cancelling the need for the else keyword.
Since contact is not null when we hit the second if statement, the check contact != null is pretty redundant in the second if. You can reduce the if statement's as below
if (contact.Name != name) { // Second if
continue;
} else {
if (contact.Name == name) { // Third if
return false;
}
}
Similarly you will notice that the the third if will hit only when contact.Name is the same as name (otherwise it would have continued). So there is no need to check again. This will reduce our checks as below
if (contact == null)
continue;
if (contact.Name != name)
continue;
else
return false;
This can be further reduced by combining the conditions in the two if statements as below
if (contact == null || contact.Name != name)
continue;
else
return false;
Which is the same as (negating the condition)
if (contact != null && contact.Name == name)
return false;
So your function will look like
private bool CheckEntry(string name) {
foreach (Contact contact in contacts)
if (contact != null && contact.Name == name)
return false;
return true;
}
Hope you follow the deductions here
Q: I may have misunderstood from the course but when you write the properties in a class with getters and setters, which I did change with my updated version, I was under the impression that it wasn't necessary, even redundant, to create a Constructor - that the class (not sure if this is correct terminology) has a default constructor built in for cases where you would want to add the property.
A: Correct. Just different ways of doing it.
Q: I like what you did with GetEntryIndex() and ContainsEntry() - I'm curious, is GetEntryIndex() not the same as writing your own Array.IndexOf(), though? Someone, either me or a method, has to scan the array. Both versions are linear so either way, this is O(n), correct? (just getting into some theory so please correct me if I'm wrong). So, just for my understanding, this is the same as: return Array.IndexOf(contacts, name); this returns -1 if it doesn't exist or whatever the index is(I'm assuming)
A: It is not the same, but similar in essence. IndexOf has a set of rules to figure out if given two objects are equal or not. You haven't defined these rules on your custom object, hence it will always return -1. There are simple LINQ statements to do these, but I would let you explore that on your own.
First, thank you for taking the time to make such a thorough response. Second, I should probably note that I am teaching myself/taking a course on Treehouse and I'm on my second week/second section of the course - just to give you an idea of where I'm coming from. That being said, I want to walk through what you have given me and where I was heading with this project so I can learn.
I agree the user should be able to update and it was a feature that I had considered but had not quite gotten there.
if returning a bool when added/updated would you then remove the update string and place it with the caller in main()? This way it only prints that it's been updated if return true else print that the contact was unchanged. It kind of feels like it might be advantageous to make a class that is suitable just checking values - which would also make my code more reusable?? If I was going to use it in Main() maybe something like this would work..
if(!addEntry()) {Console.WriteLine("Contact {0} was not updated", name);} //do something
else{Console.WriteLine("Address Book updated. {0} has been added!", name);}
Furthermore, if the user is just updating the contact it could print that it's been updated. So the Console output could be a ternary operation - if new name print contact added otherwise contact updated??
I agree and had run into the situation where nothing was done because the array was full and that was something I had planned on working on.
I absolutely agree about the existing contact variable. I was so stuck when it came to what to do that was the best I could come up with. I should have walked away for a bit and thought about it more. I don't know if you saw my updated portion, but I was able to eliminate it.
Contains for a bool method seems logical, I will be sure to follow this rule.
I had not considered this in such terms before you said that. It makes perfect sense though. If the first entry is null and the second contains the name that the user is trying to enter then you would end up with a duplicate since I was breaking from the loop at the first instance of null instead of making sure the name didn't exist in the entire array, first.
I'm not sure, without your methods, how I would have been able to eliminate my three conditionals. I see what you did to make the one work, but am I possibly missing something? Or, was that more of a reference saying, ok here's a better way - use these methods to check and then eliminate the if/else chain, thus making your code more concise.
for your code:
I may have misunderstood from the course but when you write the properties in a class with getters and setters, which I did change with my updated version, I was under the impression that it wasn't necessary, even redundant, to create a Constructor - that the class (not sure if this is correct terminology) has a default constructor built in for cases where you would want to add the property.
Correct, I did set the size to 2 for testing purposes.
I like what you did with GetEntryIndex() and ContainsEntry() - I'm curious, is GetEntryIndex() not the same as writing your own Array.IndexOf(), though? Someone, either me or a method, has to scan the array. Both versions are linear so either way, this is O(n), correct? (just getting into some theory so please correct me if I'm wrong). So, just for my understanding, this is the same as:
return Array.IndexOf(contacts, name);
this returns -1 if it doesn't exist or whatever the index is(I'm assuming)
That's a good idea with the continue in View(). That will make sure to print out any contact with an index higher than an index with a null value. By some magic, this was working in this way with the break(it would print out index 1 even if index 0 was empty), but I do realize why it shouldn't and how using continue is better.

Invalid token 'while' in class, struct, or interface member declaratio

I have the following code
class Card
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
class Program
{
static void Main(string[] args)
{
int KaLP = 8000;
int YuLP = 8000;
Card bewd = new Card();
Card LOD = new Card();
var KaibaDeck = new List<Card>() { bewd, LOD };
var KaibaHand = new List<Card>() { };
var KaibaFusionDeck = new List<Card>() { };
var KaibaGraveyard = new List<Card>() { };
var YugiDeck = new List<Card>() { };
var YugiHand = new List<Card>() { };
var YugiFusionDeck = new List<Card>() { };
var YugiGraveyard = new List<Card>() { };
int KaibaDeckSize = KaibaDeck.Count;
string sDrawChoice;
int DrawChoice;
while (KaibaDeckSize > 0)
{
if (DrawChoice == 0)
{
break;
}
else
{
KaibaBattlePhase(KaibaHand, KaibaDeck, KaibaFusionDeck, YugiDeck, Field, KaibaGraveyard, YugiGraveyard, KaLP, YuLP, "Battle");
}
}
}
public static void KaibaBattlePhase(List<Card> KaibaHand, List<Card> KaibaDeck, List<Card> KaibaFusionDeck, List<Card> YugiDeck, List< List<Card> > Field, List<Card> KaibaGraveyard, List<Card> YugiGraveyard, int KaLP, int YuLP, const string Phase)
{
string sDecision;
int Decision;
while(true)//so the while loop NEVER breaks unless you have a 'break' later on
{
Console.WriteLine("Would you like to continue the Battle Phase? (Yes-'1' No-'0', Check Kaiba's Graveyard '-1', Check YUGI's graveyard '-2')");
sDecision = Console.ReadLine();
int.TryParse(sDecision, out Decision);
if (Decision==0)
{
break;
}
if (Decision==-1)
{
KaibaGraveyardFunction(KaibaGraveyard);
}
}
}
public static void KaibaGraveyardFunction(List<Card> KaibaGraveyard)
{
Console.WriteLine("Kaiba's graveyard contains: ");
foreach (Card KG in KaibaGraveyard)
{
Console.WriteLine(KG.Name);
}
}
}
I'm getting the following errors:
On the line KaibaGraveyardFunction(KaibaGraveyard);:
Method must have a return type
At while(true):
Invalid token 'while' in class, struct, or interface member declaration
In addition, I'm getting alot of other errors such as
"Invalid token '(', '=', '.' in class, struct, or interface member declaration"
"Syntax error, ']' expected"
At the end of your method declaration for KaibaBattlePhase you have this method parameter:
const string Phase
That's a syntax error which is confusing the compiler and causing it to not understand pretty much anything afterward. (I've never seen it get that confused from a keyword, usually that happens with missing/extra curly braces and such. But I guess this triggers it too.)
Why are you trying to pass a constant as a method parameter? That doesn't really make a lot of sense. If the method thinks it's a constant, it won't accept any value for it. If you pass it a constant, the method wouldn't know or care where the value came from.
If you remove const then the rest of the errors go away.
(Side note: The error becomes a lot easier to see if you clean up that method declaration. 10 parameters is a lot. It's highly likely that you can get away with some refactoring there to make it a lot cleaner. At its simplest, you can replace all of those parameters with a parameter DTO object which has those as properties. Potentially more useful would be the Replace Method With Method Object pattern, illustrated here.)
Not completely following your logic and not sure this is your issue but you never reevaluate KaibaDeckSize. If your intention is to simply use the while (KaibaDeckSize > 0) {} evaluation as a condition of entering the block, consider replacing it with a simple if statement: if (KaibaDeckSize > 0) {} Also, you never initialize DrawChoice which will generate a Use of unassigned local variable error during compilation. Use: int DrawChoice = 0; And this DrawCount` is never reassigned either.
Did you actually post your most recent code?
int KaibaDeckSize = KaibaDeck.Count;
string sDrawChoice;
int DrawChoice = 0;
while (KaibaDeckSize > 0)
{
if (DrawChoice == 0)
{
break;
}
else
{
KaibaBattlePhase(KaibaHand, KaibaDeck,
KaibaFusionDeck, YugiDeck, Field,
KaibaGraveyard, YugiGraveyard, KaLP, YuLP, "Battle");
}
KaibaDeckSize = KaibaDeck.Count;
}

Async XML Reading in Windows Phone 7

So I have a Win Phone app that is finding a list of taxi companies and pulling their name and address from Bing successfully and populating a listbox that is being displayed to users. Now what I want to do is, to search for each of these terms on Bing, find the number of hits each search term returns and rank them accordingly (a loose sort of popularity ranking)
void findBestResult(object sender, DownloadStringCompletedEventArgs e)
{
string s = e.Result;
XmlReader reader = XmlReader.Create(new MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(s)));
String name = "";
String rName = "";
String phone = "";
List<TaxiCompany> taxiCoList = new List<TaxiCompany>();
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name.Equals("pho:Title"))
{
name = reader.ReadInnerXml();
rName = name.Replace("&","&");
}
if (reader.Name.Equals("pho:PhoneNumber"))
{
phone = reader.ReadInnerXml();
}
if (phone != "")
{
string baseURL = "http://api.search.live.net/xml.aspx?Appid=<MyAppID>&query=%22" + name + "%22&sources=web";
WebClient c = new WebClient();
c.DownloadStringAsync(new Uri(baseURL));
c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults);
taxiCoList.Add (new TaxiCompany(rName, phone, gResults));
}
phone = "";
gResults ="";
}
TaxiCompanyDisplayList.ItemsSource = taxiCoList;
}
}
So that bit of code finds the taxi company and launches an asynchronous task to find the number of search results ( gResults ) to create each teaxicompany object.
//Parses search XML result to find number of results
void findTotalResults(object sender, DownloadStringCompletedEventArgs e)
{
lock (this)
{
string s = e.Result;
XmlReader reader = XmlReader.Create(new MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(s)));
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name.Equals("web:Total"))
{
gResults = reader.ReadInnerXml();
}
}
}
}
}
The above snipped finds the number of search results on bing, but the problem is since it launches async there is no way to correlate the gResults obtained in the 2nd method with the right company in method 1. Is there any way to either:
1.) Pass the name and phone variables into the 2nd method to create the taxi object there
2.) Pass back the gResults variable and only then create the corresponding taxicompany object?
Right well there is a lot do here.
Getting some small helper code
First off I want to point you to a couple of blog posts called Simple Asynchronous Operation Runner Part 1 and Part 2. I'm not suggesting you actually read them (although you're welcome too but I've been told they're not easy reading). What you actually need is a couple of code blocks from them to put in your application.
First from Part 1 copy the code from the "AsyncOperationService" box, place it in new class file in your project called "AsyncOperationService.cs".
Second you'll need the "DownloadString" function from Part 2. You could put that anywhere but I recommend you create a static public class called "WebClientUtils" and put it in there.
Outline of solution
We're going to create a class (TaxiCompanyFinder) that has a single method which fires off the asynchronous job to get the results you are after and then has an event that is raised when the job is done.
So lets get started. You have a TaxiCompany class, I'll invent my own here so that the example is as complete as possible:-
public class TaxiCompany
{
public string Name { get; set; }
public string Phone { get; set; }
public int Total { get; set; }
}
We also need an EventArgs for the completed event that carries the completed List<TaxiCompany> and also an Error property that will return any exception that may have occured. That looks like this:-
public class FindCompaniesCompletedEventArgs : EventArgs
{
private List<TaxiCompany> _results;
public List<TaxiCompany> Results
{
get
{
if (Error != null)
throw Error;
return _results;
}
}
public Exception Error { get; private set; }
public FindCompaniesCompletedEventArgs(List<TaxiCompany> results)
{
_results = results;
}
public FindCompaniesCompletedEventArgs(Exception error)
{
Error = error;
}
}
Now we can make a start with some bare bones for the TaxiCompanyFinder class:-
public class TaxiCompanyFinder
{
protected void OnFindCompaniesCompleted(FindCompaniesCompletedEventArgs e)
{
Deployment.Current.Dispatcher.BeginInvoke(() => FindCompaniesCompleted(this, e));
}
public event EventHandler<FindCompaniesCompletedEventArgs> FindCompaniesCompleted = delegate {};
public void FindCompaniesAsync()
{
// The real work here
}
}
This is pretty straight forward so far. You'll note the use of BeginInvoke on the dispatcher, since there are going to be a series of async actions involved we want to make sure that when the event is actually raised it runs on the UI thread making it easier to consume this class.
Separating XML parsing
One of the problems your original code has is that it mixes enumerating XML with trying to do other functions as well, its all a bit spagetti. First function that I indentified is the parsing of the XML to get the name and phone number. Add this function to the class:-
IEnumerable<TaxiCompany> CreateCompaniesFromXml(string xml)
{
XmlReader reader = XmlReader.Create(new StringReader(xml));
TaxiCompany result = new TaxiCompany();
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name.Equals("pho:Title"))
{
result.Name = reader.ReadElementContentAsString();
}
if (reader.Name.Equals("pho:PhoneNumber"))
{
result.Phone = reader.ReadElementContentAsString();
}
if (result.Phone != null)
{
yield return result;
result = new TaxiCompany();
}
}
}
}
Note that this function yields a set of TaxiCompany instances from the xml without trying to do anything else. Also the use of ReadElementContentAsString which makes for tidier reading. In addition the consuming of the xml string is much smoother.
For similar reasons add this function to the class:-
private int GetTotalFromXml(string xml)
{
XmlReader reader = XmlReader.Create(new StringReader(xml));
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name.Equals("web:Total"))
{
return reader.ReadElementContentAsInt();
}
}
}
return 0;
}
The core function
Add the following function to the class, this is the function that does all the real async work:-
private IEnumerable<AsyncOperation> FindCompanies(Uri initialUri)
{
var results = new List<TaxiCompany>();
string baseURL = "http://api.search.live.net/xml.aspx?Appid=<MyAppID>&query=%22{0}%22&sources=web";
string xml = null;
yield return WebClientUtils.DownloadString(initialUri, (r) => xml = r);
foreach(var result in CreateCompaniesFromXml(xml))
{
Uri uri = new Uri(String.Format(baseURL, result.Name), UriKind.Absolute);
yield return WebClientUtils.DownloadString(uri, r => result.Total = GetTotalFromXml(r));
results.Add(result);
}
OnFindCompaniesCompleted(new FindCompaniesCompletedEventArgs(results));
}
It actually looks pretty straight forward, almost like synchonous code which is the point. It fetchs the initial xml containing the set you need, creates the set of TaxiCompany objects. It the foreaches through the set adding the Total value of each. Finally the completed event is fired with the full set of companies.
We just need to fill in the FindCompaniesAsync method:-
public void FindCompaniesAsync()
{
Uri initialUri = new Uri("ConstructUriHere", UriKind.Absolute);
FindCompanies(initialUri).Run((e) =>
{
if (e != null)
OnFindCompaniesCompleted(new FindCompaniesCompletedEventArgs(e));
});
}
I don't know what the initial Uri is or whether you need to paramatise in some way but you would just need to tweak this function. The real magic happens in the Run extension method, this jogs through all the async operations, if any return an exception then the completed event fires with Error property set.
Using the class
Now in you can consume this class like this:
var finder = new TaxiCompanyFinder();
finder.FindCompaniesCompleted += (s, args) =>
{
if (args.Error == null)
{
TaxiCompanyDisplayList.ItemsSource = args.Results;
}
else
{
// Do something sensible with args.Error
}
}
finder.FindCompaniesAsync();
You might also consider using
TaxiCompanyDisplayList.ItemsSource = args.Results.OrderByDescending(tc => tc.Total);
if you want to get the company with the highest total at the top of the list.
You can pass any object as "UserState" as part of making your asynchronous call, which will then become available in the async callback. So in your first block of code, change:
c.DownloadStringAsync(new Uri(baseURL));
c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults);
to:
TaxiCompany t = new TaxiCompany(rName, phone);
c.DownloadStringAsync(new Uri(baseURL), t);
c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults);
Which should then allow you to do this:
void findTotalResults(object sender, DownloadStringCompletedEventArgs e)
{
lock (this)
{
TaxiCompany t = e.UserState;
string s = e.Result;
...
}
}
I haven't tested this code per-se, but the general idea of passing objects to async callbacks using the eventarg's UserState should work regardless.
Have a look at the AsyncCompletedEventArgs.UserState definition on MSDN for further information.

How can I change the table adapter's command timeout

I'm using Visual Studio 2008 with C#.
I have a .xsd file and it has a table adapter. I want to change the table adapter's command timeout.
Thanks for your help.
With some small modifications csl's idea works great.
partial class FooTableAdapter
{
/**
* <summary>
* Set timeout in seconds for Select statements.
* </summary>
*/
public int SelectCommandTimeout
{
set
{
for (int i = 0; i < this.CommandCollection.Length; i++)
if (this.CommandCollection[i] != null)
this.CommandCollection[i].CommandTimeout = value;
}
}
}
To use it, just set
this.FooTableAdapter.CommandTimeout = 60; somewhere before the this.FooTableAdapter.Fill();
If you need to change the timeout on a lot of table adapters, you could create a generic extension method and have it use reflection to change the timeout.
/// <summary>
/// Set the Select command timeout for a Table Adapter
/// </summary>
public static void TableAdapterCommandTimeout<T>(this T TableAdapter, int CommandTimeout) where T : global::System.ComponentModel.Component
{
foreach (var c in typeof(T).GetProperty("CommandCollection", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetProperty | System.Reflection.BindingFlags.Instance).GetValue(TableAdapter, null) as System.Data.SqlClient.SqlCommand[])
c.CommandTimeout = CommandTimeout;
}
Usage:
this.FooTableAdapter.TableAdapterCommandTimeout(60);
this.FooTableAdapter.Fill(...);
This is a little slower. And there is the possibility of an error if you use it on the wrong type of object. (As far as I know, there is no "TableAdapter" class that you could limit it to.)
I have investigated this issue a bit today and come up with the following solution based on a few sources.
The idea is to create a base class for the table adapter too inherit which increases the timeout for all commands in the table adapter without having to rewrite too much existing code. It has to use reflection since the generated table adapters don't inherit anything useful. It exposes a public function to alter the timeout if you want to delete what i used in the constructor and use that.
using System;
using System.Data.SqlClient;
using System.Reflection;
namespace CSP
{
public class TableAdapterBase : System.ComponentModel.Component
{
public TableAdapterBase()
{
SetCommandTimeout(GetConnection().ConnectionTimeout);
}
public void SetCommandTimeout(int Timeout)
{
foreach (var c in SelectCommand())
c.CommandTimeout = Timeout;
}
private System.Data.SqlClient.SqlConnection GetConnection()
{
return GetProperty("Connection") as System.Data.SqlClient.SqlConnection;
}
private SqlCommand[] SelectCommand()
{
return GetProperty("CommandCollection") as SqlCommand[];
}
private Object GetProperty(String s)
{
return this.GetType().GetProperty(s, BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance).GetValue(this, null);
}
}
}
I had a couple of issues with using Mitchell Gilman's solution that I was eventually able to workaround.
First of all, I needed to make sure to use the right namespace. It took me a while to figure out that Designer file for the xsd data set actually contains two namespaces, one for the data set in general and one for the table adapters. So the first thing is to note is that the namespace for the table adapter should be used, not for the data set in general.
Secondly, the commandcollection may not always be initialized when the timeout command is used for the first time. To work around this, I called the InitCommandCollection command if this was the case.
So the adapted solution I used was
namespace xxx.xxxTableAdapters
partial class FooTableAdapter
{
/**
* <summary>
* Set timeout in seconds for Select statements.
* </summary>
*/
public int SelectCommandTimeout
{
set
{
if (this.CommandCollection == null)
this.InitCommandCollection();
for (int i = 0; i < this.CommandCollection.Length; i++)
if (this.CommandCollection[i] != null)
this.CommandCollection[i].CommandTimeout = value;
}
}
}
Hope that's helpful to people!
In some cases you cannot access members like Adapter in your class, since they are defined as private to the class.
Fortunately, the wizard will generate partial classes, which means you can extend them. As described in [this thread by Piebald][1], you can write your own property to set the timeout on select-commands.
Generally, you would do this:
partial class FooTableAdapter
{
/**
* <summary>
* Set timeout in seconds for Select statements.
* </summary>
*/
public int SelectCommandTimeout
{
set
{
for ( int n=0; n < _commandCollection.Length; ++n )
if ( _commandCollection[n] != null )
((System.Data.SqlClient.SqlCommand)_commandCollection[n])
.commandTimeout = value;
}
}
}
Note that I have not actually tried this myself, but it seems like a viable solution.
Say your dataset is called MySET.
There is one table called MyTable
MySETTableAdapters.MyTableTableAdapter fAdapter =
new MySETTableAdapters.MyTableTableAdapter();
fAdapter.Adapter.SelectCommand.CommandTimeout = <fill inyour value here>;
Call ChangeTimeout Function by providing the TableAdapter and Time in seconds.
this.ChangeTimeout(this.taTest, 500);
Function :
private void ChangeTimeout(Component component, int timeout)
{
if (!component.GetType().FullName.Contains("TableAdapter")) {
return;
}
PropertyInfo adapterProp = component.GetType().GetProperty("CommandCollection", BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance);
if (adapterProp == null) {
return;
}
SqlCommand[] command = adapterProp.GetValue(component, null) as SqlCommand[];
if (command == null) {
return;
}
Interaction.command(0).CommandTimeout = timeout;
}
Here's some example code from MSDN, using VB.NET:
Imports System.Data.SqlClient
Namespace MyDataSetTableAdapters
Partial Class CustomersTableAdapter
Public Sub SetCommandTimeOut(ByVal timeOut As Integer)
For Each command As SqlCommand In Me.CommandCollection
command.CommandTimeout = timeOut
Next
End Sub
End Class
End Namespace
When it comes time to call a long query, just call the SetCommandTimeOut method before the query:
Dim ds As New MyDataSet
Dim customersTA As New MyDataSetTableAdapters.CustomersTableAdapter
' Increase time-out to 60 seconds
customersTA.SetCommandTimeOut(60000)
' Do the slow query
customersTA.FillSlowQuery(ds.Customers)
There seems to be a more convenient way to do this. Here's a quick recap of what I found.
Let's say I add a (class library) project called MyDB to my solution. Into that project I add a DataSet called "Data". And into that dataset, I drag a table called "X".
What I get on the design surface is an object that shows that I have an object called "XTableAdapter".
I now open the generated code, Data.Designer.cs, and look for XTableAdapter.
When I find it, I note that it's contained in namespace MyDB.DataTableAdapters - which is just a concatenation of the name of the project, "MyDB", the name of the DataSet, "Data", and "TableAdapters".
With that in hand, I now go back to the class library, still called Class1.cs (which I'll ignore for now).
I change its namespace from MyDB to MyDB.DataTableAdapters.
I change the class declaration to public partial class XTableAdapter,
and make it look like this:
using System.Data.SqlClient;
namespace MyDB.DataTableAdapters
{
public partial class XTableAdapter
{
public void SetTimeout(int seconds)
{
foreach (SqlCommand cmd in CommandCollection)
{
cmd.CommandTimeout = seconds;
}
}
}
}
The calling sequence could hardly be clearer:
int TwoMinutes = 120;
XTableAdapter.SetTimeout(TwoMinutes);
Less muss, less fuss, less reflection (well, none), less filling.
If you use a partial class, make you have the right namespace. Probably [your data set's name] + "TableAdapters'. Example:
namespace MyProject.DataSet1TableAdapters
You can open up the Properties folder, open Settings.settings and alter the Timeout property of your connection string.
This one is a bit old now and suspect this solution is not relevant to everyone, but I've ended up using AniPol's solution to override the ObjectDataSource control as follows:
public class MyObjectDataSource : ObjectDataSource
{
public MyObjectDataSource()
{
this.ObjectCreated += this.MyObjectDataSource_ObjectCreated;
}
private void MyObjectDataSource_ObjectCreated(object sender, ObjectDataSourceEventArgs e)
{
var objectDataSourceView = sender as ObjectDataSourceView;
if (objectDataSourceView != null && objectDataSourceView.TypeName.EndsWith("TableAdapter"))
{
var adapter = e.ObjectInstance;
PropertyInfo adapterProp = adapter.GetType()
.GetProperty(
"CommandCollection",
BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance);
if (adapterProp == null)
{
return;
}
SqlCommand[] commandCollection = adapterProp.GetValue(adapter, null) as SqlCommand[];
if (commandCollection == null)
{
return;
}
foreach (System.Data.SqlClient.SqlCommand cmd in commandCollection)
{
cmd.CommandTimeout = 120;
}
}
}
}
I do like this ; Right click Fill() or GetX() function and click Goto Defination from menu.
You will see Source code of DATATABLE. And find ;
private global::System.Data.SqlClient.SqlCommand[] _commandCollection;
command line from your dataadapter class.
And Change the private to public .
Now you can access the _commandCollection and you can change all attributes.
But be careful when you add or change any Filed form DESIGNER , the public will be private again by autogenerate system.
And also , when you finish to call Fill or Get Function you must reset _commandColleciton calling this function ( InitCommandCollection() )
public void InitCommandCollection() {}
This function is also private by autogen, you must change to public also!
Example:
dsIslemlerTableAdapters.tblIslemlerTableAdapter _t = new dsIslemlerTableAdapters.tblIslemlerTableAdapter();
dsIslemler.tblIslemlerDataTable _m = new dsIslemler.tblIslemlerDataTable();
_t._commandCollection[0].CommandText = "Select * From tblIslemler Where IslemTarihi>='' And IslemTarihi<=''";
_m = _t.GetData();
_t.InitCommandCollection();
Expanding on the already very useful answers for tableadapters that helped me a lot, I also had the need to read out the actual timeout value. Thus:
namespace XTrans.XferTableAdapters
{
public partial class FooTableAdapter
{
int? _timeout = null;
///<summary>
///Get or set the current timeout in seconds for Select statements.
///</summary>
public int CurrentCommandTimeout
{
get
{
int timeout = 0;
if (_timeout != null)
{
timeout = (int)_timeout;
}
else
{
for (int i = 0; i < this.CommandCollection.Length; i++)
if (this.CommandCollection[i] != null)
timeout = this.CommandCollection[i].CommandTimeout;
}
return timeout;
}
set
{
if (this.CommandCollection == null)
this.InitCommandCollection();
for (int i = 0; i < this.CommandCollection.Length; i++)
if (this.CommandCollection[i] != null)
{
this.CommandCollection[i].CommandTimeout = value;
_timeout = value;
}
}
}
}
}
If you go to [name of DataSet].Designer.cs which is a file added under the data set file in the solution and then search for :
private void InitCommandCollection();
This is a function that you should be able to set properties for functions that have been defined in a table adapter.
The first line in that function is
this._commandCollection = new global::System.Data.IDbCommand[<number of function defined in a table adapater>];
and then in the next line for each of those function, you can set
((global::System.Data.SqlClient.SqlCommand)(this._commandCollection[<index>])).CommandTimeout = 0;
which 0 indicates no limitation and the function will not stop due to the time out and also it can be set to 10, 20, 30 or 1000 and so on
After scratching my head all day long I finally got the resolution for this. After designing your .xsd file all you need to do is go to your dataset.designer.cs page which is an auto generated page and change the code to provided below. It really works. Give it a try.
protected global::System.Data.SqlClient.SqlCommand[] CommandCollection
{
get
{
if ((this._commandCollection == null))
{
this.InitCommandCollection();
_commandCollection[0].CommandTimeout = 0;
}
_commandCollection[0].CommandTimeout = 0;
return this._commandCollection;
}
}

Unique EventId generation

I'm using the Windows Event Log to record some events. Events within the Windows Event Log can be assigned a handful of properties. One of which, is an EventID.
Now I want to use the EventId to try and group related errors. I could just pick a number for each call to the logging method I do, but that seems a little tedious.
I want the system to do this automatically. It would choose an eventId that is "unique" to the position in the code where the logging event occurred. Now, there's only 65536 unique event IDs, so there are likely to be collisions but they should be rare enough to make the EventId a useful way to group errors.
One strategy would be to take the hashcode of the stacktrace but that would mean that the first and second calls in the following code would have generate the same event ID.
public void TestLog()
{
LogSomething("Moo");
// Do some stuff and then a 100 lines later..
LogSomething("Moo");
}
I thought of walking up the call stack using the StackFrame class which has a GetFileLineNumber method. The problem with this strategy is that it will only work when built with debug symbols on. I need it to work in production code too.
Does anyone have any ideas?
Here is some code you can use to generate an EventID with the properties I describe in my question:
public static int GenerateEventId()
{
StackTrace trace = new StackTrace();
StringBuilder builder = new StringBuilder();
builder.Append(Environment.StackTrace);
foreach (StackFrame frame in trace.GetFrames())
{
builder.Append(frame.GetILOffset());
builder.Append(",");
}
return builder.ToString().GetHashCode() & 0xFFFF;
}
The frame.GetILOffset() method call gives the position within that particular frame at the time of execution.
I concatenate these offsets with the entire stacktrace to give a unique string for the current position within the program.
Finally, since there are only 65536 unique event IDs I logical AND the hashcode against 0xFFFF to extract least significant 16-bits. This value then becomes the EventId.
The IL offset number is available without debug symbols. Combined with the stack information and hashed, I think that would do the trick.
Here's an article that, in part, covers retrieving the IL offset (for the purpose of logging it for an offline match to PDB files--different problem but I think it'll show you what you need):
http://timstall.dotnetdevelopersjournal.com/getting_file_and_line_numbers_without_deploying_the_pdb_file.htm
Create a hash using the ILOffset of the last but one stack frame instead of the line number (i.e. the stack frame of your TestLog method above).
*Important: This post focuses at solving the root cause of what it appears your problem is instead of providing a solution you specifically asked for. I realize this post is old, but felt it important to contribute. *
My team had a similar issue, and we changed the way we managed our logging which has reduced production support and bug patching times significantly. Pragmatically this works in most enterprise apps my team works on:
Prefix log messages with the "class name"."function name".
For true errors, output the captured Exception to the event logger.
Focus on having clear messages as part of the peer code review as opposed to event id's.
Use a unique event id for each function, just go top to bottom and key them.
when it becomes impractical to code each function a different event ID, each class should just just have a unique one (collisions be damned).
Utilize Event categories to reduce event id reliance when filtering the log
Of course it matters how big your apps are and how sensitive the data is. Most of ours are around 10k to 500k lines of code with minimally sensitive information. It may feel oversimplified, but from a KISS standpoint it pragmatically works.
That being said, using an abstract Event Log class to simplify the process makes it easy to utilize, although cleanup my be unpleasant. For Example:
MyClass.cs (using the wrapper)
class MyClass
{
// hardcoded, but should be from configuration vars
private string AppName = "MyApp";
private string AppVersion = "1.0.0.0";
private string ClassName = "MyClass";
private string LogName = "MyApp Log";
EventLogAdapter oEventLogAdapter;
EventLogEntryType oEventLogEntryType;
public MyClass(){
this.oEventLogAdapter = new EventLogAdapter(
this.AppName
, this.LogName
, this.AppName
, this.AppVersion
, this.ClassName
);
}
private bool MyFunction() {
bool result = false;
this.oEventLogAdapter.SetMethodInformation("MyFunction", 100);
try {
// do stuff
this.oEventLogAdapter.WriteEntry("Something important found out...", EventLogEntryType.Information);
} catch (Exception oException) {
this.oEventLogAdapter.WriteEntry("Error: " + oException.ToString(), EventLogEntryType.Error);
}
return result;
}
}
EventLogAdapter.cs
class EventLogAdapter
{
//vars
private string _EventProgram = "";
private string _EventSource = "";
private string _ProgramName = "";
private string _ProgramVersion = "";
private string _EventClass = "";
private string _EventMethod = "";
private int _EventCode = 1;
private bool _Initialized = false;
private System.Diagnostics.EventLog oEventLog = new EventLog();
// methods
public EventLogAdapter() { }
public EventLogAdapter(
string EventProgram
, string EventSource
, string ProgramName
, string ProgramVersion
, string EventClass
) {
this.SetEventProgram(EventProgram);
this.SetEventSource(EventSource);
this.SetProgramName(ProgramName);
this.SetProgramVersion(ProgramVersion);
this.SetEventClass(EventClass);
this.InitializeEventLog();
}
public void InitializeEventLog() {
try {
if(
!String.IsNullOrEmpty(this._EventSource)
&& !String.IsNullOrEmpty(this._EventProgram)
){
if (!System.Diagnostics.EventLog.SourceExists(this._EventSource)) {
System.Diagnostics.EventLog.CreateEventSource(
this._EventSource
, this._EventProgram
);
}
this.oEventLog.Source = this._EventSource;
this.oEventLog.Log = this._EventProgram;
this._Initialized = true;
}
} catch { }
}
public void WriteEntry(string Message, System.Diagnostics.EventLogEntryType EventEntryType) {
try {
string _message =
"[" + this._ProgramName + " " + this._ProgramVersion + "]"
+ "." + this._EventClass + "." + this._EventMethod + "():\n"
+ Message;
this.oEventLog.WriteEntry(
Message
, EventEntryType
, this._EventCode
);
} catch { }
}
public void SetMethodInformation(
string EventMethod
,int EventCode
) {
this.SetEventMethod(EventMethod);
this.SetEventCode(EventCode);
}
public string GetEventProgram() { return this._EventProgram; }
public string GetEventSource() { return this._EventSource; }
public string GetProgramName() { return this._ProgramName; }
public string GetProgramVersion() { return this._ProgramVersion; }
public string GetEventClass() { return this._EventClass; }
public string GetEventMethod() { return this._EventMethod; }
public int GetEventCode() { return this._EventCode; }
public void SetEventProgram(string EventProgram) { this._EventProgram = EventProgram; }
public void SetEventSource(string EventSource) { this._EventSource = EventSource; }
public void SetProgramName(string ProgramName) { this._ProgramName = ProgramName; }
public void SetProgramVersion(string ProgramVersion) { this._ProgramVersion = ProgramVersion; }
public void SetEventClass(string EventClass) { this._EventClass = EventClass; }
public void SetEventMethod(string EventMethod) { this._EventMethod = EventMethod; }
public void SetEventCode(int EventCode) { this._EventCode = EventCode; }
}
Thanks for the idea of hashing the call stack, I was going to ask that very same question of how to pick an eventId.
I recommend putting a static variable in LogSomething that increments each time it is called.
Now I want to use the EventId to try
and group related errors.
You have filters in event viewer so why (Go to find ? You have 65536 unique event IDs too.
Or rather use log4net or something ??
just my ideas....

Categories