Introduction
Here, in this post, we are going to a see some LINQ queries, which covers both basics and advanced. LINQ queries were introduced few years ago to offer a consistent way for working with data across many datasources and formats. In LINQ queries, you will always work with objects, which makes it simple to write. I hope you would have already written lots of LINQ queries already, if you haven't, I strongly recommend you read this blog where you can find the answer for, why do we need to use LINQ? Here I am going to create an MVC demo application. I hope you will like this. Now let's begin.
Background
Whenever I get a chance to write some server side code in C#, I always write it using LINQ. And few weeks ago, I was assigned to a training programme where my job was to teach LINQ, hence this post covers the queries I wrote for the training programme. Hope you will find it useful.
Using the Code
A LINQ query can be written in two ways:
- Query Syntax
- Method Chain or Using dot(.) operator
There are so many articles available on the Internet on the topic LINQ, but most of them don't cover the differences of writing the queries in two possible ways, the motive of this article is to write the queries in both ways, so that you can understand the differences.
As I mentioned, we are going to create an MVC application, we need to create it first and then configure the entity. Let's go and do that.
Create a Database
To get started with, we need to configure our database first. To do so, either you can download the Wild World Importers from here or you can run the script file included in the download link above.
Once after you created the database, you can create your MVC application and Entity Data Model in it.
Configuring MVC Application with Entity Data Model
By this time, I hope you would have configured your MVC application with Entity Data Model. Now it is time to create a controller and Entity
object.
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Windows.Forms;
using LINQ_B_to_A.Models;
namespace LINQ_B_to_A.Controllers
{
public class HomeController : Controller
{
public MyDataModel DtContext { get; set; } = new MyDataModel();
}
}
Now we can write some LINQ queries as everything is set to get started.
Setting Up the Index View with Possible Actions
This is just to call the actions we are going to write. Let's see the Index page now.
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
<input type="button" class="btn-info"
onclick="location.href='@Url.Action
("LoadAll","Home")'" value="Load All - Query Expression"/>
<input type="button" class="btn-info" onclick="location.href='@Url.Action
("JoinWithWhere","Home")'" value="Join With Where" />
<input type="button" class="btn-info" onclick="location.href='@Url.Action
("LeftJoin","Home")'" value="Left Join" />
<input type="button" class="btn-info" onclick="location.href='@Url.Action
("DistinctSample","Home")'" value="Distinct Sample" />
<input type="button" class="btn-info" onclick="location.href='@Url.Action
("EqualsSamples","Home")'" value="Equals Sample" />
<input type="button" class="btn-info" onclick="location.href='@Url.Action
("NotEqualsSamples","Home")'" value="Not Equals" />
<input type="button" class="btn-info" onclick="location.href='@Url.Action
("PagingQueries","Home")'" value="Paging Queries" />
<input type="button" class="btn-info" onclick="location.href='@Url.Action
("MathQueries","Home")'" value="Math Queries" />
<input type="button" class="btn-info" onclick="location.href='@Url.Action
("StringQueries","Home")'" value="String Queries" />
<input type="button" class="btn-info" onclick="location.href='@Url.Action
("SelectMany","Home")'" value="Select Many" />
Normal Select Query With StartWith and Take
Let's have a look at the following query:
public ActionResult LoadAll()
{
var loadAllData = (from d in DtContext.Cities
where d.CityName.StartsWith("C") select d).Take(10);
var loadAllData1 = DtContext.Cities.Where
(d => d.CityName.StartsWith("C")).Take(10);
return PartialView(loadAllData);
}
As you can see, we are just fetching the data from Cities
in both ways. And we are using StartWith
as a filter, which actually looks for the city names which starts with the letter C
, and finally we are taking 10 elements from it. The first query is the "Query syntax" and the second one is "Method chain". Personally, I like the second way. How about you?
@model IQueryable<LINQ_B_to_A.Models.City>
<style>
dd, dt, pre {
border: 1px solid #ccc;
padding: 5px;
margin: 5px;
width: auto;
width: 20%;
}
dd {
background-color: #a9a9a9
}
pre {
width: auto;
}
</style>
<dd>City Names</dd>
@foreach (var ctyObj in Model)
{
<dt>@ctyObj.CityName</dt>
}
<pre>
public ActionResult LoadAll()
{
var loadAllData = (from d in DtContext.Cities where d.CityName.StartsWith("C") select d).Take(10);
var loadAllData1 = DtContext.Cities.Where(d => d.CityName.StartsWith("C")).Take(10);
return PartialView(loadAllData);
}
</pre>
We are ready to see our first query in action now.
JOIN Query
To write a join
, the query would be as follows:
public ActionResult JoinWithWhere()
{
var loadAllData = (from oOrders in DtContext.Orders
join oOrderLines in DtContext.OrderLines
on oOrders.OrderID equals oOrderLines.OrderID
orderby oOrders.OrderID
select new OrderAndOrderLines()
{
OrderId = oOrders.OrderID,
Description = oOrderLines.Description,
Quantity = oOrderLines.Quantity
}).Take(10);
var asMethodChain = DtContext.Orders.Join
(DtContext.OrderLines, oOrders => oOrders.OrderID,
oOrderLines => oOrderLines.OrderID,
(oOrders, oOrderLines) => new { oOrders, oOrderLines })
.OrderBy(@o => @o.oOrders.OrderID)
.Select(@s => new OrderAndOrderLines()
{
OrderId = @s.oOrders.OrderID,
Description = @s.oOrderLines.Description,
Quantity = @s.oOrderLines.Quantity
}).Take(10);
return PartialView(loadAllData);
}
In the above query, we are just joining the tables Order
and OrderLines
with OrderID
and to select, we are using an another custom model OrderAndOrderLines
.
namespace LINQ_B_to_A.Models
{
public partial class OrderAndOrderLines
{
public int OrderId { get; set; }
public string Description { get; set; }
public int? Quantity { get; set; }
}
}
The view can be written as follows:
@model IEnumerable<LINQ_B_to_A.Models.OrderAndOrderLines>
<style>
td, th, thead,pre {
border: 1px solid #ccc;
padding: 5px;
margin: 5px;
width: auto;
width: 20%;
}
caption {
background-color: #a9a9a9
}
pre {
width: auto;
}
</style>
<table>
<caption>Order Details</caption>
<tr>
<th>Order ID</th>
<th>Description</th>
<th>Quantity</th>
</tr>
@foreach (var @item in Model)
{
<tr>
<td>@item.OrderId</td>
<td>@item.Description</td>
<td>@item.Quantity</td>
</tr>
}
</table>
<pre>
public ActionResult JoinWithWhere()
{
var loadAllData = (from oOrders in DtContext.Orders
join oOrderLines in DtContext.OrderLines
on oOrders.OrderID equals oOrderLines.OrderID
orderby oOrders.OrderID
select new OrderAndOrderLines()
{
OrderId = oOrders.OrderID,
Description = oOrderLines.Description,
Quantity = oOrderLines.Quantity
}).Take(10);
var asMethodChain = DtContext.Orders.Join
(DtContext.OrderLines, oOrders => oOrders.OrderID,
oOrderLines => oOrderLines.OrderID,
(oOrders, oOrderLines) => new { oOrders, oOrderLines })
.OrderBy(o => o.oOrders.OrderID)
.Select(s => new OrderAndOrderLines()
{
OrderId = s.oOrders.OrderID,
Description = s.oOrderLines.Description,
Quantity = s.oOrderLines.Quantity
}).Take(10);
return PartialView(loadAllData);
}
</pre>
Once you run the action, you can see the result as follows:
Left JOIN Query
To write a Left join
, the query would be as shown below:
public ActionResult LeftJoin()
{
var loadAllData = (from oOrder in DtContext.Orders
join oOrderLine in DtContext.OrderLines
on oOrder.OrderID equals oOrderLine.OrderID
into lftOrder
from afterJoined in lftOrder.DefaultIfEmpty()
orderby oOrder.OrderID descending
select new OrderAndOrderLines()
{
OrderId = oOrder.OrderID,
Description = afterJoined.Description
}).Take(10).ToList();
var lftJoinMethodChain = (DtContext.Orders.GroupJoin(DtContext.OrderLines,
oOrder => oOrder.OrderID, oOrderLine => oOrderLine.OrderID,
(oOrder, lftJoin) => new { oOrder, lftJoin })
.SelectMany(@sm => @sm.lftJoin.DefaultIfEmpty(),
(@sm, afterJoin) => new { @sm, afterJoin })
.OrderByDescending(@o => @o.sm.oOrder.OrderID)
.Select(@s => new OrderAndOrderLines()
{
OrderId = @s.sm.oOrder.OrderID,
Description = @s.afterJoin.Description
})).Take(10).ToList();
return PartialView(loadAllData);
}
In the above query, we are just joining the tables Order
and OrderLines
with OrderID
and to select
, we are using another custom model OrderAndOrderLines
as our previous query. The differences you could find here are, using an 'into
' and 'DefaultIfEmpty
' statements. The DefaultIfEmpty
is making sure that it returns empty if there are no appropriate rows found in the second table.
The view can be written as follows:
@model IEnumerable<LINQ_B_to_A.Models.OrderAndOrderLines>
<style>
td, th, thead, pre {
border: 1px solid #ccc;
padding: 5px;
margin: 5px;
width: auto;
width: 20%;
}
caption {
background-color: #a9a9a9
}
pre {
width: auto;
}
</style>
<table>
<caption>Order Details</caption>
<tr>
<th>Order ID</th>
<th>Description</th>
<th>Quantity</th>
</tr>
@foreach (var @item in Model)
{
<tr>
<td>@item.OrderId</td>
<td>@item.Description</td>
<td>@item.Quantity</td>
</tr>
}
</table>
<pre>
public ActionResult LeftJoin()
{
var loadAllData = (from oOrder in DtContext.Orders
join oOrderLine in DtContext.OrderLines
on oOrder.OrderID equals oOrderLine.OrderID
into lftOrder
from afterJoined in lftOrder.DefaultIfEmpty()
orderby oOrder.OrderID descending
select new OrderAndOrderLines()
{
OrderId = oOrder.OrderID,
Description = afterJoined.Description
}).Take(10).ToList();
var lftJoinMethodChain = (DtContext.Orders.GroupJoin(DtContext.OrderLines,
oOrder => oOrder.OrderID, oOrderLine => oOrderLine.OrderID,
(oOrder, lftJoin) => new { oOrder, lftJoin })
.SelectMany(sm => sm.lftJoin.DefaultIfEmpty(),
(sm, afterJoin) => new { sm, afterJoin })
.OrderByDescending(o => o.sm.oOrder.OrderID)
.Select(s => new OrderAndOrderLines()
{
OrderId = s.sm.oOrder.OrderID,
Description = s.afterJoin.Description
})).Take(10).ToList();
return PartialView(loadAllData);
}
Once you run the action, you can see the result as follows:
As you can see in the image, the order id 200000
doesn't have any appropriate rows in the second table, so it shows as empty.
Distinct Query
The below query shows how we can write a simple distinct query.
public ActionResult DistinctSample()
{
var distictSample = (from oOrder in DtContext.OrderLines
select oOrder.Description).Distinct().Take(10).ToList();
var distictAsMethodChain = (DtContext.OrderLines.Select
(oOrder => oOrder.Description)).Distinct().Take(10).ToList();
return PartialView(distictSample);
}
In the above query, we use Distinct
to make sure that only distinct items are selected from the result. The view can be written as follows:
@model List<string>
<style>
dd, dt, pre {
border: 1px solid #ccc;
padding: 5px;
margin: 5px;
width: auto;
width: 20%;
}
dd {
background-color: #a9a9a9
}
pre {
width: auto;
}
</style>
<dd>Order Descriptions</dd>
@foreach (var orderLinesyObj in Model)
{
<dt>@orderLinesyObj</dt>
}
<pre>
public ActionResult DistinctSample()
{
var distictSample = (from oOrder in DtContext.OrderLines
select oOrder.Description).Distinct().Take(10).ToList();
var distictAsMethodChain =
(DtContext.OrderLines.Select
(oOrder => oOrder.Description)).Distinct().Take(10).ToList();
return PartialView(distictSample);
}
</pre>
Once you run the action, you can see the result as follows:
Equals and Not Equals Queries
We can write the equals and not equals query as shown below:
public ActionResult EqualsSamples()
{
var objEquals = (from objCity in DtContext.Cities
where objCity.CityName.Equals("Troy")
select objCity).Take(2);
var objEquals1 = DtContext.Cities.Where
(d => d.CityName.Equals("Troy")).Take(2);
return PartialView("OperatorSamples", objEquals);
}
public ActionResult NotEqualsSamples()
{
var objNotEquals = (from objCity in DtContext.Cities
where objCity.CityName != "Troy"
select objCity).Take(5);
var objNotEquals1 = (from objCity in DtContext.Cities
where !objCity.CityName.Equals("Troy")
select objCity).Take(5);
var objNotEquals2 = DtContext.Cities.Where
(d => d.CityName != "Troy").Take(2);
var objNotEquals3 = DtContext.Cities.Where
(d => !d.CityName.Equals("Troy")).Take(2);
return PartialView("OperatorSamples", objNotEquals);
}
The view can be written as follows:
@model IQueryable<LINQ_B_to_A.Models.City>
<style>
td, th, thead, pre {
border: 1px solid #ccc;
padding: 5px;
margin: 5px;
width: auto;
width: 20%;
}
caption {
background-color: #a9a9a9
}
pre {
width: auto;
}
</style>
<table>
<caption>City Details</caption>
<tr>
<th>City ID</th>
<th>City Name</th>
<th>City Location</th>
</tr>
@foreach (var @item in Model)
{
<tr>
<td>@item.CityID</td>
<td>@item.CityName</td>
<td>@item.Location</td>
</tr>
}
</table>
<caption>Equals Oerator</caption>
<pre>
public ActionResult EqualsSamples()
{
var objEquals = (from objCity in DtContext.Cities
where objCity.CityName.Equals("Troy")
select objCity).Take(2);
var objEquals1 = DtContext.Cities.Where
(d => d.CityName.Equals("Troy")).Take(2);
return PartialView("OperatorSamples", objEquals);
}
public ActionResult NotEqualsSamples()
{
var objNotEquals = (from objCity in DtContext.Cities
where objCity.CityName != "Troy"
select objCity).Take(5);
var objNotEquals1 = (from objCity in DtContext.Cities
where !objCity.CityName.Equals("Troy")
select objCity).Take(5);
var objNotEquals2 = DtContext.Cities.Where
(d => d.CityName != "Troy").Take(2);
var objNotEquals3 = DtContext.Cities.Where
(d => !d.CityName.Equals("Troy")).Take(2);
return PartialView("OperatorSamples", objNotEquals);
}
public ActionResult PagingQueries()
{
var objNotEquals = (from objCity in DtContext.Cities
where objCity.CityName != "Troy"
orderby objCity.CityName ascending
select objCity).Skip(5).Take(5);
var objNotEquals2 = DtContext.Cities.Where
(d => d.CityName != "Troy").Skip(5).Take(5);
return PartialView("OperatorSamples", objNotEquals);
}
Once you run the action, you can see the result as follows:
LINQ Paging Queries
Paging queries are always important as we work in some grid controls, with LINQ, those are very easy. Let's see one of those queries.
public ActionResult PagingQueries()
{
var objNotEquals = (from objCity in DtContext.Cities
where objCity.CityName != "Troy"
orderby objCity.CityName ascending
select objCity).Skip(5).Take(5);
var objNotEquals2 = DtContext.Cities.Where
(d => d.CityName != "Troy").Skip(5).Take(5);
return PartialView("OperatorSamples", objNotEquals);
}
Once you run the action, you can see the result as follows:
LINQ Math Queries
Here, we are going to write the possible Math
functions in our LINQ query.
public ActionResult MathQueries()
{
var objMath = (from objInv in DtContext.InvoiceLines
where objInv.ExtendedPrice >
10 && objInv.Quantity < 15
orderby objInv.InvoiceLineID descending
select new MathClass()
{
Actual = objInv.ExtendedPrice,
Round = Math.Round(objInv.ExtendedPrice),
Floor = Math.Floor(objInv.ExtendedPrice),
Ceiling = Math.Ceiling(objInv.ExtendedPrice),
Abs = Math.Abs(objInv.ExtendedPrice)
}).Take(10);
var objMath2 = DtContext.InvoiceLines
.Where(objInv => objInv.ExtendedPrice >
10 && objInv.Quantity < 15)
.OrderByDescending(o => o.InvoiceLineID)
.Select(objInv => new MathClass()
{
Actual = objInv.ExtendedPrice,
Round = Math.Round(objInv.ExtendedPrice),
Floor = Math.Floor(objInv.ExtendedPrice),
Ceiling = Math.Ceiling(objInv.ExtendedPrice),
Abs = Math.Abs(objInv.ExtendedPrice)
}).Take(10);
return PartialView("MathQueries", objMath);
}
As you can see, we have written most of the possible Math
functions in our query and selecting with a custom model MathClass
.
namespace LINQ_B_to_A.Models
{
public partial class MathClass
{
public decimal Actual { get; set; }
public decimal Round { get; set; }
public decimal Floor { get; set; }
public decimal Ceiling { get; set; }
public decimal Abs { get; set; }
}
}
Let's see the view now.
@model IQueryable<LINQ_B_to_A.Models.MathClass>
<style>
td, th, thead, pre {
border: 1px solid #ccc;
padding: 5px;
margin: 5px;
width: auto;
width: 20%;
}
caption {
background-color: #a9a9a9
}
pre {
width: auto;
}
</style>
<table>
<caption>Math Operators</caption>
<tr>
</tr>
@foreach (var @item in Model)
{
<tr>
<td>Actual: @item.Actual</td>
<td>Round: @item.Round</td>
<td>Floor: @item.Floor</td>
<td>Ceiling: @item.Ceiling</td>
<td>Abs: @item.Abs</td>
</tr>
}
</table>
<pre>
public ActionResult MathQueries()
{
var objMath = (from objInv in DtContext.InvoiceLines
where objInv.ExtendedPrice > 10
&& objInv.Quantity < 15
orderby objInv.InvoiceLineID descending
select new MathClass()
{
Actual = objInv.ExtendedPrice,
Round = Math.Round(objInv.ExtendedPrice),
Floor = Math.Floor(objInv.ExtendedPrice),
Ceiling = Math.Ceiling(objInv.ExtendedPrice),
Abs = Math.Abs(objInv.ExtendedPrice)
}).Take(10);
var objMath2 = DtContext.InvoiceLines
.Where(objInv => objInv.ExtendedPrice > 10
&& objInv.Quantity < 15)
.OrderByDescending(o => o.InvoiceLineID)
.Select(objInv => new MathClass()
{
Actual = objInv.ExtendedPrice,
Round = Math.Round(objInv.ExtendedPrice),
Floor = Math.Floor(objInv.ExtendedPrice),
Ceiling = Math.Ceiling(objInv.ExtendedPrice),
Abs = Math.Abs(objInv.ExtendedPrice)
}).Take(10);
return PartialView("MathQueries", objMath);
}
</pre>
See the result now.
LINQ String Queries
As we saw the Math
queries, here we are going to write the possible String
functions in our LINQ query.
public ActionResult StringQueries()
{
var objString = (from objInv in DtContext.InvoiceLines
where objInv.ExtendedPrice > 10
&& objInv.Quantity < 15
orderby objInv.InvoiceLineID descending
select new StringClass()
{
Actual = objInv.Description,
Insert = objInv.Description.Insert(2, "SibeeshPassion"),
Remove = objInv.Description.Remove(1, 1),
Substring = objInv.Description.Substring(2, 3),
ToLower = objInv.Description.ToLower(),
ToUpper = objInv.Description.ToUpper(),
TrimEnd = objInv.Description.TrimEnd(),
TrimStart = objInv.Description.TrimStart()
}).Take(2);
var objString2 = DtContext.InvoiceLines
.Where(objInv => objInv.ExtendedPrice > 10
&& objInv.Quantity < 15)
.OrderByDescending(o => o.InvoiceLineID)
.Select(objInv => new StringClass()
{
Actual = objInv.Description,
Insert = objInv.Description.Insert(2, "SibeeshPassion"),
Remove = objInv.Description.Remove(1, 1),
Substring = objInv.Description.Substring(2, 3),
ToLower = objInv.Description.ToLower(),
ToUpper = objInv.Description.ToUpper(),
TrimEnd = objInv.Description.TrimEnd(),
TrimStart = objInv.Description.TrimStart()
}).Take(2);
return PartialView("StringQueries", objString);
}
As you can see, here we are using a custom model StringClass
.
namespace LINQ_B_to_A.Models
{
public partial class StringClass
{
public string Actual { get; set; }
public string Insert { get; set; }
public string Remove { get; set; }
public string Substring { get; set; }
public string ToUpper { get; set; }
public string ToLower { get; set; }
public string TrimStart { get; set; }
public string TrimEnd { get; set; }
}
}
Let's see the view now.
@model IQueryable<LINQ_B_to_A.Models.StringClass>
<style>
td, th, thead, pre {
border: 1px solid #ccc;
padding: 5px;
margin: 5px;
width: auto;
width: 20%;
}
caption {
background-color: #a9a9a9
}
pre {
width: auto;
}
</style>
<table>
<caption>String Operators</caption>
<tr>
</tr>
@foreach (var @item in Model)
{
<tr>
<td>Actual: @item.Actual</td>
<td>Insert: @item.Insert</td>
<td>Remove: @item.Remove</td>
<td>Substring: @item.Substring</td>
<td>ToLower: @item.ToLower</td>
<td>ToUpper: @item.ToUpper</td>
<td>TrimEnd: @item.TrimEnd</td>
<td>TrimStart: @item.TrimStart</td>
</tr>
}
</table>
<pre>
public ActionResult StringQueries()
{
var objString = (from objInv in DtContext.InvoiceLines
where objInv.ExtendedPrice > 10 && objInv.Quantity < 15
orderby objInv.InvoiceLineID descending
select new StringClass()
{
Actual = objInv.Description,
Insert = objInv.Description.Insert(2,"SibeeshPassion"),
Remove = objInv.Description.Remove(1,1),
Substring = objInv.Description.Substring(2,3),
ToLower = objInv.Description.ToLower(),
ToUpper = objInv.Description.ToUpper(),
TrimEnd = objInv.Description.TrimEnd(),
TrimStart = objInv.Description.TrimStart()
}).Take(2);
var objString2 = DtContext.InvoiceLines
.Where(objInv => objInv.ExtendedPrice > 10 && objInv.Quantity < 15)
.OrderByDescending(o => o.InvoiceLineID)
.Select(objInv => new StringClass()
{
Actual = objInv.Description,
Insert = objInv.Description.Insert(2, "SibeeshPassion"),
Remove = objInv.Description.Remove(1, 1),
Substring = objInv.Description.Substring(2, 3),
ToLower = objInv.Description.ToLower(),
ToUpper = objInv.Description.ToUpper(),
TrimEnd = objInv.Description.TrimEnd(),
TrimStart = objInv.Description.TrimStart()
}).Take(2);
return PartialView("StringQueries", objString);
}
</pre>
Now see the result.
SelectMany Query
A SelectMany
query flattens the result to a single dimensional collection, so to loop through the result, we just need only one loop.
If you use the normal Select
, you will be getting a list of lists, thus you will have to use two loops to get the datas.
public ActionResult SelectMany()
{
var selectMany = DtContext.Orders.SelectMany(order => order.Invoices).Take(10);
var select = DtContext.Orders.Select(order => order.Invoices).Take(10);
return PartialView("SelectMany", selectMany);
}
Let's see the view now.
@model IQueryable<LINQ_B_to_A.Models.Invoice>
<style>
td, th, thead, pre {
border: 1px solid #ccc;
padding: 5px;
margin: 5px;
width: auto;
width: 20%;
}
caption {
background-color: #a9a9a9
}
pre {
width: auto;
}
</style>
<table>
<caption>Invoice Details</caption>
<tr>
<th>Order ID</th>
<th>Invoice ID</th>
<th>Invoice Date</th>
</tr>
@foreach (var @item in Model)
{
<tr>
<td>@item.OrderID</td>
<td>@item.InvoiceID</td>
<td>@item.InvoiceDate</td>
</tr>
}
</table>
<caption>Select Many</caption>
<pre>
public ActionResult SelectMany()
{
var selectMany =
DtContext.Orders.SelectMany(order => order.Invoices).Take(10);
var select = DtContext.Orders.Select(order => order.Invoices).Take(10);
return PartialView("SelectMany", selectMany);
}
</pre>
Now see the result.
That's all for today, I will come with another set of LINQ queries in the next part. Till then, bye.
Conclusion
Did I miss anything that you may think is needed? Could you find this post useful? I hope you liked this article. Please share your valuable suggestions and feedback.
Your Turn. What Do You Think?
Please share your feedback, but do try to stay on topic. If you have a question unrelated to this post, you’re better off posting it on C# Corner, Code Project, Stack Overflow, ASP.NET Forum instead of commenting here. Tweet or email me a link to your question there and I’ll definitely try to help if I can.