How to write LINQ query to sql IN - c#

I want to query my cosmosDB to get List of Documents of type ZonesDO && whose id are in UserPreferance.Zones My UserPreferance class is :
public class UserPreference
{
[JsonProperty("zones")]
public List<Zone> Zones { get; set; }
}
and Zone Class is:
public class Zone
{
[JsonProperty("id")]
public override Guid Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("category")]
public string Category { get; set; }
}
I am trying this query but not able to complete it.
var zones = DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri)
.Where(z => z.Type == typeof(ZoneDO).ToString() &&
*z.Id in user.UserPreference.Zones.ids*)// here I need the solution
.AsEnumerable().ToList();

Perform select on Zones.Id and then check with Contains to get desired result
var zones = DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri)
.Where(z => z.Type == typeof(ZoneDO).ToString() &&
user.UserPreference.Zones.Select(x => x.Id).Contains(z.Id))
.AsEnumerable().ToList();

You can try to use Contain method.
var zones = DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri)
.Where(z => z.Type == typeof(ZoneDO).ToString() &&
user.UserPreference.Zones.Select(x => x.Id).Contain(z.Id)).ToList();
Or you can use inline Where and Count
var zones = DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri)
.Where(
z => z.Type == typeof(ZoneDO).ToString() &&
user.UserPreference.Zones.Where(a=> a.Id == z.Id).Count() > 0
).ToList();

Another option may be to use the Any() operator:
var zones = DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri)
.Where(z => z.Type == typeof(ZoneDO).ToString() &&
user.UserPreference.Zones.Where(x => x.Id == z.Id).Any())
.AsEnumerable().ToList();

Linq providers usually understands basic methods on arrays/collections. Therefore you can use Contains method.
var zones = DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri)
.Where(z => z.Type == typeof(ZoneDO).ToString() &&
user.UserPreference.Zones.Contains(z.Id))
.AsEnumerable().ToList();

