LINQ - Dynamic Order By Expression - c#

I have a little issue. Currently, I am trying to write dynamic order by query using linq.
Sql query which i am trying to implement in linq
select * from tbl
order by case when Location='Loc9787f85b-c953-4238-8bad-f712b6444443' then 1
when Location='Loc9787f85b-c953-4238-8bad-f712b6444442' then 2 end
Location value is is retrieved and saved in list. It can one or more values.
This solution seems to work for static location value. Since I retrieve location value dynamically I didnt know how to implement for dynamic location value.
var temp = tbl.OrderBy(t => t.Location== 'Loc9787f85b-c953-4238-8bad-f712b6444443' ?
1 : (t.Location== 'Loc9787f85b-c953-4238-8bad-f712b6444442' ? 2 : 3))
I will be retrieving location using this piece of code:
List<String> Location = CustomerService.GetAllLocation();
I am trying to order by using this list values. Is it possible to implement dynamic order by using list containing column value?

Use
List<String> locations = CustomerService.GetAllLocation();
var ordered = tbl.OrderBy(t => locations.Contains(t.Location) ? 0 : 1);
or, if the index should represent the priority:
var ordered = tbl
.Where(t => locations.Contains(t.Location))
.ToList() //because List.IndexOf is not supported in LINQ-TO-SQL
.OrderBy(t => locations.IndexOf(t.Location));

Rather push the logic out to a method like so:
var temp = tbl.OrderBy(t => GetOrder(t));
public int GetOrder(LocationObject t)
{
int returnValue = 0;
if (t.Location== "Loc9787f85b-c953-4238-8bad-f712b6444443")
{
returnValue = 1;
}
else if (t.Location == "Loc9787f85b-c953-4238-8bad-f712b6444442")
{
returnValue = 2;
}
else
{
returnValue = 3;
}
return returnValue;
}

Related

how to get a linq query result to int

