Sort table with million rows , LinQ connection - c#

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>";

Related

Combine several Lists into one, GroupBy certain column, convert each Grouped result into HTML table

I am working on method that will send an email to each user. I have several lists where I am collecting information about items that should be fixed. After that I am Grouping by user and would like to send an email to each of them to take some actions.
Here is my current code:
public static void SendEmail()
{
string HTMLTableInString = "";
string PM = "";
var combined = List1
.Concat(List2)
.Concat(List3)
.Concat(List4)
.Concat(List5)
.Concat(List6)
.Concat(List7)
.Concat(List8)
.ToList();
if (combined.Any())
{
var GroupedList = combined.GroupBy(x => x.User)
.Select(grp => grp.ToList())
.ToList();
foreach (var item in GroupedList)
{
foreach (var pos in item)
{
HTMLTableInString = Other.CreateHTMLTable(GroupedList,
x => (pos.ProjectNumber, "Project number"),
x => (pos.SubProjectNumber, "Sub-project number"),
x => (pos.SubProjName, "Sub-project name"),
x => (pos.User, "User")
);
Console.WriteLine(pos.ProjectNumber + " " + pos.SubProjName + " " + pos.User);
PM = pos.User;
}
if (!string.IsNullOrWhiteSpace(PM))
{
Console.WriteLine("-------------------------------------------------------------");
Console.WriteLine(HTMLTableInString);
Console.WriteLine("-------------------------------------------------------------");
// Send an email
}
}
}
}
Currently this line produces what is expected
Console.WriteLine(pos.ProjectNumber + " " + pos.SubProjName + " " + pos.User);
However my HTML table does not look right, amount of rows is correct but they all have same values. This is because HTMLTableInString is overwritten numerous times (depends on User records). I understand where is an issue, but I am not able to figure out the right solution to this. Any suggestions?
CreateHTMLTable is a method, used to convert list into a string (=HTML table).
public static string CreateHTMLTable<T>(IEnumerable<T> list, params Func<T, (object, string)>[] fxns) // Used for generating HTML table
{
var sb = new StringBuilder();
sb.Append("<table>\n");
sb.Append("<thead>\n");
sb.Append("<tr>\n");
foreach (var fxn in fxns)
sb.Append("<th>").Append(fxn(default).Item2).AppendLine("</th>");
sb.Append("</tr>\n");
sb.Append("</thead>\n");
foreach (var item in list)
{
sb.Append("<tr>\n");
foreach (var fxn in fxns)
{
sb.Append("<td>");
sb.Append(fxn(item).Item1);
sb.Append("</td>");
}
sb.Append("\n</tr>\n");
}
sb.Append("</table>");
return sb.ToString();
}
I have figured it out, although I don't know is it the best possible solution. I have added one "intermediate" list to be created and writing to it/emptying it in loop.
public static void SendEmail()
{
string HTMLTableInString = "";
string PM = "";
List<ListDefaultStructure> ListForEmailTable = new List<ListDefaultStructure>();
var combined = List1
.Concat(List2)
.Concat(List3)
.Concat(List4)
.Concat(List5)
.Concat(List6)
.Concat(List7)
.Concat(List8)
.ToList();
if (combined.Any())
{
var GroupedList = combined.GroupBy(x => x.User)
.Select(grp => grp.ToList())
.ToList();
foreach (var item in GroupedList)
{
foreach (var pos in item)
{
ListForEmailTable.Add(new ListDefaultStructure
{
ProjectNumber = pos?.ProjectNumber,
SubProjectNumber = pos?.SubProjectNumber,
SubProjectName = pos?.SubProjectName,
User = pos?.User
});
PM = pos.User;
}
if (!string.IsNullOrWhiteSpace(PM))
{
HTMLTableInString = Other.CreateHTMLTable(GroupedList,
x => (pos.ProjectNumber, "Project number"),
x => (pos.SubProjectNumber, "Sub-project number"),
x => (pos.SubProjName, "Sub-project name"),
x => (pos.User, "User")
);
Console.WriteLine("-------------------------------------------------------------");
Console.WriteLine(HTMLTableInString);
Console.WriteLine("-------------------------------------------------------------");
// Send an email
}
}
}
}

how to get multiple result sets using linq

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,
//...
};
}
}

How do I sanitize a dynamic table name using Dapper?

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!

Can't Select Records from a database table with grouping using Linq

