var query = db.Customers
.Where("City == #0 and Orders.Count >= #1", "London", 10)
.OrderBy(someStringVariable)
.Select("new(CompanyName as Name, Phone)");
How can I check if someStringVariable is a valid order by expression before I run this query?
I want to check it instead of catching ParseException.
Valid string: "City ASC, Phone DESC"
Invalid string is not existing field or mistype in DESC: "City1 DESC1"
With help of Anu I am using this function but I would prefer some "TryParseQuery" in the Linq.Dynamic namespace.
public static bool IsValidOrderString(Type type, string ordering)
{
if (string.IsNullOrWhiteSpace(ordering)) return false;
var orderList = ordering.Trim().Split(',');
var fields = type.GetProperties().Select(property => property.Name).ToArray();
foreach (var orderItem in orderList)
{
var order = orderItem.TrimEnd();
if (order.EndsWith(" ASC", StringComparison.InvariantCultureIgnoreCase) || order.EndsWith(" DESC", StringComparison.InvariantCultureIgnoreCase)) order = order.Substring(0, order.Length - 4);
if (!fields.Contains(order.Trim())) return false;
}
return true;
}
as #mjwills pointed out, it is hard to do it with 100% reliability, but one thing you could do is compare 'someStringVariable' with list of columns in your table. You can find the list of columns via
.GetProperties().Select(property => property.Name).ToArray();
Again you need to be aware that this has many pitfalls. Properties can be mapped to column names that are not same as the property name.
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 table here where it gets popuplated with ActiveDirectory users every night. This list included generic AD accounts used for a variety of purposes.
Examples of lastnames of generic accounts:
vendor testing
IT support
Dept1 Printer
Visitor1
Visitor2
Guest1
Guest2 and etc
I want to retrieve all records ignoring these records. Something like
select * from table where lastname not like '%visitor%'
and lastname not like "%support%"
and so on I made this query but it does not do substring comparison.
List<String> _ignoreList = new List<String> { "visitor", "test" };
IQueryable<String> _records =
from _adUserDatas in _adUserDataDBDataContext.ADUserDatas
where
_adUserDatas.accountActive.ToLower().Contains("yes")
&& _adUserDatas.staffStudentType.ToLower().Contains("neither")
&& !_ignoreList.Contains(_adUserDatas.lastName)
orderby _adUserDatas.username
select _adUserDatas.username;
Here's the resulting SQL being sent to SQL Server.
{
SELECT[t0].[username]
FROM[dbo].[ADUserData] AS[t0]
WHERE
(LOWER([t0].[accountActive]) LIKE# p0)
AND
(LOWER([t0].[staffStudentType]) LIKE# p1)
AND
(NOT([t0].[lastName] IN(#p2, #p3)))
ORDER BY[t0].[username]
}
in LINQ query above, it did not ignore a record with the lastname "only for testing acct".
Any ideas on how to implement it using LINQ?
I've search the net but nothing came up.
Thanks a lot
That is because your are checking whether ignoreList contains the LastName, try doing it the other way.. i.e Whether LastName conatins anything from the ignoreList..
&& !_ignoreList.Any( il => _adUserDatas.lastName.Contains( il ) )
This way it will check whether "only for testing acct" contains anything from { "visitor", "test" }
Hm.. it could be hard to get to work like predicate with in clausule.. My solution would be other:
var queryable = from _adUserDatas in _adUserDataDBDataContext.ADUserDatas
where
_adUserDatas.accountActive.ToLower().Contains("yes")
&& _adUserDatas.staffStudentType.ToLower().Contains("neither")
orderby _adUserDatas.username
select _adUserDatas.username;
foreach (var ignore in _ignoreList)
{
var localIgnore = ignore;
queryable = queryable.Where(userName => !userName.Contains(localIgnore))
}
var result = queryable.ToList();
The answer from pwas lead me to one that works for my situation. PredicateBuilder which is mentioned in lots of topics here in SOF.com. http://www.albahari.com/nutshell/predicatebuilder.aspx
Here's the final code:
ADUserDataDBDataContext _adUserDataDBDataContext = new ADUserDataDBDataContext();
IQueryable<String> _records = null;
Expression<Func<ADUserData,Boolean>> _whereClause = PredicateBuilder.True<ADUserData>();
_whereClause = _whereClause.And(ADUserData => ADUserData.accountActive.ToLower().Contains("yes"));
foreach (var _item in _ignoreList)
{
_whereClause = _whereClause.And(ADUserData => !ADUserData.lastName.ToLower().Contains(_item));
}
_records = _adUserDataDBDataContext.ADUserDatas
.Where(_whereClause)
.Select(ADUserData => ADUserData.fan);
return _records.ToList();
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.
I have class "Postavke" and then i store it into
List<Postavke> postavke = new List<Postavke>();
Now i want to find some elemnt (property) from this List. I know "Name", "Surname" and i want to get "Address".
How to get "Adress" if i know "Name" and "Surname". All this are properties in "Postavke" class
whole class
public class Postavke
{
#region Properties
public string Name { get; set; }
public string Surname { get; set; }
public string Address { get; set; }
#endregion
#region Methods
public Postavke(string name, string surname, string address, string oznakaLokacije, string oznakaZapore)
{
Name = ean;
Surname = surname;
Address = address;
}
#endregion
}
You can query postavke for all results that contain the name and surname and put the results into a list. Putting the results into a list I find makes things easier to validate and handle unforseen items as from the looks of it it is possible that duplicate items could appear.
if the results must have all data within the list item then:
List<Postavke> results = new List<Postavke>();
var query1 = from a in postavke
where a.Name == searchName
&& a.Surname == searchSurname
select a;
results.AddRange(query1);
this list will have all the results that contain the exact name and surname.
If you just want the address then you can use:
List<string> results = new List<string>();
var query1 = from a in postavke
where a.Name == searchName
&& a.Surname == searchSurname
select a.Address;
results.AddRange(query1);
this will produce a list of addresses. From here you can then validate the list if you want by doing such things as checking to see how many items in the list there are so you know how you want to handle it etc.
If you want to use just either the name or the surname then you can remove the line that asks for one or the other.
If you end up with duplicates but the results are the same then you can change the line
results.AddRange(query1);
to
results.AddRange(query1.Distinct());
by using the Distinct() method it will sort the query and remove duplicates so the list will not be in the same order as it would if you didn't use it.
If you only wanted one result then it's worth validating it by checking how many items are in the list by using
results.Count
you could if it equals 1 carry on, otherwise throw up a message or some other way you may want to handle it. Other pieces of validation that are good is to set values ToLower() and Trim() during the search as to avoid actually changing the original text.
So taking the last query as an example:
List<string> results = new List<string>();
var query1 = from a in postavke
where a.Name.ToLower().Trim() == searchName.ToLower().Trim()
&& a.Surname.ToLower().Trim() == searchSurname.ToLower().Trim()
select a.Address;
results.AddRange(query1);
you can also filter the query with the where clause after you make the query by doing:
List<string> results = new List<string>();
var query1 = from a in postavke
select a.Address;
query1 = query1.Where(h => h.Name == searchName);
query1 = query1.Where(h => h.Surname == searchSurname);
results.AddRange(query1);
The 2 where clauses were split to show you can perform where clause at different points so you could have where clauses within if statements. you can combine the 2 into:
query1 = query1.Where(h => h.Name == searchName && h.Surname == searchSurname);
Hopefully this helps
This will work if you can be sure there's exactly one match
var address = poatavke.Where(p=>p.Name == name && p.Surname == surname).Single().Address;
If you don't know if there's no matches or exactly one you can do:
var posta = poatavke.Where(p=>p.Name == name && p.Surname == surname).SingleOrDefault()
var address = posta == null ? string.Empty : posta.Address;
if you don't know how many matches there's going to be but always want the first (or are using ET which doesn't understand Single())
var posta = poatavke.Where(p=>p.Name == name && p.Surname == surname).FirstOrDefault()
var address = posta == null ? string.Empty : posta.Address;
I have a search result from the database,
int searchTerm = "xx"
var result = from orderLines in context.OrderLineSearch(searchTerm)
select new
{
OrderNumber = orders.OrderNumber,
OrderLineId = orders.OrderLineId
};
I need to validate whether result contains many orders. That is I need to check whether multiple orders are returned or not.
I did the validation by storing the first order number in a variable and compared whether all the other rows contains only this order number. See below
string orderNumber = result.First().OrderNumber;
bool isValid = result.Where(x => x.OrderNumber != orderNumber).Count() == 0;
I want to know the best way to validate using LINQ? Can any one help me?
Thanks in advance.
May be you should to try group results by OrderNumber and then calculate Count?
var result = from orderLines in context.OrderLineSearch(searchTerm)
group orderLines by orderLines.OrderNumber into g
select g.Key
bool hasElements = result.Any();