Alternative coding to a conditional var inferred-type query LINQ to XML? - linq

This is a follow up on a related topic found here
https://stackoverflow.com/questions/1987485/conditionally-assign-c-var-as-elegant-as-it-gets
if I am doing the following:
var query = (SearchString == "" ?
(
from MEDIA in xdoc.Descendants("MEDIA")
select new
{
PLAY = MEDIA.Element("PLAY").Value,
PIC = MEDIA.Element("PIC").Value,
TTL = MEDIA.Element("TTL").Value
}
):
from MEDIA in xdoc.Descendants("MEDIA")
where MEDIA.Element("TTL").ToString().ToLower().Contains(SearchString)
select new
{
PLAY = MEDIA.Element("PLAY").Value,
PIC = MEDIA.Element("PIC").Value,
TTL = MEDIA.Element("TTL").Value
}
) ;
How would I declare the query type to make it static at the class level?
Alternatively, in the referenced post Marc Gravell point out a different approach
IQueryable<Part> query = db.Participant;
if(email != null) query = query.Where(p => p.EmailAddress == email);
if(seqNr != null) query = query.Where(p => p.SequenceNumber == seqNr);
...
How would I declare/recode the query in my case?
Here is my wild attempts :)
IEnumerable<XElement> query = xdoc.Descendants("MEDIA");
if (SearchString != "" )
query = query.Where(m => m.Element("TTL").ToString().ToLower().Contains(SearchString));
Thank you.

How would I declare the query type to make it static at the class level?
You can't. Anonymous types are, well, anonymous... so they don't have a name you can use to declare variables. Your query is of type IEnumerable<something>, but you can't refer to something in your code. So you need to create a specific class that represent the results of your query, and use it instead of the anonymous type.

Related

LINQ to Entities does not recognize the method 'System.String get_Item(System.String)' method

I've looked at the various solutions here but none of them seem to work for me, probably because I'm too new to all this and am groping in the dark a bit. In the code below, the object "appointment" contains some basic LDAP information. From a list of such objects I want to be able to get a single record, based on employee id. I hope the code here is sufficient to illustrate. FTR, I've tried various formulations, including trying to use from and a select. All fail with the error given in the Title above.
IQueryable<appointment> query = null;
foreach(var record in results)
{
BoiseStateLdapDataObject record1 = record;
query = db.appointments.Where(x => x.student_id == record1.Values["employeeid"]);
}
if (query != null)
{
var selectedRecord = query.SingleOrDefault();
}
Try to move employee id getting out of query:
IQueryable<appointment> query = null;
foreach(var record in results)
{
var employeeId = record.Values["employeeid"];
query = db.appointments.Where(x => x.student_id == employeeId);
}
if (query != null)
{
var selectedRecord = query.SingleOrDefault();
}

LINQ to Entities complex query

Is it possible ...??? I have 4 DropDownLists on my main page and the
user may select from any, all or some of
the DropDownLists. I am capturing their selection (or non-selection) using a SESSION
variable. What I would like to be able to do is pass the session
variable values to my Data Access Layer and build a WHERE clause
(maybe using StringBuilder) and then place that variable SOMEHOW into
my query expression. Is that possible??? Sorry, I'm a newbie. Thanks ~susan~
public class DLgetRestaurants
{
FVTCEntities db = new FVTCEntities();
public List<RESTAURANT> getRestaurants(string cuisineName, string priceName, string cityName)
[Build a string based on the values passed to the function]
{
var cuisineID = db.CUISINEs.First(s => s.CUISINE_NAME == cuisineName).CUISINE_ID;
List<RESTAURANT> result = (from RESTAURANT in db.RESTAURANTs.Include("CITY").Include("CUISINE").Include("Price")
where **[USE STRINGBUIDER EXPRSSION HERE]**
select RESTAURANT).ToList();
return result;
}
}
You can compose Where conditions which are linked by a logical AND relatively easy in LINQ extension method syntax:
var query = db.RESTAURANTs.Include("CITY").Include("CUISINE").Include("Price");
if (userHasSelectedInDDL1)
query = query.Where(r => r.PropertyForDDL1 == ValueFromDDL1);
if (userHasSelectedInDDL2)
query = query.Where(r => r.PropertyForDDL2 == ValueFromDDL2);
if (userHasSelectedInDDL3)
query = query.Where(r => r.PropertyForDDL3 == ValueFromDDL3);
if (userHasSelectedInDDL4)
query = query.Where(r => r.PropertyForDDL4 == ValueFromDDL4);
List<RESTAURANT> result = query.ToList();
For a much more flexible solution to build queries dynamically the Dynamic LINQ Library recommended by boca is probably the better choice.
I have done this in the past using the Dynamic Linq Library.

Only primitive types ('such as Int32, String, and Guid') are supported in this context when I try updating my viewmodel