So apparently you have a user. This user has a UserPreference and this UserPreference has zero or more Zones. It seems that this user, and the Zones of this user's UserPreference is in local memory (not in a database. The Zones are IEnumerable and not IQueryable)
Although you didn't specify, it seems that DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri) returns an IQueryable<ZoneDO>
You want all ZoneDo that have an Id that is an Id of one of the Zones in the one and only UserPreference of your user.
In baby steps:
IEnumerable<Guid> zoneIds = user.Userpreference.Zones.Select(zone => zone.Id);
IQueryable<ZoneDO> allZoneDOs = DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri);
IEnumerable<ZoneDO> requestedZoneDOs = allZoneDOs
.Where(zoneDo => zoneIds.Contains(zoneDo.Id);
Until now no query is performed, nor any enumeration. To perform the query and return the fetched data as a List<ZoneDO> use .ToList();
List<ZoneDO> documents = requestedZoneDOs.ToList();
TODO: if desired put all statements into one big LINQ statement. I doubt whether this will improve performance. It surely will deteriorate readability and thus maintainability.
Did you notice that I did not do your type checking part. This was not needed, because function CreateDocumentQuery<ZoneDO> already returns a sequence f ZoneDo.
If not, and there are other types in the returned sequence, use OfType<ZoneDo> instead of checking on string representation of the returned objects:
IQueryable<ZoneDO> allZoneDOs = DbUtil.Client
.CreateDocumentQuery<ZoneDO>(CollectionUri)
.OfType<ZoneDo>();
AsEnumerable is not needed. IQueryable.ToList() will perform the query and convert the data to a list.
AsEnumerable is only needed if you need the data in local memory to continue with LINQ statements that cannot be performed as IQueryable, like LINQ statements where you call local functions, or LINQ statements that cannot be translated into SQL.
AsEnumerable will fetch the requested data per 'page'. So if you only need the first (few) elements, you don't fetch the complete table, but only the first (few) pages.

Related

Returning IEnumerable<> for a model in ASP.NET Web API

I am trying to get a list whose type is a model(called ItemDetail). However, I get this error: "Object reference not set to an instance of an object."
public class ItemDetail
{
public decimal sum { get; set; }
public string username { get; set; }
public string units { get; set; }
public string remarks { get; set; }
}
The API code is as follows:
[HttpGet]
[Route("api/items/details/{id}")]
public IEnumerable<ItemDetail> ItemDetails(int id)
{
using (ShopEntities entities = new ShopEntities())
{
var itemDetails = entities.vwItemDetails.ToList();
var userIds = from data in itemDetails
select data.Userid;
var item_Details = new List<ItemDetail> ();
foreach (int userId in userIds)
{
int totals = (int)entities.vwItemDetails
.Where(i => i.UserID == userId & i.item_id == id)
.Select(i => i.quantity)
.DefaultIfEmpty(0)
.Sum();
var itemRecord = entities.vwItemDetails.SingleOrDefault(i => i.item_id == id & i.Userid == userId);
item_Details.Append(new ItemDetail
{
username = itemRecord.username,
units = itemRecord.units,
remarks = itemRecord.Remarks,
sum = totals
});
}
return item_Details;
}
}
Thanks in advance for your help.
EDIT: The error occurs inside the foreach loop where I'm trying to append the new ItemDetail (item_Details.Append(new ItemDetail)
I think I see the problem...
var itemRecord = entities.vwItemDetails.SingleOrDefault(i => i.item_id == id & i.Userid == userId);
Your filtering for the SingleOrDefault() is using a bitwise AND operator instead of boolean one. The value of itemRecord as it's written right now is almost certainly always null. Try changing that line to this:
var itemRecord = entities.vwItemDetails.SingleOrDefault(i => i.item_id == id && i.Userid == userId);
EDIT:
I just realized you do the same thing in this LINQ area:
int totals = (int)entities.vwItemDetails
.Where(i => i.UserID == userId & i.item_id == id)
.Select(i => i.quantity)
.DefaultIfEmpty(0)
.Sum();
Again, totals is probably coming up as 0.
EDIT 2:
There is more wrong here than I original anticipated. I created a semi-complete working sample and you've got problems beyond the use of the bitwise operator.
As orhtej2 pointed out in the comments, you are setting yourself up to return null, but you don't check for it. So that is the immediate cause of your exception. You're probably iterating through a list of user IDs where some of the IDs aren't linked to the item ID you're working with. That will return a null because of SingleOrDefault.
The fix for that is to check if itemRecord is null, and if so, do continue; to move onto the next user ID. Or if you want to stop processing and return an error, do that. Either way the situation should be handled.
Related to that is another consequence of using SingleOrDefault. A friend of mine pointed out that if you end up with more than one result in your where clause there, you will get an exception as well. Unless you can guarantee that no single user ID will have more than one instance of a given item ID in that collection of item details, you need to use a different method. The most straightforward would be to use Where() instead, and handle the IEnumerable<> that it returns. You'll have another loop, but that's showbiz.

LINQ querying using an entity object not in the database - Error: LINQ to Entities does not recognize the method

I have an Entity Framework v5 model created from a database. The table Season has a corresponding entity called Season. I need to calculate the Season's minimum start date and maximum end date for each year for a Project_Group. I then need to be able to JOIN those yearly min/max Season values in other LINQ queries. To do so, I have created a SeasonLimits class in my Data Access Layer project. (A SeasonLimits table does not exist in the database.)
public partial class SeasonLimits : EntityObject
{
public int Year { get; set; }
public DateTime Min_Start_Date { get; set; }
public int Min_Start_Date_ID { get; set; }
public DateTime Max_End_Date { get; set; }
public int Max_End_Date_ID { get; set; }
public static IQueryable<SeasonLimits> QuerySeasonLimits(MyEntities context, int project_Group_ID)
{
return context
.Season
.Where(s => s.Locations.Project.Project_Group.Any(pg => pg.Project_Group_ID == project_Group_ID))
.GroupBy(x => x.Year)
.Select(sl => new SeasonLimits
{
Year = sl.Key,
Min_Start_Date = sl.Min(d => d.Start_Date),
Min_Start_Date_ID = sl.Min(d => d.Start_Date_ID),
Max_End_Date = sl.Max(d => d.End_Date),
Max_End_Date_ID = sl.Max(d => d.End_Date_ID)
});
}
}
// MVC Project
var seasonHoursByYear =
from d in context.AuxiliaryDateHours
from sl in SeasonLimits.QuerySeasonLimits(context, pg.Project_Group_ID)
where d.Date_ID >= sl.Min_Start_Date_ID
&& d.Date_ID < sl.Max_End_Date_ID
group d by new
{
d.Year
} into grp4
orderby grp4.Key.Year
select new
{
Year = grp4.Key.Year,
HoursInYear = grp4.Count()
};
In my MVC project, whenever I attempt to use the QuerySeasonLimits method in a LINQ query JOIN, I receive the message,
"LINQ to Entities does not recognize the method
'System.Linq.IQueryable`1[MyDAL.SeasonLimits]
QuerySeasonLimits(MyDAL.MyEntities, MyDAL.Project_Group)' method, and
this method cannot be translated into a store expression."
Is this error being generated because SeasonLimits is not an entity that exists in the database? If this can't be done this way, is there another way to reference the logic so that it can be used in other LINQ queries?
EF is trying to translate your query to SQL and as there is no direct mapping between your method and the generated SQL you're getting the error.
First option would be not to use the method and instead write the contents of the method directly in the original query (I'm not sure at the moment if this would work, as I don't have a VS running). In the case this would work, you'll most likely end up with a very complicated SQL with a poor performance.
So here comes the second option: don't be afraid to use multiple queries to get what you need. Sometimes it also makes sense to send a simpler query to the DB and continue with modifications (aggregation, selection, ...) in the C# code. The query gets translated to SQL everytime you try to enumerate over it or if you use one of the ToList, ToDictionary, ToArray, ToLookup methods or if you're using a First, FirstOrDefault, Single or SingleOrDefault calls (see the LINQ documentation for the specifics).
One possible example that could fix your query (but most likely is not the best solution) is to start your query with:
var seasonHoursByYear =
from d in context.AuxiliaryDateHours.ToList()
[...]
and continue with all the rest. This minor change has fundamental impact:
by calling ToList the DB will be immediately queried and the whole
AuxiliaryDateHours table will be loaded into the application (this will be a performance problem if the table has too many rows)
a second query will be generated when calling your QuerySeasonLimits method (you could/should also include a ToList call for that)
the rest of the seasonHoursByYear query: where, grouping, ... will happen in memory
There are a couple of other points that might be unrelated at this point.
I haven't really investigated the intent of your code - as this could lead to further optimizations - even total reworks that could bring you more gains in the end...
I eliminated the SeasonLimits object and the QuerySeasonLimits method, and wrote the contents of the method directly in the original query.
// MVC Project
var seasonLimits =
from s in context.Season
.Where(s => s.Locations.Project.Project_Group.Any(pg => pg.Project_Group_ID == Project_Group_ID))
group s by new
{
s.Year
} into grp
select new
{
grp.Key.Year,
Min_Start_Date = grp.Min(x => x.Start_Date),
Min_Start_Date_ID = grp.Min(x => x.Start_Date_ID),
Max_End_Date = grp.Max(x => x.End_Date),
Max_End_Date_ID = grp.Max(x => x.End_Date_ID)
};
var seasonHoursByYear =
from d in context.AuxiliaryDateHours
from sl in seasonLimits
where d.Date_ID >= sl.Min_Start_Date_ID
&& d.Date_ID < sl.Max_End_Date_ID
group d by new
{
d.Year
} into grp4
orderby grp4.Key.Year
select new
{
Year = grp4.Key.Year,
HoursInYear = grp4.Count()
};

Finding duplicate items then selecting one with closest date to currentDate

Note: Using Windows Mobile 6.5 Compact Framework.
I have a collection of the following object.
public class RFileModel
{
public List<string> RequiredFilesForR = new List<string>();
public string Date { get; set; }
public string RouteId { get; set; }
}
var ListOfRFileModels = new List<RFileModel>();
There is the chance that the same RouteId will be in multiple instances of RFileModel but with a different Date.
I'm trying to identify the duplicates and select only one, the one closest to the current date.
I have the following LINQ so far:
var query = ListOfRFileModels.GroupBy(route => route.RouteId)
.OrderBy(newGroup => newGroup.Key)
.Select(newGroup => newGroup).ToList();
But I don't think this is what I need, since it still returns all elements. I was expecting a list of non unique RouteId, that way I can iterate each non-unique id and compare dates to see which one to keep.
How can I accomplish this with LINQ or just plain ole foreach?
Your expression sorts groups, not group elements. Here is how to fix it:
DateTime currentDate = ...
var query = ListOfRFileModels
.GroupBy(route => route.RouteId)
.Select(g => g.OrderBy(fm => currentDate-fm.Date).First())
.ToList();
currentDate-fm.Date expression produces the difference between the current date and the date of the RFileModel object. The object with the smallest difference would end up in the first position of the ordered sequence. The call First() picks it up from the group to produce the final result.
Assuming you want ONLY the members with duplicates, take #dasblinkenlight's answer and add a Where clause: .Where(grp => grp.Count()>1):
DateTime currentDate = DateTime.Now;
var query = ListOfRFileModels
.GroupBy(route => route.RouteId)
.Where(grp => grp.Count()>1)
.Select(g => g.OrderBy(fm => currentDate-fm.Date).First())
.ToList();

MongoDb c# driver find item in array by field value

i found the way to check is the value contains in simple array :
var filter = Builders<Post>.Filter.AnyEq(x => x.Tags, "mongodb");
But how to find a complex item with many fields by a concrete field ?
I found the way to write it via the dot notation approach with BsonDocument builder, but how can i do it with typed lambda notations ?
upd
i think it some kind of
builderInst.AnyIn(p => p.ComplexCollection.Select(ml => ml.Id), mlIds)
but can't check right now, is anyone could help ?
There is ElemMatch
var filter = Builders<Post>.Filter.ElemMatch(x => x.Tags, x => x.Name == "test");
var res = await collection.Find(filter).ToListAsync()
Here's an example that returns a single complex item from an array (using MongoDB.Driver v2.5.0):
Simple Data Model
public class Zoo
{
public List<Animal> Animals { get; set; }
}
public class Animal
{
public string Name { get; set; }
}
Option 1 (Aggregation)
public Animal FindAnimalInZoo(string animalName)
{
var zooWithAnimalFilter = Builders<Zoo>.Filter
.ElemMatch(z => z.Animals, a => a.Name == animalName);
return _db.GetCollection<Zoo>("zoos").Aggregate()
.Match(zooWithAnimalFilter)
.Project<Animal>(
Builders<Zoo>.Projection.Expression<Animal>(z =>
z.Animals.FirstOrDefault(a => a.Name == animalName)))
.FirstOrDefault(); // or .ToList() to return multiple
}
Option 2 (Filter & Linq) This was about 5x slower for me
public Animal FindAnimalInZoo(string animalName)
{
// Same as above
var zooWithAnimalFilter = Builders<Zoo>.Filter
.ElemMatch(z => z.Animals, a => a.Name == animalName);
var zooWithAnimal = _db.GetCollection<Zoo>("zoos")
.Find(zooWithAnimalFilter)
.FirstOrDefault();
return zooWithAnimal.Animals.FirstOrDefault(a => a.Name == animalName);
}
You need the $elemMatch operator. You could use Builders<T>.Filter.ElemMatch or an Any expression:
Find(x => x.Tags.Any(t => t.Name == "test")).ToListAsync()
http://mongodb.github.io/mongo-csharp-driver/2.0/reference/driver/expressions/#elemmatch
As of the 2.4.2 release of the C# drivers, the IFindFluent interface can be used for querying on array element. ElemMatch cannot be used on an array of strings directly, whereas the find interface will work on either simple or complex types (e.g. 'Tags.Name') and is strongly typed.
FilterDefinitionBuilder<Post> tcBuilder = Builders<Post>.Filter;
FilterDefinition<Post> tcFilter = tcBuilder.Eq("Tags","mongodb") & tcBuilder.Eq("Tags","asp.net");
...
await myCollection.FindAsync(tcFilter);
Linq driver uses the aggregation framework, but for a query with no aggregation operators a find is faster.
Note that this has been broken in previous versions of the driver so the answer was not available at the time of original posting.

Sitecore: efficient way to use LINQ to compare against an ID

I have a LINQ query retrieving a list of , such as this:
var results = SearchContext.GetQueryable<Person>()
.Where(i => i.Enabled)
.Where(i => i.TemplateName == "Person")
.Random(6);
Each object of type "Person" has a "Location" field which is also a Glass mapped item, and hence has an ID; I would like to only select items whose Location has a specific ID.
How can I go about doing this in an efficient manner?
EDIT: I should probably clarify that I am unable to perform this comparison, efficiently or not. Because the GUID is an object and I cannot perform ToString in a LINQ query, I am unable to only pick the items whose Location item has a specific ID. Any clues on how this could be achieved?
EDIT 2: Adding the clause
.Where(i => i.Location.Id == this.Id)
Doesn't work, for... some reason, as I'm unable to debug what LINQ "sees". If I convert the other ID I'm comparing it against to string this way:
var theOtherID = this.Id.ToString("N");
Then it works with this LINQ line:
.Where(i => i["Location"].Contains(theOtherID))
I still have no idea why.
One approach is to include a separate property on Person that is ignored by Glass mapper, but can be used in searches:
[SitecoreIgnore]
[Sitecore.ContentSearch.IndexField("location")]
public Sitecore.Data.ID LocationID { get; set; }
You can use this in your search as follows:
Sitecore.Data.ID locationId = Sitecore.Data.ID.Parse(stringOrGuid);
var results = SearchContext.GetQueryable<Person>()
.Where(i => i.Enabled)
.Where(i => i.TemplateName == "Person")
.Where(i => i.LocationID == locationId)
.Random(6);
I think the efficiency of using multiple where clauses vs. conditionals is debatable. They will likely result in the same Lucene query being performed. I would prefer readability over optimization in this instance, but that's just me.
I can't think of a more efficient methods than using a simple where statement like in:
var results = SearchContext.GetQueryable<Person>()
.Where(i => i.Enabled && i.TemplateName == "Person" &&
i.Location != null && i.Location.Id == 1)
.Random(6);
Keep in mind that if you use the && statement instead of a where for each parameter, you reduce the complexity of the algorithm.
You could also use an Inverse Navigation Property on Location to a virtual ICollection<Person> and then be able to do this:
var results = SearchContext.GetQueryable<Location>()
.Where(i => i.Id == 1 && i.Persons.Where(p => p.Enabled && p.TemplateName == "Person").Any())
.Random(6);
The first option would still be the most efficient, because the second one uses sub-queries. But it is worth knowing you can do your search the other way.

Categories

Resources