I'm trying to execute the following code, where two columns full of game ids in my DB are matched, then three separate queries are executed to pull related information for the game ids that matched, and it's all packaged nicely in an e-mail.
foreach (var TempId in ResultGameIDCompare)
var command1 = "EXEC DB query";
var command2 = "EXEC DB query";
var command3 = "EXEC DB query";
var GameInfo = db2.Query(command1, (string)TempId.Game_ID);
var SystemInfo = db2.Query(command2, (string)TempId.Game_ID);
var EditionInfo = db2.Query(command3, (string)TempId.Game_ID);
foreach (var result in GameInfo)
{
foreach (var result2 in SystemInfo)
{
foreach (var result3 in EditionInfo)
{
var text = " <ul>"+
"<p>My game information is as follows:" +#result.Name+"</p>"+
" <strong>System:</strong> " #result2.System+
" <strong>Price:</strong> " +#result3.Price+
" <strong>Edition:</strong> " +#result3.Edition+
"<ul>";
WebMail.Send("Test#test.com",
"This is a test",
body : text,
isBodyHtml:true );
}
}
}
The code works but if one of my queries returns an empty row nothing is fed into the collection for that game id and understandably no e-mail is generated for that game id.
So I'm trying to re-work my code so an e-mail is still generated even if an empty row is returned for the SystemInfo or EditionInfo query(GameInfo will never return an empty row) but I'm having difficulty figuring out how to go about this. I've attempted modifying my code as follows:
var Blank = " ";
foreach (var TempId in ResultGameIDCompare)
var command1 = "EXEC DB query";
var command2 = "EXEC DB query";
var command3 = "EXEC DB query";
var GameInfo = db2.Query(command1, (string)TempId.Game_ID);
var SystemInfo = db2.Query(command2, (string)TempId.Game_ID);
var EditionInfo = db2.Query(command3, (string)TempId.Game_ID);
foreach (var result in GameInfo)
{
foreach (var result2 in SystemInfo.DefaultIfEmpty(Blank))
{
foreach (var result3 in EditionInfo.DefaultIfEmpty(Blank))
{
var text = " <ul>"+
"<p>My game information is as follows:" +#result.Name+"</p>"+
" <strong>System:</strong> " #result2.System+
" <strong>Price:</strong> " +#result3.Price+
" <strong>Edition:</strong> " +#result3.Edition+
"<ul>";
WebMail.Send("Test#test.com",
"This is a test",
body : text,
isBodyHtml:true );
}
}
}
But it returns error "string' does not contain a definition for 'System'" and I'm honestly not sure if this alteration will do what I think it will do. Return " " in positions no data was returned from the query.
foreach (var result in GameInfo)
{
StringBuilder sb = new StringBuilder();
sb.Append("<ul>");
sb.AppendFormat("<p>My game information is as follows: {0}</p>", #result.Name);
foreach (var result2 in SystemInfo)
{
sb.AppendFormat("<strong>System: {0}</strong>", #result2.System);
foreach (var result3 in EditionInfo)
{
sb.AppendFormat("<strong>Price: {0}</strong>", #result3.Price);
sb.AppendFormat("<strong>Edition: {0}</strong>", #result3.Edtion);
}
}
sb.Append("</ul>");
WebMail.Send("Test#test.com",
"This is a test",
body: sb.ToString(),
isBodyHtml: true);
}
Just move the creation of the string outside into the first loop, and add to it in the inner loops as you get more data. StringBuilder is a high performance string concatenation system in the System.Text namespace and avoids pricey + concatenations.
Your variable Blank needs to be an IEnumerable of the same type as your original collection.
If your SystemInfo is a collection of strings,
SystemInfo.DefaultIfEmpty(new[]{ Blank })
will do.
Related
I'm trying to pull some information from our VSTS hosted evironment that I need to present as report. What I observed that the API library is reporting there are no related items associated with that WorkItem even though I see related links from the VSTS web app.
Here is my code -
void Main()
{
string url = "https://[redacted].visualstudio.com";
string token = "[redacted]";
string project = "[redacted]";
string version = "[redacted]";
VssConnection conn = GetConnection(url, token);
WorkItemTrackingHttpClient witClient = conn.GetClient<WorkItemTrackingHttpClient>();
Wiql q = new Wiql();
q.Query = $"SELECT * FROM WorkItems WHERE [System.TeamProject] = '{project}' AND [System.Tags] CONTAINS '{version}' AND [System.WorkItemType] IN ('Product Backlog Item', 'Defect') ORDER BY [System.CreatedDate] desc";
var qi = witClient.QueryByWiqlAsync(q).Result;
var ids = qi.WorkItems.Select(x => x.Id);
var workitems = witClient.GetWorkItemsAsync(ids).Result.Select(r =>
{
return new
{
ItemId = r.Id,
ItemAssignedTo = r.Fields["System.AssignedTo"],
ItemCreatedBy = r.Fields["System.CreatedBy"],
ItemTitle = r.Fields["System.Title"],
ItemType = r.Fields["System.WorkItemType"],
State = r.Fields["System.State"],
ItemHasDescription = r.Fields.ContainsKey("System.Description") ? "Yes" : "No",
ItemHasAcceptanceCriteria = r.Fields.ContainsKey("Microsoft.VSTS.Common.AcceptanceCriteria") ? "Yes" : "No",
RelatedItems = r.Fields.ContainsKey("System.RelatedLinkCount") ? r.Fields["System.RelatedLinkCount"] : null //This line reports no related links,
Links = r.Links != null ? r.Links.Links : null //So does this line report null
};
});
workitems.Dump();
conn.Disconnect();
}
private static VssConnection GetConnection(string accountUri, string personalAccessToken)
{
var cred = new VssBasicCredential(string.Empty, personalAccessToken);
VssHttpMessageHandler vssHandler = new VssHttpMessageHandler(cred, VssClientHttpRequestSettings.Default.Clone());
return new VssConnection(
new Uri(accountUri),
vssHandler,
new DelegatingHandler[] { new SuppressHandler() });
}
public class SuppressHandler : DelegatingHandler
{
}
Also I'm getting these console logging which I'd like to avoid.
Web method running: [https://[redacted].visualstudio.com/_apis/wit/wiql] (POST)wiql[wit]
Is there a way I can remove these console logging?
Cannot figure out the problem based on your code.
However you can use below code sample to retrieve the work item information from VSTS, it works on my side:
To avoid getting these console logging, you can disable the Program output :
Right-click in the Output Window --> deselect the option Program output, then try it again.
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using System;
using System.Collections.Generic;
using System.Linq;
namespace QueryWorkitems0619
{
class Program
{
static void Main(string[] args)
{
Uri uri = new Uri("https://{account}.visualstudio.com");
string PAT = "TokenHere";
string project = "ProjectName";
VssBasicCredential credentials = new VssBasicCredential("", PAT);
//create a wiql object and build our query
Wiql wiql = new Wiql()
{
Query = "Select * " +
"From WorkItems " +
"Where [Work Item Type] IN ('Product Backlog Item', 'Task') " +
"And [System.TeamProject] = '" + project + "' " +
"And [System.State] <> 'Closed' " +
"And [System.RelatedLinkCount] > '0'" +
"Order By [State] Asc, [Changed Date] Desc"
};
//create instance of work item tracking http client
using (WorkItemTrackingHttpClient workItemTrackingHttpClient = new WorkItemTrackingHttpClient(uri, credentials))
{
//execute the query to get the list of work items in the results
WorkItemQueryResult workItemQueryResult = workItemTrackingHttpClient.QueryByWiqlAsync(wiql).Result;
//some error handling
if (workItemQueryResult.WorkItems.Count() != 0)
{
//need to get the list of our work item ids and put them into an array
List<int> list = new List<int>();
foreach (var item in workItemQueryResult.WorkItems)
{
list.Add(item.Id);
}
int[] arr = list.ToArray();
//build a list of the fields we want to see
string[] fields = new string[3];
fields[0] = "System.Id";
fields[1] = "System.Title";
fields[2] = "System.RelatedLinkCount";
//get work items for the ids found in query
var workItems = workItemTrackingHttpClient.GetWorkItemsAsync(arr, fields, workItemQueryResult.AsOf).Result;
Console.WriteLine("Query Results: {0} items found", workItems.Count);
//loop though work items and write to console
foreach (var workItem in workItems)
{
Console.WriteLine("ID:{0} Title:{1} RelatedLinkCount:{2}", workItem.Id, workItem.Fields["System.Title"], workItem.Fields["System.RelatedLinkCount"]);
}
Console.ReadLine();
}
}
}
}
}
I just had a similar problem, what you have to do is use the WorkItemExpand parameter of GetWorkItemAsync method like below:
var item = client.GetWorkItemAsync(c_projectName, id, null, null, WorkItemExpand.Relations).Result;
If not used the Relations property is null, which is quite misleading. If used it stores the proper information about related work.
I'm trying to build a insert query that uses all the textboxes names (the keys so to speak in KeyValuePair<>) so I will not have to type out all the named parameters (ie the textboxes name) but instead just use all of the textboxes names from the list.
My code is as follows:
private void buttonInsert_Click(object sender, EventArgs e)
{
using (members.DBConnection = new System.Data.OleDb.OleDbConnection(#"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\qbc.mdb"))
{
members.DBConnection.Open();
// find out which textboxes were filled out
foreach (Control ctrl in Controls)
{
if (ctrl is TextBox)
{
members.textBoxes.Add(new System.Collections.Generic.KeyValuePair<string, string>(((TextBox)ctrl).Name, ((TextBox)ctrl).Text));
}
}
for (int i = 0; i < members.textBoxes.Count; i++)
{
members.DBCommand.Parameters.AddWithValue(String.Format("#{0}", members.textBoxes[i].Key),
!string.IsNullOrEmpty(members.textBoxes[i].Value) ? members.textBoxes[i].Value : "");
}
using (members.DBCommand = new System.Data.OleDb.OleDbCommand("INSERT INTO members (fullName, birthday, phoneNumber, address, email, status, anniversary)" +
"VALUES(#" + members.textBoxes + ")", members.DBConnection))
{
try
{
members.DBCommand.ExecuteNonQuery();
}
catch (System.Data.OleDb.OleDbException ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
}
}
But when I try to use members.textBoxes.Key (on the using members.DBCommand line, it is giving me an error:
List<KeyValuePair<string, string>> does not contain a definition for 'Key' and no extension method named 'Key' accepting a first argument of 'List<KeyValuePair<string,string>>' could be found (are you missing a using directive or assembly reference?)
I would really prefer not to type out all the names of the textboxes in the VALUES part of the query, if there is a way to do that.
Any help would be appreciated
Thanks!
Update -
I have changed my code to so it follows some answers given
private void buttonInsert_Click(object sender, EventArgs e)
{
// associate the textboxes with the column fields in the database
// long but meh
// household head textboxes
textBox_hh.Tag = "fullName";
textBoxHHBirthday.Tag = "birthday";
textBoxPhone.Tag = "phoneNumber";
textBoxAddress.Tag = "address";
textBoxEmail.Tag = "email";
textBoxStatus.Tag = "status";
using (members.DBConnection = new System.Data.OleDb.OleDbConnection(#"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\qbc.mdb"))
{
members.DBConnection.Open();
// find out which textboxes were filled out
foreach (var textbox in Controls.Cast<Control>().OfType<TextBox>())
{
var pair = new KeyValuePair<string, string>(textbox.Tag.ToString(), textbox.Text);
members.textBoxes.Add(pair);
}
var columnNames = string.Join(", ", members.textBoxes.Select(m => m.Key));
var parameterNames = string.Join(", ", members.textBoxes.Select(m => $"#{m.Key}"));
var query = $"INSERT INTO members ({columnNames}) VALUES ({parameterNames})";
using (members.DBCommand = new System.Data.OleDb.OleDbCommand(query, members.DBConnection))
{
try
{
foreach (var member in members.textBoxes)
{
members.DBCommand.Parameters.AddWithValue($"#{member.Key}", member.Value);
}
members.DBCommand.ExecuteNonQuery();
}
catch (System.Data.OleDb.OleDbException ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
}
}
but unfortunately the query now is just INSERT INTO members () VALUES ()
If your table's columns' names are the same as your textboxes names:
using (members.DBCommand = new System.Data.OleDb.OleDbCommand("INSERT INTO members (" + String.Join(", ", members.textBoxes.Select(tb => tb.Key).ToArray()) + ")" +
" VALUES(" + String.Join(", ", members.textBoxes.Select(tb => "'" + tb.Value+ "'").ToArray()) + ")", members.DBConnection))
Else and if the items of the list textboxes are added in the same order of your columns then try this:
using (members.DBCommand = new System.Data.OleDb.OleDbCommand("INSERT INTO members (fullName, birthday, phoneNumber, address, email, status, anniversary)" +
" VALUES(" + String.Join(", ", members.textBoxes.Select(tb => "#" + tb.Value).ToArray()) + ")", members.DBConnection))
I think you are now spending more time for trying generate insert query automatically then if you have typed it manually.
For generating proper insert query you need loop column names and column values(read names of parameters).
You definitely should use SqlParameters for values
var columnNames = string.Join(", ", members.textBoxes.Select(m => m.Key));
var parameterNames = string.Join(", ", members.textBoxes.Select(m => $"#{m.Key}"));
var query = $"INSERT INTO members ({columnNames}) VALUES ({parameterNames})";
using (var command = new OleDbCommand(query, members.DBConnection))
{
foreach (var member in members.textBoxes)
{
command.Parameters.AddWithValue($"#{member.Key}", member.Value)
}
command.ExecuteNonQuery();
}
You can associate column names with textboxes by using .Tag property
textboxFullName.Tag = "fullName";
Then create key value pairs with column names as a key
foreach (var textbox in Controls.Cast<Control>().OfType<TextBox>())
{
var pair = new KeyValuePair<string, string>(textbox.Tag.ToString(), textbox.Text);
members.textBoxes.Add(pair);
}
I have a stored procedure with around 14 different result sets. How do I retrieve them all as by now I only get the first result set.
[HttpGet]
[Route("tire-tabel")]
public List<DeviationCalculation_Result> TireTabel(decimal presentWidth, decimal presentAspectRatio, string presentRimSize, int maxDeviation)
{
using (var context = new OminiTireEntities())
{
var result = context.Database.SqlQuery<DeviationCalculation_Result>(
"exec [Tabel].[DeviationCalculation] #PresentWidth = '" + presentWidth + "', " +
"#PresentAspectRatio= '" + presentAspectRatio + "', " +
"#PresentInches= '" + presentRimSize + "', " +
"#MaxDeviation= '" + maxDeviation + "'").ToList<DeviationCalculation_Result>();
return result;
}
}
Sample Code:
using (var db = new BloggingContext())
{
// If using Code First we need to make sure the model is built before we open the connection
// This isn't required for models created with the EF Designer
db.Database.Initialize(force: false);
// Create a SQL command to execute the sproc
var cmd = db.Database.Connection.CreateCommand();
cmd.CommandText = "[dbo].[GetAllBlogsAndPosts]";
try
{
db.Database.Connection.Open();
// Run the sproc
var reader = cmd.ExecuteReader();
// Read Blogs from the first result set
var blogs = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<Blog>(reader, "Blogs", MergeOption.AppendOnly);
foreach (var item in blogs)
{
Console.WriteLine(item.Name);
}
// Move to second result set and read Posts
reader.NextResult();
var posts = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<Post>(reader, "Posts", MergeOption.AppendOnly);
foreach (var item in posts)
{
Console.WriteLine(item.Title);
}
}
finally
{
db.Database.Connection.Close();
}
}
The Translate method accepts the reader that we received when we executed the procedure, an EntitySet name, and a MergeOption. The EntitySet name will be the same as the DbSet property on your derived context. The MergeOption enum controls how results are handled if the same entity already exists in memory.
Reference : https://msdn.microsoft.com/en-us/library/jj691402(v=vs.113).aspx
I also recommend to use Parameters instead of executing the queries as mentioned in the question as it can result in SQL injection
With Dapper it is super simple:
public DeviationCalculationResult Get(decimal presentWidth, decimal presentAspectRatio, string presentRimSize, int maxDeviation)
{
using (var context = new OminiTireEntities())
{
var reader = context.Database.Connection.QueryMultiple("[Tabel].[DeviationCalculation]",
new
{
PresentWidth = presentWidth,
PresentAspectRatio = presentAspectRatio,
PresentInches = presentRimSize,
MaxDeviation = maxDeviation
}, commandType: CommandType.StoredProcedure);
var first = reader.Read<First>().ToList().First();
var second = reader.Read<Second>().ToList().First();
var third = reader.Read<Third>().ToList().First();
//...and so on...
return new DeviationCalculationResult
{
First = first,
Second = second,
Third = third,
//...
};
}
}
I'm new to Dapper, and writing a query that will pull from a provided schema and table, along with using dynamic ordering and filtering.
Dapper make dynamic parameters very simple, however, I'm not sure how to do this with tables in the order by and where clauses. Here's my method below, and I see the issues with SQL injection:
public GridData GetGridData(string schema, string table, TableDataParameters tableDataParameters)
{
using (var dbConnection = VarConnection)
{
dbConnection.Open();
if (!this.TableExists(dbConnection, schema, table))
{
throw new ItemNotFoundException($"Could not locate table {schema}.{table}.");
}
string orderyByClause = string.Join(",", tableDataParameters.SortModel.Select(s => $"[{s.ColId}] {(s.Sort.ToLower() == "asc" ? "asc" : "desc")}"));
var parameters = new DynamicParameters();
string whereClause;
if (tableDataParameters.FilterModel == null || !tableDataParameters.FilterModel.Any())
{
whereClause = "1=1";
}
else
{
whereClause = string.Join(" AND ", tableDataParameters.FilterModel.Select((fm, i) =>
{
string whereParam = $"whereParam{i}";
parameters.Add(whereParam, fm.Filter);
if (fm.Operation == "startsWith")
{
return $"[{fm.Column}] LIKE #{whereParam} + '%'";
}
throw new InvalidOperationException($"Unsupported filter operation '{fm.Operation}'");
}));
}
var query = $"SELECT COUNT(1) [total] " +
$"FROM [{schema}].[{table}] " +
$"WHERE {whereClause} " +
$"SELECT * " +
$"FROM [{schema}].[{table}] " +
$"WHERE {whereClause} " +
$"ORDER BY {orderyByClause} " +
$"OFFSET {tableDataParameters.StartIndex.Value} ROWS " +
$"FETCH NEXT {tableDataParameters.StopIndex.Value - tableDataParameters.StartIndex.Value} ROWS ONLY";
int total = 0;
using (var reader = dbConnection.ExecuteReader(query, parameters))
{
// First batch, it's the count
if (reader.Read())
{
total = reader.GetInt32(0);
}
var gridColumns = new List<GridColumn>();
var gridRows = new List<string[]>();
if (reader.NextResult() && reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
string key = reader.GetName(i);
gridColumns.Add(new GridColumn(key, key, null, ""));
}
var items = new object[reader.FieldCount];
reader.GetValues(items);
gridRows.Add(items.Select(i => i.ToString()).ToArray());
}
while (reader.Read())
{
var items = new object[reader.FieldCount];
reader.GetValues(items);
gridRows.Add(items.Select(i => i.ToString()).ToArray());
}
return new GridData(tableDataParameters.StartIndex.Value, tableDataParameters.StopIndex.Value, total, gridRows.Count(), gridColumns.ToArray(), gridRows.ToArray());
}
}
}
Should I use something like DbCommandBuilder.QuoteIdentifier, https://msdn.microsoft.com/en-us/library/system.data.common.dbcommandbuilder.quoteidentifier(v=vs.110).aspx
in this case? That doesn't seem like it would help so much here.
Thanks!
Dynamic parameters is an oxymoron! Dapper makes parameters easy, but you can't paramaterize table and column names. This is a restriction of SQL, not dapper. If you really want to do this, you have to use dynamic sql and string methods, and you're on your own as regards SQL injection.
You will be happier and live longer if you don't do this. It's just a bad road. You're not adding much value, and you're potentially introducing a load of problems and limitations.
It looks like you're writing an app to browse a database. Good tools already exist for this!
I've got a problem and no idea how to solve it in SQL server 2012 ,
I have table with +5,000,000 row record ( ID , Link ),
but when I queryselect N row shows error "Request timed out."
Code:
public void _Read()
{
LinQDataContext _DB = new LinQDataContext();
var _img = _DB.HotelImages.Where(o => o.ID_Hotel == Hotel_DA.ID_Hotel);
string _imghtml = "";
foreach (var item in _img)
{
_imghtml = _imghtml + "<a href=''><img src='" + item.Url_Image + "' alt=''></a>";
}
}
A covering index combined with fetching only the required columns (as already suggested) will result in the best performance improvement:
Index:
CREATE INDEX IX_ID_Hotel ON YourTable(ID_Hotel)
INCLUDE (Url_Image)
You should also make sure you context is disposed. And i suggest you to use a string builder instead of concatenanting strings.
string html;
var builder = new StringBuilder();
using (LinQDataContext _DB = new LinQDataContext())
{
var urls = _DB.HotelImages.Where(o => o.ID_Hotel == Hotel_DA.ID_Hotel)
.Select(x => x.Url_Image);
foreach (var url in urls)
{
builder.Append("<a href=''><img src='")
.Append(url)
.Append("'")
.Append("alt=''></a>");
}
}
html = builder.ToString();
Currently you are fetching complete HotelImage objects. Maybe they also contain blobs? You should narrow down the data you fetch from the database by selecting only Url_Image, because that's the only property you use:
var _img = _DB.HotelImages.Where(o => o.ID_Hotel == Hotel_DA.ID_Hotel)
.Select(x => x.Url_Image);
And then
_imghtml = _imghtml + "<a href=''><img src='" + item + "' alt=''></a>";