Printing useful output with Log4net in Application_Error and LINQ queries - c#

I'm new to this stuff but what I'm trying to do is filter the log4net log I've bungled into Application_Error by some particular bits of information, such as HTTP_USER_AGENT REQUEST TYPE CONTENT_TYPE HTTP_REFERER.
The code I have so far is:
string[] vars = {"IP address", "X-Forwarded-For", "HTTP_USER_AGENT","REQUEST TYPE","CONTENT_TYPE","HTTP_REFERER"};
var param = Request.Params;
var paramEnum = param.GetEnumerator();
while (paramEnum.MoveNext())
{
foreach (var paramVar in vars.Where(paramVar => paramVar == paramEnum.Current.ToString()))
{
Log.Error("\nparam:" + paramVar);
}
}
But it looks ugly and moreover I think I'm not on the right track in terms of a succinct LINQ query, especially using both a while and foreach loop.
I'm new to LINQ as I say and resharper created the LINQ for me - if you feel I'm lacking in certain areas please let me know what I'd need to further my understanding of collections/querying.
Is this the most performant way of doing things, or am I on the wrong track altogether?

It's better to iterate through vars instead
foreach (var paramVar in vars)
{
var value = Request.Params.Get(paramVar);
if (!string.IsNullOrEmpty(value))
{
Log.Error("\nparam:" + paramVar);
}
}
or using LINQ
var query = from paramVar in vars
let value = Request.Params.Get(paramVar)
where !string.IsNullOrEmpty(value)
select paramVar;
foreach (var paramVar in query)
{
Log.Error("\nparam:" + paramVar);
}

Related

LINQ or Lambda for two for loops

The code I have written works fine, this inquiry being purely for educational purposes. I want to know how others would do this better and cleaner. I especially hate the way I use two for loops to get data. There has to be a more efficient way.
I tried to do with LINQ but one of them is a class and the other one is just a string[]. So I couldn't figure out how to use it.
I have got a Document Name Table in my SQL database and Files in Content Folder.
I have got a Two list- ListOfFileNamesSavedInTheDB and ListOfFileNamesInTheFolder.
Basically, I am getting all file names saved in Database and checking is it exist in the Folder, if not delete file name from the database.
var clientDocList = documentRepository.Documents.Where(c => c.ClientID == clientID).ToList();
if (Directory.Exists(directoryPath))
{
string[] fileList = Directory.GetFiles(directoryPath).Select(Path.GetFileName).ToArray();
foreach (var clientDoc in clientDocList)
{
bool fileNotExist = true;
foreach (var file in fileList)
{
if (clientDoc.DocFileName.Trim().ToUpper()==file.ToUpper().Trim())
{
fileNotExist = false;
break;
}
}
if (fileNotExist)
{
documentRepository.Delete(clientDoc);
}
}
}
I am not exactly sure of how you want your code to work but I believe you need something like this
//string TextResult = "";
ClientDocList documentRepository = GetClientDocList();
var directoryPath = "";
var clientID = 1;
var clientDocList = documentRepository.Documents.Where(c => c.ClientID == clientID).ToList();
if (Directory.Exists(directoryPath) || true) // I need to pass your condition
{
string[] files = new string[] { "file1", "file5", "file6" };
List<string> fileList = files.Select(x => x.Trim().ToUpper()).ToList(); // I like working with lists, if you want an array it's ok
foreach (var clientDoc in clientDocList.Where(c => !fileList.Contains(c.DocFileName.Trim().ToUpper())))
{
//TextResult += $" {clientDoc.DocFileName} does not exists so you have to delete it from db";
documentRepository.Delete(clientDoc);
}
}
//Console.WriteLine(TextResult);
To be honest, I really don't like this line
fileList = files.Select(x => x.Trim().ToUpper()).ToList()
so I would suggest you add a helper function comparing the list of file names to the specific file name
public static bool TrimContains(List<string> names, string name)
{
return names.Any(x => x.Trim().Equals(name.Trim(), StringComparison.InvariantCultureIgnoreCase));
}
and your final code would become
List<string> fileList = new List<string>() { "file1", "file5", "file6" };
foreach (var clientDoc in clientDocList.Where(c => !TrimContains(fileList, c.DocFileName)))
{
//TextResult += $" {clientDoc.DocFileName} does not exists so you have to delete it from db";
documentRepository.Delete(clientDoc);
}
Instead of retrieving all documents from database and do the checking in memory, I suggest to check which document doesn't exist in folder in one query:
if (Directory.Exists(directoryPath))
{
var fileList = Directory.GetFiles(directoryPath).Select(Path.GetFileName);
var clientDocList = documentRepository.Documents.Where(c => c.ClientID == clientID && !fileList.Contains(c.DocFileName.Trim())).ToList();
documentRepository.Documents.RemoveRange(clientDocList);
}
Note: this is just a sample to demonstrate the idea, may have syntax error somewhere since I don't have IDE with me at the moment. But the idea is there
This code is not only shorter but also more efficient since it only uses a single query to retrieve documents from database. I assume the number of files in a folder is not too large to convert to SQL by EF