I'm trying to Select an SQL table and grouping columns using Linq to SQL, Entities, or Object (I don't really know what.) I'm a bit new to Linq and could use some help. The code structure is straight-forward in my view. When I don't add in the GroupBy method, it works fine. JT_Temp is an entity model by the way. When I run my code below, it goes to the exception:
The entity or complex type 'JT_Temp' cannot be constructed in LINQ to Entities query.
I have tried this and various stackoverflow solutions but they don't seem to solve and apply to my case.
Here is my current code:
//Goal:
//SELECT EnvelopeCode, Branch_COA, AQ_COA, AQ_Branch, SUM(Amount), AQ_PostStatus FROM JT_Temp
//GROUP BY EnvelopeCode, Branch_COA, AQ_COA, AQ_Branch, AQ_PostStatus
//var csvFilteredRecord = Context.JT_Temp.SqlQuery("SELECT * FROM JT_Temp").ToList<JT_Temp>();
// GROUP BY -- No go; Manual SELECT -- No go;
try
{
var csvFilteredRecord = (
from c in Context.JT_Temp
group c by new
{
c.EnvelopeCode,
c.Branch_COA,
c.AQ_COA,
c.AQ_Branch,
c.AQ_PostStatus
} into i
select new JT_Temp
{
EnvelopeCode = i.Key.EnvelopeCode,
Branch_COA = i.Key.Branch_COA,
AQ_COA = i.Key.AQ_COA,
AQ_Branch = i.Key.AQ_Branch,
//TO-DO SUM(Amount),
AQ_PostStatus = i.Key.AQ_PostStatus
}).ToList();
foreach (var Record in csvFilteredRecord)
{
Console.WriteLine(
Record.EnvelopeCode
+ Record.Branch_COA
+ Record.AQ_COA
//+ Record.Amount
+ Record.AQ_PostStatus
);
}
}
catch (Exception e)
{
Console.WriteLine("---------- " + e.Message);
Console.ReadLine();
}
You can't project into JT_Temp. Just use an anonymous object. Also, no reason to make it a list, so I removed the .ToList()
Query syntax:
var csvFilteredRecord = (
from c in Context.JT_Temp
group c by new
{
c.EnvelopeCode,
c.Branch_COA,
c.AQ_COA,
c.AQ_Branch,
c.AQ_PostStatus
} into i
select new
{
EnvelopeCode = i.Key.EnvelopeCode,
Branch_COA = i.Key.Branch_COA,
AQ_COA = i.Key.AQ_COA,
AQ_Branch = i.Key.AQ_Branch,
//TO-DO SUM(Amount),
AQ_PostStatus = i.Key.AQ_PostStatus
});
Method syntax:
var csvFilteredRecord = Context.JT_Temp.GroupBy(k=> new
{
c.EnvelopeCode,
c.Branch_COA,
c.AQ_COA,
c.AQ_Branch,
c.AQ_PostStatus
},
v=>v.Amount,
(k,v)=>new {
k.EnvelopeCode,
k.Branch_COA,
k.AQ_COA,
k.AQ_Branch,
k.AQ_PostStatus
Amount=v.Sum()
});
foreach (var Record in csvFilteredRecord)
{
Console.WriteLine(
Record.EnvelopeCode
+ Record.Branch_COA
+ Record.AQ_COA
+ Record.Amount
+ Record.AQ_PostStatus
);
}

What is an efficient way of iterating through a list according to its distinct values?

string sStoreStockFeed = "";
string sSeparator = "";
var distinctStoreIDList = skuStoreStockLevels.Select(x => x.Item1).Distinct();
foreach (var storeID in distinctStoreIDList)
{
foreach (var item in skuStoreStockLevels)
{
if (item.Item1 == storeID)
{
// add this one to a job for this store
sStoreStockFeed += sSeparator + item.Item1.ToString() + "," + item.Item2.ToString() + "," + item.Item3.ToString();
sSeparator = "|";
}
}
// some code to process the string before moving on
sStoreStockFeed = "";
sSeparator = "";
}
In the above code snippet skuStoreStockLevels just happens to be a List of type Tuple and Item1 is the StoreID. having got a distinct list it then iterates through the (non-distinct) list to get every applicable item. The inefficiency is that the (big) inner list is iterated throuh repeatedly for each distinct item (StoreID).
UPDATE: pure LINQ solution. This will give you list of strings, created for each group of items.
var query = skuStoreStockLevel.GroupBy(x => x.Item1)
.Select(g => g.Aggregate(new StringBuilder(),
(sb, x) => sb.AppendFormat("{0}{1},{2},{3}", sSeparator, x.Item1, x.Item2, x.Item3),
(sb) => sb.ToString()));
foreach(var feed in query)
// some code to process the string before moving on
Also there are other options - ordering of sequence. Equal items will follow one after another.
int storeID = -1;
StringBuilder builder = new StringBuilder();
foreach (var item in skuStoreStockLevel.OrderBy(x => x.Item1))
{
builder.AppendFormat("{0}{1},{2},{3}", sSeparator, item.Item1, item.Item2, item.Item3);
if (item.Item1 != storeID)
{
// some code to process the string before moving on
storeID = item.Item1;
}
}
Or you can use grouping
StringBuilder builder = new StringBuilder();
foreach (var storeGroup in skuStoreStockLevel.GroupBy(x => x.Item1))
{
foreach (var item in storeGroup)
builder.AppendFormat("{0}{1},{2},{3}", sSeparator, item.Item1, item.Item2, item.Item3);
// some code to process the string before moving on
}
And, of course, it's better to use StringBuilder for creating strings.
Use Linq GroupBy which will build you a list of grouped items:
string sStoreStockFeed = "";
string sSeparator = "";
var itemsByStore = skuStoreStockLevels.GroupBy(x => x.Item1);
foreach (var storeItems in itemsByStore )
{
// storeItems.Key is the storeId, that is x.Item1
foreach(var item in storeItems)
{
sStoreStockFeed += sSeparator + item.Item1.ToString() + "," + item.Item2.ToString() + "," + item.Item3.ToString();
sSeparator = "|";
}
// some code to process the string before moving on
sStoreStockFeed = "";
sSeparator = "";
}

Categories

Resources