Very basic question. How do I do modify Linq results?
To expound further, I selected an order list from an order table in the database. I need to display the results in a gridview on a web form. First, I need to modify some of the results. For instance, the "Quote" field should be changed to blank when the "Order" field has a value. It is likely I might have to do more elaborate manipulation of the results. In the past, it was possible to loop through arrays, and modify them, but today's programming seems to not want loops happen. At the moment the results seem to be read-only, as if I am doing something wrong by needing to modify the list.
protected void fillGridView()
{
using (CqsDataDataContext cqsDC = new CqsDataDataContext())
{
var orderlist = from x in cqsDC.MasterQuoteRecs
where x.CustomerNumber == accountNumber && x.DateCreated > DateTime.Now.AddDays(howfar)
orderby x.DateCreated descending
select new
{
customer = x.customername,
order = x.OrderReserveNumber,
quote = x.Quote,
date = Convert.ToDateTime(x.DateCreated).ToShortDateString(),
project = x.ProjectName,
total = x.Cost,
status = x.QuoteStatus
};
// I would like to loop thru list and make changes to it here
GridView1.DataSource = orderlist;
GridView1.DataBind();
}
}
You end up with an IQueryable<anonymoustype> with your current query. Since they're anonymous types they're readonly and can't be changed anyway.
Your best option, especially if you intend to have more complex manipulations that can't be done in the query by the database, is to use a class instead. You'll also want to add a ToList() at the end of your query so you end up with a List<YourClass> and can then loop over it as you usually would and change the objects.
So make a class that has all your properties, for example MasterQuote, and use that in your query instead:
var query = from x in cqsDC.MasterQuoteRecs
where x.CustomerNumber == accountNumber && x.DateCreated > DateTime.Now.AddDays(howfar)
orderby x.DateCreated descending
select new MasterQuote
{
Customer = x.customername,
Order = x.OrderReserveNumber,
Quote = x.Quote,
Date = Convert.ToDateTime(x.DateCreated).ToShortDateString(),
Project = x.ProjectName,
Total = x.Cost,
Status = x.QuoteStatus
};
var orderList = query.ToList();
foreach (var item in orderList)
{
if (item.OrderReserveNumber > 0)
{
item.Quote = "";
}
}
Your MasterQuote class would look something like:
public class MasterQuote
{
public string Customer { get; set; }
public int Order { get; set; }
// and so on
}
Of course for your given example you could probably accomplish the Quote manipulation in your query as seth mentioned.
Just use a ternary operator.
select new
{
customer = x.customername,
order = x.OrderReserveNumber,
quote = x.OrderReserveNumber != null ? string.Empty : x.Quote,
date = Convert.ToDateTime(x.DateCreated).ToShortDateString(),
project = x.ProjectName,
total = x.Cost,
status = x.QuoteStatus
};
Depends on how complex the changes are really, but the one you mentioned could be done with a simple method on a static class.
Of you could just chain linq statements together, and do the equivalent of sql case for the quote column.
It seems unlikely that OrderList doesn't implement IEnumerable so if all the linq gets too messy foreach will do the job.
Related
I'm running a query in my project with multiple joins. I want to provide the WHERE clause with a variable instead of hard coded as it is now but cannot seem to get the correct syntax.
var data = (from a in db.StudentData
join b in db.Contacts on a.SID equals b.SID
join c in db.Addresses on a.SID equals c.SID
join d in db.EntryQuals.DefaultIfEmpty() on a.SID equals d.SID
where a.SID == searchTxt
select new
{
ID = a.SID,
Birthdate = a.BIRTHDTE,
FirstName = a.FNAMES,
PreviousName = a.PREVSURNAME,
EntryQualAwardID = d.ENTRYQUALAWARDID,
AwardDate = d.AWARDDATE
}).ToList();
How can I get my WHERE clause to work with a variable (ie: a.[ fieldVar ] ) where fieldVar could be "SID" as it is in the code currently.
When dealing with user select-able search criteria you will need to code for the possible selections. When dealing with building searches I recommend using the Fluent syntax over the Linq QL syntax as it builds an expression that is easy to conditionally modify as you go. From there you can use a Predicate & PredicateBuilder to dynamically compose your WHERE condition.
Jacques solution will work, but the downside of this approach is that you are building a rather large & complex SQL statement which conditionally applies criteria. My preference is to conditionally add the WHERE clauses in the code to ensure the SQL is only as complex as it needs to be.
If you want to do something like a smart search (think Google with one text entry to search across several possible fields)
var whereClause = PredicateBuilder.False<StudentData>();
int id;
DateTime date;
if(int.TryParse(searchTxt, out id))
whereClause = whereClause.Or(x => x.SID == id);
else if(DateTime.TryParse(searchTxt, out date))
whereClause = whereClause.Or(x => x.BirthDate == date);
else
whereClause = whereClause.Or(x => x.FirstName.Contains(searchTxt));
var data = db.StudentData
.Where(whereClause)
.Select(a => new
{
ID = a.SID,
Birthdate = a.BIRTHDTE,
FirstName = a.FNAMES,
PreviousName = a.PREVSURNAME,
EntryQualAwardID = a.EntryQuals.ENTRYQUALAWARDID,
AwardDate = a.EntryQuals.AWARDDATE
}).ToList();
This does some basic evaluations of the search criteria to see if it fits the purpose of the search. I.e. if they can search by name, date, or ID and IDs are numeric, we only search on an ID if the criteria was numeric. If it looked like a date, we search by date, otherwise we search by name. (and potentially other searchable strings)
If they can search for ID, FirstName, and BirthDate and enter one or more of those as separate entry fields (Search criteria page) then based on which entries they fill in you can either pass separate nullable parameters and do the above based on what parameters are passed, or pass a list of search values with something like an Enum for which value was searched for:
I.e. by parameters:
private void ByParameters(int? id = null, DateTime? birthDate = null, string name = null)
{
var whereClause = PredicateBuilder.False<StudentData>();
if(id.HasValue)
whereClause = whereClause.Or(x => x.SID == id.Value);
if(date.HasValue)
{
DateTime dateValue = date.Value.Date;
whereClause = whereClause.Or(x => x.BirthDate == dateValue);
}
if (!string.IsNullOrEmpty(name))
whereClause = whereClause.Or(x => x.FirstName.Contains(name));
// ....
}
If the number of parameters starts to get big, then a custom type can be created to encapsulate the individual null-able values. I.e.:
[Serializable]
public class SearchCriteria
{
public int? Id { get; set; }
public DateTime? BirthDate { get; set; }
public string Name { get; set; }
}
private void ByParameters(SearchCriteria criteria)
{
// ....
}
Or you can compose a more dynamic parameter list object with a criteria type and value but it starts getting more complex than it's probably worth.
You can't really do that in Linq, sine linq needs to know the the type of the field at compile time. A workaround would be something like
where (fieldVar=="SID" && a.SID == searchTxt) ||
(fieldVar=="FNAMES" && a.FNAMES== searchTxt) || ...
This will also alert you at compile time if you are doing an illegal comparison, eg. comparing a date to a string.
I have a query which selects a list of my class. It looks like so:
IQueryable<ClaimsBySupplierAggregate> agg =
(from d in alliance.SupplierSearchByReviewPeriod
where d.ClientID == ClientID && ReviewPeriodIDs.Contains((int)d.ReviewPeriodID)
select new ClaimsBySupplierAggregate {
Amount = d.Amount,
StatusCategoryID = d.StatusCategoryID,
DeptName = d.DepartmentName,
APLReason = d.APLReason,
Area = d.AreaDesc,
StatusCategoryDesc = d.StatusCategoryDesc,
Agreed = d.Agreed
});
Later on in the application I select each variable and get the distinct values like this:
SupplierModel.APLReason = agg.Select(r => r.APLReason).Distinct().ToList();
SupplierModel.AreaDesc = agg.Select(r => r.Area).Distinct().ToList();
SupplierModel.DeptName = agg.Select(r => r.DeptName).Distinct().ToList();
SupplierModel.StatCatDes = aggg.Select(r => r.StatusCategoryDesc).Distinct().ToList();
Is there a way to do this in one LINQ statement?
You could using Aggregate, but you would need a complex object for the seed, which brings you to the same complexity of code. I think that for this particular case, using LInQ enters the Golden Hammer antipattern. Just use an old fashioned loop and four HashSets instead of Lists and you are done and the code is more readable and your intention clearer.
I think you can use this then customize the code follow your expectation.
Or you can use group by.
public class LinqTest
{
public int id { get; set; }
public string value { get; set; }
public override bool Equals(object obj)
{
LinqTest obj2 = obj as LinqTest;
if (obj2 == null) return false;
return id == obj2.id;
}
public override int GetHashCode()
{
return id;
}
}
List<LinqTest> uniqueIDs = myList.Distinct().ToList();
There is a contrived way to do this:
from d in alliance.SupplierSearchByReviewPeriod
where d.ClientID == ClientID && ReviewPeriodIDs.Contains((int)d.ReviewPeriodID)
group d by 0 into g
select new
{
APLReasons = g.Select(d => d.APLReason).Distinct(),
AreaDescs = g.Select(d => d.Area).Distinct(),
DeptNames = g.Select(d => d.DeptName).Distinct(),
StatusCategoryDescs = g.Select(d => d.StatusCategoryDesc).Distinct(),
}
The part group d by 0 into g creates one group of all items, from which you can subsequently query any aggregate you want.
BUT...
...this creates a very inefficient query with UNIONs and (of course) DISTINCTs. It's probably better (performance-wise) to simply get the flat data from the database and do the aggregations in subsequent code.
I've got a table Installation which can contains one or many Equipements.
And for functionnal reasons, I've overwritten my table Installation and added a field NbrEquipements.
I want to fill this field with Linq, but I'm stuck...
Due to special reasons, there is no relation between these to tables. So, no Installation.Equipements member into my class. Therefore, no Installation.Equipements.Count...
I'm trying some stuff. Here is my code:
var query = RepoInstallation.AsQueryable();
// Some filter
query = query.Where(i => i.City.RegionId == pRegionId));
int?[] etatIds = { 2, 3 };
query = (from i in query
select new Installation
{
NbrEquipements= (from e in RepoEquipement.AsQueryable()
where e.InstallationSpecialId == i.SpecialId
&& (etatIds.Contains(e.EquEtat))
select e.SasId
).Count()
});
But with this try, I got this error:
The entity or complex type 'myModel.Installation' cannot be constructed in a LINQ to Entities query
I've tried some other stuff but I'm always turning around...
Another thing that can be useful for me: It would be great to fill a field called Equipements which is a List<Equipement>.
After that, I would be able to Count this list...
Is it possible ?
Tell me if I'm not clear.
Thanks in advance.
Here is the final code:
//In the class:
[Dependency]
public MyEntities MyEntities { get; set; }
//My Methode code:
var query = MyEntities .SasInstallations.AsQueryable();
// Some filter
query = query.Where(i => i.City.RegionId == pRegionId));
var liste = new List<Installation>();
var queryWithListEquipements =
from i in query
select new
{
Ins = i,
EquipementsTemp = (from eq in MyEntities.Equipements.AsQueryable()
where eq.SpecialId == i.SpecialId
&& (etatIds.Contains(eq.SasEquEtat))
select eq
).ToList()
};
var listWithListEquipements = queryWithListEquipements.ToList();
foreach (var anonymousItem in listWithListEquipements)
{
var ins = anonymousItem.Ins;
ins.Equipements = anonymousItem.EquipementsTemp;
ins.NumberEquipements = ins.Equipements.Count();
liste.Add(ins);
}
return liste;
By the way, this is very very fast (even the listing of Equipements). So this is working exactly has I wished. Thanks again for your help everyone!
Use an anonymous type. EF does not like to instantiate entity classes inside a query.
var results = (from i in query
select new
{
NbrEquipements= (from e in RepoEquipement
where e.InstallationSpecialId == i.SpecialId
&& (etatIds.Contains(e.EquEtat))
select e.SasId
).Count()
})
.ToList();
Notice how I used select new instead of select new Installation.
You can then use the data inside the list (which is now in memory) to create instances of type Installation if you want like this:
var installations = results.Select(x =>
new Installation
{
NbrEquipements = x.NbrEquipements
}).ToList();
Here is how to obtain the list of equipment for each installation entity:
var results = (from i in query
select new
{
Installation = i,
Equipment = (from e in RepoEquipement
where e.InstallationSpecialId == i.SpecialId
&& (etatIds.Contains(e.EquEtat))
select e).ToList()
})
.ToList();
This will return a list of anonymous objects. Each object will contain a property called Installation and another property called Equipment (which is a list). You can easily convert this list (of anonymous objects) to another list of whatever type that you want.
I am just learning LINQ and I have come across and issue Im not sure how to do in LINQ.
string numbers = "1,3,4,5";
string[] outletsInaStringArray = outlets.Split(',');
List<string> numbersAsAList = outletsInaStringArray.ToList();
I have a field in my database which holds a number. I only want to select the lines WHERE the number in the database is IN the line list of numbers "1,3,4,5" (these numbers are just examples).
Thanks in advance
I have looked at Tim and James answers and also looked at the line that James has sent. Im still a bit confused.....Sorry. Below is my actual code. It compiles but does not work
string outlets = "1,3,4,5"
string[] outletsNeeded = outlets.Split(',');
List<string> outletsNeededList = outletsNeeded.ToList();
DashboardEntities1 db = new DashboardEntities1();
var deptSalesQuery = (
from d in db.DashboardFigures
where (d.TypeOfinformation == "DEPTSALES") && (outletsNeeded.ToString().Contains(d.OutletNo.ToString()))
select new DeptSales
{
Dn = (int)d.Number,
Dnm = "Mens",
On = d.OutletNo,
Qs = (double)d.Value_4,
Se = (double)d.Value_2,
Si = (double)d.Value_3
}
);
In the DASHBAORDFIGURES table in SQL I have 2 records where the outlets number = 1, and therefore should have come up with two records.
Sorry if this is a simple thing, its just new to me and its frustrating.
You can use Contains as tagged:
var query = db.Table
.Where(x => outletsInaStringArray.Contains(x.Number) && x.information == "SALES");
that was method syntax, if you prefer query syntax:
var query = from figure in db.Figures
where outletsInaStringArray.Contains(figure.number)
&& figure.information == "SALES"
select figure;
But the column number is int, the List<string> stores strings, maybe your LINQ provider does not support .Contains(figure.ToString()). Then convert the strings to int first:
List<int> outletsNeededList = outletsNeeded.Select(int.Parse).ToList();
The answer that Tim provided is one method. Linq and lambda are interchangeable. Have a look at the following posting as well. Link
var result = from x in db.Table.ToList()
where outletsInaStringArray.Contains(x.Number)
select x;
Also have a look the following as it offers a very similar solution to the one you are looking for:
Link
As per i understand, you want to fetch data in similar way as IN (SQL) clause does it.
SELECT <Field_List>
FROM Table
WHERE IntegerField IN (1,2,4,5)
But i'm wondering why do you want to do it that way, when you can join data and get only matches. The worse is that you're trying to mix different data type and pass comma delimited text as a set of integers (i may be wrong):
SELECT <Field_List>
FROM Table
WHERE IntegerField IN ("1,2,4,5")
Above query won't execute, because the set of integers is "packed" into comma delimited string. To be able to execute that query, a conversion between data types must be done. Numbers in a string have to be converted to a set of integers (using user define split function or Common Table Expression):
;WITH CTE AS
(
--here convertion occurs
)
SELECT t2.<Field_List>
FROM CTE As t1 INNER JOIN TableName AS t2 ON t1.MyNumber = t2.IntegerField
Linq + any programming language is more flexible. You can build a list of integers (List) to build query.
See simple example:
void Main()
{
List<MyData> data = new List<MyData>{
new MyData(1,10),
new MyData(2, 11),
new MyData(5, 12),
new MyData(8, 13),
new MyData(12, 14)
};
//you're using comma delimited string
//string searchedNumbers = "1,3,4,5";
//var qry = from n in data
// join s in searchedNumbers.Split(',').Select(x=>int.Parse(x)) on n.ID equals s
// select n;
//qry.Dump();
List<int> searchedNumbers = new List<int>{1,2,4,5};
var qry = from n in data
join s in searchedNumbers on n.ID equals s
select n;
qry.Dump();
}
// Define other methods and classes here
class MyData
{
private int id = 0;
private int weight = 0;
public MyData(int _id, int _weight)
{
id = _id;
weight = _weight;
}
public int ID
{
get{return id;}
set {id = value;}
}
public int Weight
{
get{return weight;}
set {weight = value;}
}
}
Result:
ID Weight
1 10
5 12
Cheers
Maciej
Thank you all iv now got it to work using all your suggestions
the final code that works is as follows
DeptSales myDeptSales = new DeptSales(); // Single department
List<DeptSales> myDeptSalesList = new List<DeptSales>(); // List of Departments
DashboardEntities1 db = new DashboardEntities1();
var deptSalesQuery = from d in db.DashboardFigures
join s in outlets.Split(',').Select(x => int.Parse(x)) on d.OutletNo equals s
where (d.TypeOfinformation == "DEPTSALES")
select new DeptSales
{
Dn = (int)d.Number,
Dnm = "Mens",
On = d.OutletNo,
Qs = (double)d.Value_4,
Se = (double)d.Value_2,
Si = (double)d.Value_3
};
Thanks once again.
I have two List objects. I want a count of how many items between the two list match. Now I could loop through with a counter as I come across matches....but that would be kinda lame.
However this one is stretching my LINQ knowledge. I believe what I want to do is Join, discern (where), group, and project the count. I came to this approach by reading similar SO questions and LINQ documentation.
However if this is flawed by all means it doesn't have to be this way. I just want the count of matching elements.
So my "master" object is:
public class Master
{
public StringModel OldText { get; private set; }
public StringModel NewText { get; private set; }
public Master()
{
OldText = new StringModel();
NewText = new StringModel();
}
}
StringModel is:
public class StringModel
{
public List<strPiece> Lines { get; private set; }
public StringModel()
{
Lines = new List<strPiece>();
}
}
My LINQ thus far is:
var unchangedJoin = from oldLine in Master.OldText.Lines
join newLine in Master.NewText.Lines
on oldLine.Type equals newLine.Type
where oldLine.Type == "ABC"
group oldLine by --as you can see I kinda break down here.
Any help finishing would be appreciated. Also if I need to post more code just let me know.
Thank You
Sounds like a good use for intersect
var infoQuery =
(from cust in db.Customers
select cust.Country)
.Intersect
(from emp in db.Employees
select emp.Country)
;
Just perform a GroupJoin rather than a Join.
var unchangedJoin = from oldLine in Master.OldText.Lines
join newLine in Master.NewText.Lines
on oldLine.Type equals newLine.Type
into newMatches
where oldLine.Type == "ABC"
select oldLine;
var matches = unchangedJoin.Count();
I too would recommend Intersect. If you go that way, using the fluent syntax makes more sense.
int matches = Master.OldText.Lines
.Where(line => line.Type == "ABC")
.Intersect(Master.NewText.Lines)
.Count();