Optimizing loop

I'm trying to implement a tool that groups certain strings based on the lemmas of their words. During the initialization I make a dictionary for each possible group containing a list of words that would group into this key. This is what I have so far:
public Dictionary<string, HashSet<string>> Sets { get; set; }
private void Initialize(IStemmer stemmer)
{
// Stemming of keywords and groups
var keywordStems = new Dictionary<string, List<string>>();
var groupStems = new Dictionary<string, List<string>>();
foreach (string keyword in Keywords)
{
keywordStems.Add(keyword, CreateLemmas(keyword, stemmer));
foreach (string subset in CreateSubsets(keyword))
{
if (subset.Length > 1 && !groupStems.ContainsKey(subset))
{
groupStems.Add(subset, CreateLemmas(subset, stemmer));
}
}
}
// Initialize all viable sets
// This is the slow part
foreach (string gr in groupStems.Keys)
{
var grStems = groupStems[gr];
var grKeywords = new HashSet<string>((from kw in Keywords
where grStems.All(keywordStems[kw].Contains)
select kw));
if (grKeywords.Count >= Settings.MinCount)
{
Sets.Add(gr, grKeywords);
}
}
}
Is there any way that I can speed the bottleneck of this method up?
The answer of #mjwills is a good idea. It seems likely that this is the most expensive operation:
var grKeywords = new HashSet<string>((
from kw in Keywords
where grStems.All(keywordStems[kw].Contains)
select kw));
The suggestion is to optimize the Contains by taking advantage of the fact that the stems are a set. But if they're a set then why are we repeatedly asking for containment at all? They're a set; do set operations. The question is "what are the keywords such that every member of the grStem set is contained within the keyword's stem set". "Is every member of this set contained in that set" is the subset operation.
var grKeywords = new HashSet<string>((
from kw in Keywords
where grStems.IsSubsetOf(keywordStems[kw])
select kw));
The implementation of IsSubsetOf is optimized for common scenarios like "both operands are sets". And it takes early outs; if your group stems set is larger than the keyword stem set then you don't need to check every element; one of them is going to be missing. But your original algorithm checks every element anyways, even when you could bail early and save all that time.
And again #mjwills has a good idea which I'll suggest some possible improvements to. The idea here is to execute the query, cache the results in an array, and only later realize it as a hash set, if necessary:
foreach (var entry in groupStems)
{
var grStems = entry.Value;
var grKeywords = (WHATEVER).ToArray();
if (grKeywords.Length >= Settings.MinCount)
Sets.Add(entry.Key, new HashSet<string>(grKeywords));
}
First: I actually doubt that avoiding the unnnecessary hash set construction by replacing it with unnecessary array constructions is a win. Measure it and see.
Second: ToList can be faster than ToArray because a list can be constructed before you know the size of the query result set. ToArray basically has to do a ToList first, and then copy the results into an exactly-sized array. So if ToArray is not a win, ToList might be. Or not. Measure it.
Third: I note that the whole thing can be rewritten into a query should you prefer that style.
var q = from entry in groupStems
let grStems = entry.Value
let grKeywords = new HashSet<string>(WHATEVER)
where grKeywords.Count >= Settings.MinCount
select (entry.Key, grKeywords);
var result = q.ToDictionary( ... and so on ... )
That's probably not faster, but it might be easier to reason about.
One suggestion would be to change:
var keywordStems = new Dictionary<string, List<string>>();
to:
var keywordStems = new Dictionary<string, HashSet<string>>();
That should have an impact due to your later Contains call:
var grKeywords = new HashSet<string>((from kw in Keywords
where grStems.All(keywordStems[kw].Contains)
select kw));
because Contains is generally faster on a HashSet than a List.
Also consider changing:
foreach (string gr in groupStems.Keys)
{
var grStems = groupStems[gr];
var grKeywords = new HashSet<string>((from kw in Keywords
where grStems.All(keywordStems[kw].Contains)
select kw));
if (grKeywords.Count >= Settings.MinCount)
{
Sets.Add(gr, grKeywords);
}
}
to:
foreach (var entry in groupStems)
{
var grStems = entry.Value;
var grKeywords = (from kw in Keywords
where grStems.All(keywordStems[kw].Contains)
select kw).ToArray();
if (grKeywords.Length >= Settings.MinCount)
{
Sets.Add(entry.Key, new HashSet<string>(grKeywords));
}
}
By shifting the HashSet initialization (which is relatively expensive compared to initializing an Array) into the if statement then you may improve performance if the if is entered relatively rarely (in your comments you state it is entered roughly 25% of the time).