below query return a single value eg 50. I want it be assign to int so I can do some calculation with that value. how can I do this
var LTLimit = from a in dbavailability.tACLicenseTypes
where a.License_Type == "Long Term"
select a.Limit;
string AST = "";
I'm not completely clear on what you're asking, but presumably the type of data returned by your Linq query is not int, and you want to convert it to int? If so, simply convert it to an int:
var LTLimit = (from a in dbavailability.tACLicenseTypes
where a.License_Type == "Long Term"
select a.Limit).ToList();
int LTLimitInt = 0
if (!int.TryParse(LTLimit.First(), out LTLimitInt))
{
Console.WritLine("LTLimit is not a number!")
}
Since you've updated your question, here's a solution to convert the returned number to an int, multiply it by 2, then convert the result to a string:
var LTLimit = (from a in dbavailability.tACLicenseTypes
where a.License_Type == "Long Term"
select a.Limit).ToList();
int LTLimitInt = 0;
string multipliedResultStr = string.Empty;
if (!int.TryParse(LTLimit.First(), out LTLimitInt))
{
Console.WritLine("LTLimit is not a number!")
}
else
{
multipliedResult = (LTLimitInt * 2).ToString();
Console.WriteLine(string.Format("Result is {0}, multipliedResult ));
}
Edit;
Corrected code to take first item from list.
The following code is one way to retrieve the first integer returned in your query:
var LTLimit = (from a in dbavailability.tACLicenseTypes
where a.License_Type == "Long Term"
select a.Limit).ToList();
int limit = LTLimit[0];
What is the return type of the query?
If the returned type is of another primative data type like a string, then you can try to convert it to an integer using :
var i = Convert.ToInt32(LTLimit);
Or using :
int.TryParse(LTLimit, out var i);
However, it would be better if the data was stored in the correct type in the first place.
You can use Sum() after where() for value of object property
like this bonus.Where(b => b.Id == x.key && b.RequiredScore.HasValue).Sum(b => b.RequiredScore.Value);

How do i add OR in between where using linq to entity query?

I wanted to search for multiple values from the same column using OR in linq. In SQL, it is something like this:
var query = "Select * from table where id = 1";
query += "OR where id = 2";
The id is from an array of ids so i pass the id to a variable and loop it. The number of id in the array is not fixed and depends on how many ids chosen by the user by checking on the checkbox in the table. Because if i do as below, it will return null because it somewhat inteprets my where query as AND. How do i change it so that it will get rows from all ids(using OR)?
Request request = db.Requests;
var selectedIdList = new List<string>(arrId);
if (arrId.Length > 0)
{
for (var item = 0; item <= selectedIdList.Count() - 1; item++)
{
var detailId = Convert.ToInt32(selectedIdList[item]);
request = request.Where(y => y.Id == detailId);
}
}
Simply use || for OR and && for AND inside Where
But I prefer :
request.Where(y => selectedIdList.Contains(y.Id));
If selectedIdList is an array of Strings :
request.Where(y => selectedIdList.Contains(y.Id.ToString());
You can use LinqKit. Linqkit provides things to create predicate dynamically
Just install linqKit using packageManager, And use predicateBuilder to build predicate
var predicate = PredicateBuilder.True<Patient>();
predicate=predicate.And(...)
OR
predicate=predicate.Or(...)
And then use predicate in ur where clause
This way you can create Or (dynamically) as you would in dynamic SQL
You can get more details from here
https://www.c-sharpcorner.com/UploadFile/c42694/dynamic-query-in-linq-using-predicate-builder/

sort string as number in linq to entity

I have som strings like "1","2","3","10" and etc and when use orderby sorted list is "1","10","2","3". I want to sort them as number like 1,2,3,...,10. I use below code to sort the list.
var model = (from c in General.db.GlbTbComboBases
where c.ClassCode.Equals(classCode)
select new ReturnData { id = c.BaseCode, name = c.FAName }).OrderBy(c => c.id,
new SemiNumericComparer());
if (model.Any())
{
CacheManager.cache.GetOrAdd<List<ReturnData>>(key, () =>
model.ToList<ReturnData>());
return model.ToList<ReturnData>();
}
public class SemiNumericComparer : IComparer<string>
{
public int Compare(string s1, string s2)
{
if (IsNumeric(s1) && IsNumeric(s2))
{
if (Convert.ToInt32(s1) > Convert.ToInt32(s2)) return 1;
if (Convert.ToInt32(s1) < Convert.ToInt32(s2)) return -1;
if (Convert.ToInt32(s1) == Convert.ToInt32(s2)) return 0;
}
if (IsNumeric(s1) && !IsNumeric(s2))
return -1;
if (!IsNumeric(s1) && IsNumeric(s2))
return 1;
return string.Compare(s1, s2, true);
}
public static bool IsNumeric(object value)
{
try
{
int i = Convert.ToInt32(value.ToString());
return true;
}
catch (FormatException)
{
return false;
}
}
}
when I run the code I get this error :
LINQ to Entities does not recognize the method 'System.Linq.IOrderedQueryable`1[Salary.Classes.ReturnData] OrderBy[ReturnData,String](System.Linq.IQueryable`1[Salary.Classes.ReturnData], System.Linq.Expressions.Expression`1[System.Func`2[Salary.Classes.ReturnData,System.String]], System.Collections.Generic.IComparer`1[System.String])' method, and this method cannot be translated into a store expression.
It's a legacy database and I can't change any data type because may raise error on other applications.
You have two problems here:
You are storing numbers as strings in your database and
Youre trying to execute C# code on Sql Server
The exception you are receiving is due to the fact that the compiler cannot translate the comparison logic from SemiNumericComparer class into a sql query.
In order to achieve the desired result you could:
a) Load all data in memory and perform the comparison using SemiNumericComparer in memory by iterating through the selected results and ordering them after that like this:
var model = (from c in General.db.GlbTbComboBases
where c.ClassCode.Equals(classCode)
select new ReturnData { id = c.BaseCode, name = c.FAName })
.ToList() // this will load data into memory
.OrderBy(c => c.id, new SemiNumericComparer());
This, however is not a good approach because it will add a lot of useless memory consumption if the dataset is quite small and will crash your application if your dataset is larger than the available memory at a given time.
Edit As pointed out by #gsubiran this approach is not valid.
b) Convert your strings into numbers on Sql Server using SqlFunctions and order them as numbers using the ordering provided by Sql Server:
var model = (from c in General.db.GlbTbComboBases
where c.ClassCode.Equals(classCode)
select new ReturnData { id = c.BaseCode, name = c.FAName })
.OrderBy(c => SqlFunctions.IsNumeric(c.id));
If you want a numerical order you have to sort numberical data types:
string[] str = {"1", "10", "2","011"};
List<string> ordered = str.OrderBy(x => int.Parse(x)).ToList();
Try something like this:
var model = (from c in General.db.GlbTbComboBases
where c.ClassCode.Equals(classCode)
select new { Id = Convert.ToInt32(c.BaseCode), Name = c.FAName })
.OrderBy(c => c.Id)
.Select(x => new ReturnData { id = x.Id, name = x.Name });
Simply added an anonymous type to sort and then converted to the required type. Of course it takes more memory.
There is no a built in way to convert string to int on linq to entities.
SqlFunctions class has many useful functions but no one of them have support to convert/parse/cast string to int.
You could use your own custom sql function and then add it to your EF model and use it as you need. I tested it with table value function, I don't know if it also work with scalar value functions.
CREATE FUNCTION [dbo].[StringToInt](
#strInt AS VARCHAR(30)
)
RETURNS TABLE WITH SCHEMABINDING
RETURN
SELECT TRY_CAST (#strInt AS INT) [IntValue];
Then you add it to your EF model (YourModel.context.cs)
[DbFunction("YourContext", "StringToInt")]
public virtual IQueryable<Nullable<int>> StringToInt(string strInt)
{
var strIntParameter = strInt != null ?
new ObjectParameter("strInt", strInt) :
new ObjectParameter("strInt", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<Nullable<int>>("[YourContext].[StringToInt](#strInt)", strIntParameter);
}
Finally you just could use it in a sorting expression in this way:
var qry = context.SomeEntityYouHave
.Where(x => x.Category == "Numbers")
.OrderBy(
x => ctx.StringToInt(x.StringPropertyContainingNumbersToSort)
.FirstOrDefault()//Don't forget to call FirstOrDefault
);
With this approach you are sorting data on SQL side avoiding the need to do an early call to .ToList() and then sort all the data on application memory.
Of course is a better approach to have int values stored as int instead of strings but some times for different reasons it couldn't be an option.

LINQ: Add RowNumber Column

How can the query below be modified to include a column for row number (ie: one-based index of results)?
var myResult = from currRow in someTable
where currRow.someCategory == someCategoryValue
orderby currRow.createdDate descending
select currRow;
EDIT1: I'm looking for the results to be {idx, col1, col2...col-n} not {idx, row}.
EDIT2: The row number should correspond to result rows not the table rows.
EDIT3: I DataBind these results to a GridView. My goal was to add a row number column to the GridView. Perhaps a different approach would be better.
Use the method-syntax where Enumerable.Select has an overload with the index:
var myResult = someTable.Select((r, i) => new { Row = r, Index = i })
.Where(x => x.Row.someCategory == someCategoryValue)
.OrderByDescending(x => x.Row.createdDate);
Note that this approach presumes that you want the original index of the row in the table and not in the filtered result since i select the index before i filter with Where.
EDIT: I'm looking for the results to be {idx, col1, col2...col-n} not
{idx, row}. The row number should correspond to result rows not
the table rows.
Then select the anonymous type with all columns you need:
var myResult = someTable.Where(r => r.someCategory == someCategoryValue)
.OrderByDescending(r => r.createdDate)
.Select((r, i) => new { idx = i, col1 = r.col1, col2 = r.col2, ...col-n = r.ColN });
Use this Select method:
Projects each element of a sequence into a new form by incorporating the element's index.
Example:
var myResult = someTable.Where(currRow => currRow.someCategory == someCategoryValue)
.OrderByDescending(currRow => currRow.createdDate)
.Select((currRow, index) => new {Row = currRow, Index = index + 1});
In response to your edit:
If you want a DataTable as result, you can go the non-Linq way by simply using a DataView and add a additional column afterwards.
someTable.DefaultView.RowFilter = String.Format("someCategory = '{0}'", someCategoryValue);
someTable.DefaultView.Sort = "createdDate";
var resultTable = someTable.DefaultView.ToTable();
resultTable.Columns.Add("Number", typeof(int));
int i = 0;
foreach (DataRow row in resultTable.Rows)
row["Number"] = ++i;
what about?
int i;
var myResult = from currRow in someTable
where currRow.someCategory == someCategoryValue
orderby currRow.createdDate descending
select new {Record = i++, currRow};
Just for fun, here's an alternative to Select with two arguments:
var resultsWithIndexes = myResult.Zip(Enumerable.Range(1, int.MaxValue - 1),
(o, i) => new { Index = i, Result = o });
According to you edit 1. NO, YOU CAN'T Linq returns the table as it is. You can build each column, but you lose the power of mapped entities.
This has been asked multiple times before: How do you add an index field to Linq results
There is no straightforward way if want to keep a flat list of columns (i.e. OP's Edit2) and also want a generic solution that works with any IEnumerable without requiring you to list out the set of expected columns.
However, there is a roundabout way to kinda go about it which is to dump the query results into a DataTable using the ToDataTable() method from here and then add a RowNumber column to that table.
var table = query.ToList().ToDataTable();
table.Columns.Add("RowNum", typeof(int));
int i = 0;
foreach (DataRow row in table.Rows)
row["RowNum"] = ++i;
This would likely cause performance issues with large datasets but it's not insanely slow either. On my machine a dataset with ~6500 rows took 33ms to process.
If your original query returned an anonymous type, then that type definition will get lost in the conversion so you'll lose the static typing on the column names of the resulting IEnumerable when you call table.AsEnumerable(). In other words, instead of being able to write something like table.AsEnumerable().First().RowNum you instead have to write table.AsEnumerable().First()["RowNum"]
However, if you don't care about performance and really want your static typing back, then you can use JSON.NET to convert the DataTable to a json string and then back to a list based on the anonymous type from the original query result. This method requires a placeholder RowNum field to be present in the original query results.
var query = (from currRow in someTable
where currRow.someCategory == someCategoryValue
orderby currRow.createdDate descending
select new { currRow.someCategory, currRow.createdDate, RowNum = -1 }).ToList();
var table = query.ToDataTable();
//Placeholder RowNum column has to already exist in query results
//So not adding a new column, but merely populating it
int i = 0;
foreach (DataRow row in table.Rows)
row["RowNum"] = ++i;
string json = JsonConvert.SerializeObject(table);
var staticallyTypedList = JsonConvert.DeserializeAnonymousType(json, query);
Console.WriteLine(staticallyTypedList.First().RowNum);
This added about 120ms to the processing time for my 6500 item dataset.
It's crazy, but it works.
I know I'm late to the party, but I wanted to show what worked for me.
I have a list of objects, and the object has an integer property on it for "row number"... or in this case, "Sequence Number". This is what I did to populate that field:
myListOfObjects = myListOfObjects.Select((o, i) => { o.SequenceNumber = i; return o; }).ToList();
I was surprised to see that this worked.
This one helped me in my case - Excel sheet extraction. anonymous type
var UploadItemList = ItemMaster.Worksheet().AsEnumerable().Select((x, index) => new
{
Code = x["Code"].Value == null ? "" : x["Code"].Value.ToString().Trim(),
Description = x["Description"].Value == null ? "" : x["Description"].Value.ToString().Trim(),
Unit = x["Unit"].Value == null ? "" : x["Unit"].Value.ToString().Trim(),
Quantity = x["Quantity"].Value == null ? "" : x["Quantity"].Value.ToString().Trim(),
Rate = x["Rate"].Value == null ? "" : x["Rate"].Value.ToString().Trim(),
Amount = x["Amount"].Value == null ? "" : x["Amount"].Value.ToString().Trim(),
RowNumber = index+1
}).ToList();
int Lc = 1;
var Lst = LstItemGrid.GroupBy(item => item.CategoryName)
.Select(group => new { CategoryName = group.Key, Items = group.ToList() ,RowIndex= Lc++ })
.ToList();

Adding a where/order by clause to an IQueryable

I have ths function to query a set of records from the DB:
public IQueryable<PointTransactionViewModel> GetPointTransactions(int UserID)
{
return
(
from PointTransaction p in entities.PointTransaction
join ActivityLog a in entities.ActivityLog
on p.TransactionID equals a.TransactionID
where p.UserID == UserID
select new PointTransactionViewModel
{
ID = p.TransactionID,
Balance = p.Balance,
Points = p.Amount,
RelatedActivityID = a.ID,
When = p.When,
Sender = p.SenderUserInfo.CompleteName
}
);
}
I wish to add an additional cause, like this
var entries = GetPointTransaction(1);
return entries.OrderbyDescending.Where( x => x.When >= start && w.When <= end).
( x => x.When);
However, I seem to need to create a new query from the existing one for this to work. But, I have seem this work before without creating a new query, in the code snippet before:
public PaginatedList(IQueryable<T> source, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
PageSize = pageSize;
TotalCount = source.Count();
TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize);
this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize));
}
Does the code above somehow doesn't need a new query to be created for the IQueryable source object? Was a temporary object created?
Edit
It's strange, but to get it to work I have to do the following:
IQueryable<ActivityLogEntry> log = activityRepo.GetPointTransaction(userID).
Where(x => x.PointsEarned == 50);
return log.ToList();
The following will not work:
var log = = activityRepo.GetPointTransaction(userID);
log.Where( x => x.PointsEarned == 50);
return log.ToList();
There is no error message, just that the where clause seems to be ignored (it is also returning all data which PointsEarned is not 50)
Your entries is of IQueryable type, that's enough and you can add any number of clauses before fetching the data, e.g. before calling the ToList() function.
It doesn't execute the SQL code, just an expression tree will be created until you fetch the whole data with one of the existing methods (again, e.g. the ToList() function).
var query = context.Where(x=>x.id == test);
query = query.Where(anotherCondition1);
query = query.Where(anotherCondition2);
...
var result = query.ToList();
it's equal to
var result = context.Where(x=>x.id == test)
.Where(anotherCondition1)
.Where(anotherCondition2)
....
.ToList()
This is called deferred execution, for more details see the MSDN blog post on LINQ and Deferred Execution.
You do need to create a new object. IQueryable is immutable. Don't worry this is how you are supposed to do it. This is how the queries are formed internally. All the extension methods like "Where" don't actually change the object. They just return a new one.
The code that you claim works should not work. The method doesn't even have a type.
i mean you can write this sample :
opportunites = from opp in oppDC.Opportunities
join org in oppDC.Organizations on opp.OrganizationID equals org.OrgnizationID
select new
{
opp.OpportunityID,
opp.Title,
opp.PostedBy,
opp.Address1,
opp.CreatedDate,
org.OrganizationName
};
if(condition)
{
opportunites = opportunites.Where(opp => opp.Title.StartsWith(title));
}
//------Other Condition you need
if(!String.IsNullOrEmpty(title))
{
opportunites = opportunites.Where(.....);
}
if(!String.IsNullOrEmpty(name))
{
opportunites = opportunites.Where(.....);
}
As others have pointed out, you do not need a new object. Your syntax for OrderByDescending is wrong though, you need to specify the key selector.
var entries = GetPointTransaction(1);
return entries.Where(x => x.When >= start && w.When <= end).OrderbyDescending(x => x.When);

Categories