I am having some trouble with a linq query I am trying to write.
I am trying to use the repository pattern without to much luck. Basically I have a list of transactions and a 2nd list which contains the description field that maps against a field in my case StoreItemID
public static IList<TransactionViewModel> All()
{
var result = (IList<TransactionViewModel>)HttpContext.Current.Session["Transactions"];
if (result == null)
{
var rewardTypes = BusinessItemRepository.GetItemTypes(StoreID);
HttpContext.Current.Session["Transactions"] =
result =
(from item in new MyEntities().TransactionEntries
select new TransactionViewModel()
{
ItemDescription = itemTypes.FirstOrDefault(r=>r.StoreItemID==item.StoreItemID).ItemDescription,
TransactionDate = item.PurchaseDate.Value,
TransactionAmount = item.TransactionAmount.Value,
}).ToList();
}
return result;
}
public static List<BusinessItemViewModel>GetItemTypes(int storeID)
{
var result = (List<BusinessItemViewModel>)HttpContext.Current.Session["ItemTypes"];
if (result == null)
{
HttpContext.Current.Session["ItemTypes"] = result =
(from items in new MyEntities().StoreItems
where items.IsDeleted == false && items.StoreID == storeID
select new BusinessItemViewModel()
{
ItemDescription = items.Description,
StoreID = items.StoreID,
StoreItemID = items.StoreItemID
}).ToList();
}
return result;
However I get this error
Unable to create a constant value of type 'MyMVC.ViewModels.BusinessItemViewModel'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
I know its this line of code as if I comment it out it works ok
ItemDescription = itemTypes.FirstOrDefault(r=>r.StoreItemID==item.StoreItemID).ItemDescription,
How can I map ItemDescription against my list of itemTypes?
Any help would be great :)
This line has a problem:
ItemDescription = itemTypes.FirstOrDefault(r=>r.StoreItemID==item.StoreItemID)
.ItemDescription,
Since you are using FirstOrDefault you will get null as default value for a reference type if there is no item that satifies the condition, then you'd get an exception when trying to access ItemDescription - either use First() if there always will be at least one match or check and define a default property value for ItemDescription to use if there is none:
ItemDescription = itemTypes.Any(r=>r.StoreItemID==item.StoreItemID)
? itemTypes.First(r=>r.StoreItemID==item.StoreItemID)
.ItemDescription
: "My Default",
If itemTypes is IEnumerable then it can't be used in your query (which is what the error message is telling you), because the query provider doesn't know what to do with it. So assuming the that itemTypes is based on a table in the same db as TransactionEntities, then you can use a join to achieve the same goal:
using (var entities = new MyEntities())
{
HttpContext.Current.Session["Transactions"] = result =
(from item in new entities.TransactionEntries
join itemType in entities.ItemTypes on item.StoreItemID equals itemType.StoreItemID
select new TransactionViewModel()
{
ItemDescription = itemType.ItemDescription,
TransactionDate = item.PurchaseDate.Value,
TransactionAmount = item.TransactionAmount.Value,
CustomerName = rewards.CardID//TODO: Get customer name
}).ToList();
}
I don't know the structure of your database, but hopefully you get the idea.
I had this error due a nullable integer in my LINQ query.
Adding a check within my query it solved my problem.
query with problem:
var x = entities.MyObjects.FirstOrDefault(s => s.Obj_Id.Equals(y.OBJ_ID));
query with problem solved:
var x = entities.MyObjects.FirstOrDefault(s => s.Obj_Id.HasValue && s.Obj_Id.Value.Equals(y.OBJ_ID));

How do you return a default value if a LINQ to entities query returns no values