How do I insert a statement into a block with normalized whitespace using Roslyn?

I'm new to Roslyn. I'm writing a code fix provider that transforms foreach blocks that iterate through the results of a Select, e.g.
foreach (var item in new int[0].Select(i => i.ToString()))
{
...
}
to
foreach (int i in new int[0])
{
var item = i.ToString();
...
}
To do this, I need to insert a statement at the beginning of the BlockSyntax inside the ForEachStatementSyntax that represents the foreach block. Here is my code for that:
var blockStatement = forEach.Statement as BlockSyntax;
if (blockStatement == null)
{
return document;
}
forEach = forEach.WithStatement(
blockStatment.WithStatements(
blockStatement.Statements.Insert(0, selectorStatement));
Unfortunately, doing that results in the whitespace being off:
foreach (int i in new int[0])
{
var item = i.ToString();
...
}
I Googled solutions for this. I came across this answer, which recommended using either Formatter.Format or SyntaxNode.NormalizeWhitespace.
I can't use Formatter.Format because that takes a Workspace parameter, and it looks I don't have access to a Workspace per Roslyn: Current Workspace in Diagnostic with code fix project.
I tried using NormalizeWhitespace() on the syntax root of the document, but that invasively formatted other code not related to the fix. I tried using it on just the ForEachStatementSyntax associated with the foreach block, and then calling syntaxRoot = syntaxRoot.ReplaceNode(oldForEach, newForEach), but that results in the entire foreach block not being properly indented.
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var array = new int[0];
int length = array.Length;
foreach (int i in array)
{
string item = i.ToString();
} }
}
}
So is it possible to simply insert the statement with the correct indentation in the first place, without having to format other code?
Thanks.
You can add the Formatter Annotation to the nodes that you want the formatter to run on using WithAdditionalAnnotations
blockStatement.Statements.Insert(0, selectorStatement.WithAdditionalAnnotations(Formatter.Annotation))

How to get query names that references the particular iteration path