In a LINQ to entities expression like this:
var vote = (from vote in db.Vote where
vote.Voter.Id == user.Id
select v).FirstOrDefault();
How do you add a DefaultIfEmpty value so that when there's no vote I'd get a default value?
Another approach, if Vote is a reference type and thus uses null as its default value, would be to use the null coalescing operator:
var vote = (db.Vote
.Where(v => v.Voter.Id == user.Id)
.FirstOrDefault()) ?? defaultVote;
Add your own extension method. For instance:
public static class Extension
{
public static T FirstOrDefault(this IEnumerable<T> sequence, T defaultValue)
{
return sequence.Any() ? sequence.First() : defaultValue;
}
}
With that class in scope, you can say:
var vote = (from vote in db.Vote where
vote.Voter.Id == user.Id
select v).FirstOrDefault(yourDefaultValue);
Of course, your method can also have an overload that returns default(T), if that was what you where looking for. There is already defined a DefaultIfEmpty extension method in the built-in Extension class, so I named the method in the example "FirstOrDefault", which seems like a better fit.
Just add the default value before getting the first element.
var vote = db.Vote
.Where(v => v.Voter.Id == user.Id)
.DefaultIfEmpty(defaultVote)
.First();
Note that you can now safely use First() instead of FirstOrDefault().
UPDATE
LINQ to Entity does not recognize the DefaultIfEmpty() extension method. But you can just use the null coalescing operator.
var vote = db.Vote.FirstOrDefault(v => v.Voter.Id == user.Id) ?? defaultVote;
I ended up going for a very simple approach which was recommended by an answer here that was latter erased:
var vote = (from vote in db.Vote
where vote.Voter.Id == user.Id
select v).FirstOrDefault();
if (vote == null) {
vote = new Vote() { .... };
db.AddToVoteSet(vote);
}
For some reason if I turn the resultset into a List, the Defaultifempty() works I don't know if I've inadvertantly crossed over into Linq area.
var results = (from u in rv.tbl_user
.Include("tbl_pics")
.Include("tbl_area")
.Include("tbl_province")
.ToList()
where u.tbl_province.idtbl_Province == prov
select new { u.firstName, u.cellNumber, u.tbl_area.Area, u.ID, u.tbl_province.Province_desc,
pic = (from p3 in u.tbl_pics
where p3.tbl_user.ID == u.ID
select p3.pic_path).DefaultIfEmpty("defaultpic.jpg").First()
}).ToList();

How to dynamically add OR operator to WHERE clause in LINQ

I have a variable size array of strings, and I am trying to programatically loop through the array and match all the rows in a table where the column "Tags" contains at least one of the strings in the array. Here is some pseudo code:
IQueryable<Songs> allSongMatches = musicDb.Songs; // all rows in the table
I can easily query this table filtering on a fixed set of strings, like this:
allSongMatches=allSongMatches.Where(SongsVar => SongsVar.Tags.Contains("foo1") || SongsVar.Tags.Contains("foo2") || SongsVar.Tags.Contains("foo3"));
However, this does not work (I get the following error: "A lambda expression with a statement body cannot be converted to an expression tree")
allSongMatches = allSongMatches.Where(SongsVar =>
{
bool retVal = false;
foreach(string str in strArray)
{
retVal = retVal || SongsVar.Tags.Contains(str);
}
return retVal;
});
Can anybody show me the correct strategy to accomplish this? I am still new to the world of LINQ :-)
You can use the PredicateBuilder class:
var searchPredicate = PredicateBuilder.False<Songs>();
foreach(string str in strArray)
{
var closureVariable = str; // See the link below for the reason
searchPredicate =
searchPredicate.Or(SongsVar => SongsVar.Tags.Contains(closureVariable));
}
var allSongMatches = db.Songs.Where(searchPredicate);
LinqToSql strange behaviour
I recently created an extension method for creating string searches that also allows for OR searches. Blogged about here
I also created it as a nuget package that you can install:
http://www.nuget.org/packages/NinjaNye.SearchExtensions/
Once installed you will be able to do the following
var result = db.Songs.Search(s => s.Tags, strArray);
If you want to create your own version to allow the above, you will need to do the following:
public static class QueryableExtensions
{
public static IQueryable<T> Search<T>(this IQueryable<T> source, Expression<Func<T, string>> stringProperty, params string[] searchTerms)
{
if (!searchTerms.Any())
{
return source;
}
Expression orExpression = null;
foreach (var searchTerm in searchTerms)
{
//Create expression to represent x.[property].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
var containsExpression = BuildContainsExpression(stringProperty, searchTermExpression);
orExpression = BuildOrExpression(orExpression, containsExpression);
}
var completeExpression = Expression.Lambda<Func<T, bool>>(orExpression, stringProperty.Parameters);
return source.Where(completeExpression);
}
private static Expression BuildOrExpression(Expression existingExpression, Expression expressionToAdd)
{
if (existingExpression == null)
{
return expressionToAdd;
}
//Build 'OR' expression for each property
return Expression.OrElse(existingExpression, expressionToAdd);
}
}
Alternatively, take a look at the github project for NinjaNye.SearchExtensions as this has other options and has been refactored somewhat to allow other combinations
There is another, somewhat easier method that will accomplish this. ScottGu's blog details a dynamic linq library that I've found very helpful in the past. Essentially, it generates the query from a string you pass in. Here's a sample of the code you'd write:
Dim Northwind As New NorthwindDataContext
Dim query = Northwind.Products _
.Where("CategoryID=2 AND UnitPrice>3") _
.OrderBy("SupplierId")
Gridview1.DataSource = query
Gridview1.DataBind()
More info can be found at scottgu's blog here.
Either build an Expression<T> yourself, or look at a different route.
Assuming possibleTags is a collection of tags, you can make use of a closure and a join to find matches. This should find any songs with at least one tag in possibleTags:
allSongMatches = allSongMatches.Where(s => (select t from s.Tags
join tt from possibleTags
on t == tt
select t).Count() > 0)

Resources