With selected project name, I had loaded iteration paths. Now I need to get the query names that references the selected iteration path.
Code to load iteration paths passing project name:
private void LoadIterationPaths(string projectName)
{
var tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(_tfs.Uri);
var wiStore = tfs.GetService<WorkItemStore>();
var projCollections = wiStore.Projects;
var detailsOfTheSelectedProject = projCollections.Cast<Project>().Where(project => !String.IsNullOrEmpty(_selectedTeamProject.Name))
.FirstOrDefault(project => project.Name.Contains(_selectedTeamProject.Name));
var iterationPathsList = GetIterationPaths(detailsOfTheSelectedProject);
foreach (var iterationPath in iterationPathsList.Where(iterationPath => iterationPath.Contains(projectName)))
{
cmbIterationPath.Items.Add(iterationPath);
}
cmbIterationPath.Enabled = cmbIterationPath.Items.Count > 0;
}
Now, I need to get the list of Query names that references the selected iteration Path. Thanks.
Note: I am able to get all the query names in a project but that i don't need.
For that I used the below code
foreach (StoredQuery qi in detailsOfTheSelectedProject.StoredQueries)
{
cmbQueries.Items.Add(qi.Name);
}
Your code should looks like this
string selectedIterationPath = ...
foreach (StoredQuery qi in detailsOfTheSelectedProject.StoredQueries) {
if (qi.QueryText.Contains(selectedIterationPath) {
cmbQueries.Items.Add(qi.Name);
}
}
This is what me and Beytan Kurt suggested in the comments.
Instead of a dumb Contains, you should use a Regular Expression to account for false positives and negatives.

LINQ Query to get Column Headers in Silverlight

I am working on a Silverlight application using a WCF service where I need to get all the Column Headers from a specific table. I have been trying to write a LINQ query to do this, but so far I have not been able to get it to work correctly. I have not found very much information pertaining to this. I have found the following information, but I have had difficulties connecting to my data.
http://www.c-sharpcorner.com/UploadFile/dhananjaycoder/4856/#ReadAndPostComment
So far I have tried the following...This will not compile due to DataContext needing a parameter and that is where I am stuck.
public List<string> GetColumnHeaders()
{
DataContext context = new DataContext();
List<string> columnList = new List<string>();
var dataModel = context.Mapping;
foreach (var r in dataModel.GetTables())
{
if (r.TableName.Equals("table1", StringComparison.InvariantCultureIgnoreCase))
{
foreach (var c in r.RowType.DataMembers)
{
columnList.Add(c.MappedName);
}
}
}
return columnList;
}
Instead of using DataContext context = new DataContext();
I tried the following, but I know the problem is the same.
var dataModel = new AttributeMappingSource()
.GetModel(
typeof(RepositoryBase<HBS_SondesEntities>
));
Here is my best attempt at a solution, its hard to really understand what you have tried/written.
public List<string> GetColumnHeaders(){
List<string> columnList = new List<string>();
using (SondesEntities context = new HBS_SondesEntities()){
foreach (var r in context.Mapping.GetTables()){
if (r.TableName
.Equals("table1", StringComparison.InvariantCultureIgnoreCase)) {
foreach (var c in r.RowType.DataMembers){
columnList.Add(c.MappedName);
}
}
}
}
return columnList;
}
Assuming I didn't fat finger something here is the same code using linq.
public List<string> GetColumnHeaders(){
List<string> columnList = new List<string>();
using (SondesEntities context = new HBS_SondesEntities()){
var query = (
context.Mapping.GetTables()
.Where(t=>t.TableName
.Equals(
"table1",
StringComparison.InvariantCultureIgnoreCase)
)
).SelectMany(x=>x.RowType.DataMembers);
columnList = query.Select(m=>m.MappedName).ToList()
}
return columnList;
}
This might help:
http://jesseliberty.com/2009/08/13/linq-for-silverlight-developers/
I'm not sure what you mean by table, but if its a datagrid, the link should help.

Categories