So I got something like this:
var myObj = db.SomeObject
.Include("Tasks")
.SingleOrDefault(x => x.Id == someObjectId);
if (myObj != null)
{
myObj.Tasks = myObj.Tasks.OrderBy(x => x.Number).ToList();
}
Here I want to be able to put a condition (where) on my Include, for instance:
.where task.IsDeleted == false
Thusfar I have not found a solution.
I am aware of the fact that I could use the where together with where I order the tasks but this however, does not run on the database but in stead uses memory. I want it to run on the database.
Does anyone here know how I can do this?
If so, is there also a way to put the order by condition to the included list of tasks?
Something like this, returs you your original object with its child collection filtered and sorted.
SomeObject a = db.SomeObjects.Where(x => x.Id == someobjectid)
.Select(
x =>
new
{
someObject = x,
task = x.Tasks.Where(task => task.IsDeleted == false)
.OrderBy(task => whatever)
})
.Select(x => x.someObject).Single();
It's actually loosing the collection of activities in the last select so you can do this :
SomeObject a = db.SomeObjects.Where(x => x.Id == someobjectid)
.Select(
x =>
new
{
someObject = x,
task = x.Tasks.Where(task => task.IsDeleted == false)
.OrderBy(task => whatever)
});
return a.FirstOrDefault().someObject;
To do this is EF, you need to specify a projection with a Select clause.
Something like this will get just the data you want from the db:
var anonymous = db.SomeObject.Where( x => x.Id == someObjectId )
.Select( x => new
{
SomeObject = x,
Tasks = x.Tasks
.Where( o => !o.IsDeleted )
.OrderBy( o => ... )
}
)
.SingleOrDefault()
;
You will end up with an instance of the anonymous type , but you can easily fix that up on the client:
MyObject myObject = anonymous.SomeObject;
myObject.Tasks = anonymous.Tasks;
The simple answer is: You can't do that.
Why? Because you query SomeObject.
Each returned SomeObject contains all referenced data, because if it wouldn't it wouldn't represent the actual object in the database.
What about getting them separately:
var myObj = db.SomeObject
.SingleOrDefault(x => x.Id == someObjectId);
var tasks = db.SomeObject
.Where(x => x.Id == someObjectId)
.SelectMany(x => x.Tasks)
.Where(x => !x.Deleted);
Related
I need help with Linq Contains method. Here's the code below.
This code does work but outputs an empty sets.
var query = _context.RegistrationCodes.Select(x => x);
if (request.OperatorId != null && request.OperatorId != Guid.Empty)
{
var checkOperator = _context.Operators.Include(a => a.OperatorLevel).Include(a => a.City).Include("City.StateRegion.Country").FirstOrDefault(a => a.Id == request.OperatorId);
List<String> Cities = new List<String>();
if (checkOperator.OperatorLevel.Name == "City")
{
Cities = await _context.Cities
.Where(a => (checkOperator.CityId) == (a.Id))
.Select(a => a.Code)
.ToListAsync();
}
else if (checkOperator.OperatorLevel.Name == "Regional")
{
Cities = await _context.Cities
.Where(a => checkOperator.City.StateRegionId == a.StateRegionId)
.Select(a => a.Code)
.ToListAsync();
}
else if (checkOperator.OperatorLevel.Name == "National")
{
List<Guid> StateRegion = await _context.StateRegions
.Where(a => checkOperator.City.StateRegion.CountryId == a.CountryId)
.Select(a => a.Id)
.ToListAsync();
Cities = await _context.Cities
.Where(a => StateRegion.Contains(a.StateRegionId))
.Select(a => a.Code)
.ToListAsync();
}
var nullableStrings = Cities.Cast<String?>().ToList();
query = query.Where(a => nullableStrings.Contains(a.Code));
}
I need to compare nullableStrings to a.Code which is something like this, but does not work.
query = query.Where(a => a.Code.Contains(nullableStrings));
Error : Argument 1: cannot convert from 'System.Collections.Generic.List' to 'char'
I need a method that would replace
query = query.Where(a => nullableStrings.Contains(a.Code));
A help would be appreciated. Thanks.
Looking at the code, my guess is the requirement is to get a list of operators depending on the current (check) operator's level. I suspect the issue you are encountering is that some cities may not have a code. You then want to apply all found codes to another query that you are building up.
My guess is that the crux of the problem is that some cities might not have a code, hence the concern for null-able strings, while others might have multiple codes hacked into a single-code intended field. The solution there would typically be to remove any null values
Firstly, this line:
var checkOperator = _context.Operators.Include(a => a.OperatorLevel).Include(a => a.City).Include("City.StateRegion.Country").FirstOrDefault(a => a.Id == request.OperatorId);
can be simplified to:
var checkOperator = _context.Operators
.Select(a => new
{
Level = a.OperatorLevel.Name,
CityId = a.City.Id,
CityCode = a.City.Code,
StateRegionId = a.City.StateRegion.Id,
CountryId = a.City.StateRegion.Country.Id
}).FirstOrDefault(a => a.Id == request.OperatorId);
This builds a faster query, rather than fetching an entire operator object graph, just select the fields from the object graph that we need.
Now to handle the operator level. Here I don't recommend trying to force every scenario into a single pattern. The goal is just to apply a filter to the built query, so have the scenarios do just that:
select (checkOperator.Level)
{
case "City":
query = query.Where(a => a.Code == checkOperator.CityCode);
break;
case "Regional":
var cityCodes = await _context.Cities
.Where(a => a.Code != null && a.StateRegion.Id == checkOperator.StateRegionId)
.Select(a => a.Code)
.ToListAsync();
query = query.Where(a => cityCodes.Contains(a.Code));
break;
case "Country":
var cityCodes = await _context.Cities
.Where(a => a.Code != null && a.StateRegion.Country.Id == checkOperator.CountryId)
.Select(a => a.Code)
.ToListAsync();
query = query.Where(a => cityCodes.Contains(a.Code));
break;
}
Now based on the comments it sounds like your data with cities and codes is breaking proper normalization where Code was intended as a 1-to-1 but later hacked to handle one city having multiple codes, so multiple values were concatenated with hyphens. (I.e. ABC-DEF) If this represents 2 Codes for the city then you will need to handle this..
private List<string> splitCityCodes(List<string> cityCodes)
{
if (cityCodes == null) throw NullReferenceException(nameof(cityCodes));
if (!cityCodes.Any()) throw new ArgumentException("At least one city code is expected.");
var multiCodes = cityCodes.Where(x => x.Contains("-")).ToList();
if (!multiCodes.Any())
return cityCodes;
var results = new List<string>(cityCodes);
results.RemoveRange(multiCodes);
foreach(var multiCode in multiCodes)
{
var codes = multiCode.Split("-");
results.AddRange(codes);
}
return results.Distinct();
}
That can probably be optimized, but the gist is to take the city codes, look for hyphenated values and split them up, then return a distinct list to remove any duplicates.
List<string> cityCodes = new List<string>();
select (checkOperator.Level)
{
case "City":
cityCodes = splitCityCodes(new []{checkOperator.CityCode}.ToList());
if(cityCodes.Count == 1)
query = query.Where(a => a.Code == cityCodes[0]);
else
query = query.Where(a => cityCodes.Contains(a.Code));
break;
case "Regional":
cityCodes = await _context.Cities
.Where(a => a.Code != null && a.StateRegion.Id == checkOperator.StateRegionId)
.Select(a => a.Code)
.ToListAsync();
cityCodes = splitCityCodes(cityCodes);
query = query.Where(a => cityCodes.Contains(a.Code));
break;
case "Country":
cityCodes = await _context.Cities
.Where(a => a.Code != null && a.StateRegion.Country.Id == checkOperator.CountryId)
.Select(a => a.Code)
.ToListAsync();
cityCodes = splitCityCodes(cityCodes);
query = query.Where(a => cityCodes.Contains(a.Code));
break;
}
... and I suspect that would about do it for handling the possibility of a city code containing multiple values.
If your search argument is in the form "ABC-DEF" and you want that to match "ABC" OR "DEF" then it can be done, but it is not clear from your data setup how that scenario comes about.
Lets assume these codes are airport codes, and that a city that has multiple airports has the City.Code as a hyphenated list of the Airport codes, then if the checkOperator is in Australia, and their OperatorLevel is "National" then this might build the following nullableStrings:
var Cities = new List<string> {
"PER",
"ADE",
"DRW",
"MEL-AVV",
"SYD",
"BNE",
"OOL",
"HBA"
};
If then your query is a listing of AirPorts and you want to search the airports by these codes, specifically to match both "MEL" and "AVV" then you can use syntax like this
var nullableStrings = Cities.Cast<String?>().ToList();
query = query.Where(ap => nullableStrings.Any(n => n.Contains(ap.Code)));
But if you intend this to be translated to SQL via LINQ to Entities (so be executed server-side) then we can make this query more efficient buy normalizing the search args so we can do an exact match lookup:
var nullableStrings = Cities.Where(x => !String.IsNullOrWhiteSpace (x))
.SelectMany(x => x.Split('-'))
.Cast<String?>()
.ToList();
query = query.Where(ap => nullableStrings.Contains(ap.Code));
As this routine is called as part of a larger set and your checkOperator goes out of scope, you should try to reduce the fields that you retrieve from the database to the specific set that this query needs through a projection
Using .Select() to project out specific fields can help improve the overall efficiency of the database, not just each individual query. If the additional fields are minimal or natural surrogate keys, and your projections are common to other query scenarios then they can make good candidates for specific index optimizations.
Instead of loading SELECT * from all these table in this include list:
var checkOperator = _context.Operators.Include(a => a.OperatorLevel)
.Include(a => a.City.StateRegion.Country)
.FirstOrDefault(a => a.Id == request.OperatorId);
So instead of all the fields from OperatorLevel, City, StateRegion, Country we can load just the fields that our logic needs:
var checkOperator = _context.Operators.Where(o => o.Id == request.OperatorId)
.Select(o => new {
OperatorLevelName = o.OperatorLevel.Name,
o.CityId,
o.City.StateRegionId,
o.City.StateRegion.CountryId
})
.FirstOrDefault();
So many of the EF has poor performance opinions out there stem from a lot of poorly defined examples that proliferate the web. Eagerly loading is the same as executing SELECT * FROM ... for simple tables it's only a bandwidth and memory waste, but for complex tables that have computed columns or custom expressions there can be significant server CPU costs.
It cannot be overstated the improvements that you can experience if you use projections to expose only the specific sub-set of the data that you need, especially if you will not be attempting to modify the results of the query.
Be a good corporate citizen, only take what you need!
So lets put this back into your logic:
if (request.OperatorId != null && request.OperatorId != Guid.Empty)
{
var checkOperator = _context.Operators.Where(o => o.Id == request.OperatorId)
.Select(o => new {
OperatorLevelName = o.OperatorLevel.Name,
o.CityId,
o.City.StateRegionId,
o.City.StateRegion.CountryId
})
.FirstOrDefault();
IQueryable<City> cityQuery = null;
if (checkOperator.OperatorLevelName == "City")
cityQuery = _context.Cities
.Where(a => checkOperator.CityId == a.Id);
else if (checkOperator.OperatorLevelName == "Regional")
cityQuery = _context. Cities
.Where(a => checkOperator.StateRegionId == a.StateRegionId);
else if (checkOperator.OperatorLevelName == "National")
cityQuery = _context. Cities
.Where(c => c.StateRegion.CountryId == checkOperator.CountryId);
// TODO: is there any default filter when operator level is something else?
if (cityQuery != null)
{
var nullableStrings = cityQuery.Select(a => a.Code)
.ToList()
.Where(x => !String.IsNullOrWhiteSpace(x))
.SelectMany(x => x.Split('-'))
.Cast<String?>()
.ToList();
query = query.Where(ap => nullableStrings.Contains(ap.Code));
}
}
If you don't want or need to normalize the strings, then you can defer this whole expression without realizing the city query at all:
// No nullable string, but we can still remove missing Codes
cityQuery = cityQuery.Where(c => c.Code != null);
query = query.Where(ap => cityQuery.Any(c => c.Code.Contains(ap.Code)));
var listOfIds = new List<string>();
var allItems = IEnumerable<Info>();
foreach (var id in collectionIds)
{
listOfIds.AddRange(allItems
.Where(p => p.Data.FirstOrDefault(m => m.Key == "myId").Value == id)
.Select(x => x.Id));
}
I would like to avoid using AddRange but use only Add in this case and maybe use only FirstOrDefault in the place of where to avoid the last Select case.
Is this possible and if yes how?
Assuming your original code is giving you the correct data, specifically you are OK with:
Only concerned that the first item in p.Data contains a matching value and;
p.Data will always contains at least a single element.
Then this code will give you the same output:
var listOfIds = allItems
.Where(p => collectionIds.Contains(p.Data.First(m => m.Key == "myId").Value))
.ToList();
However, if you really do care that any value in p.Data matches, then this would be more appropriate:
var listOfIds = allItems
.Where(p => p.Data.Any(m => m.Key == "myId" &&
collectionIds.Contains(m.Value)))
.ToList();
How about this approach:
var listOfIds = new List<string>();
var allItems = IEnumerable<Info>();
var groupedAllItems = allItems.GroupBy(x => x.Data.FirstOrDefault(m => m.Key == "myId")?.Value ?? "MyIdNotFound");
//collectionIds should be of type HashSet<string> for the contains to be fast
listOfIds.AddRange(groupedAllItems.Where(x => collectionIds.Contains(x.Key)).SelectMany(x => x));
I want to retrieve a list of games from my database and the count the number of games that a specified team won and lost and put it into an object with a win and loss property. I was trying this but it doesn't seem to be correct.
var winLoss = _teamService.GetGames()
.Where(x => x.Result != "Tie")
.GroupBy(x => x.Result)
.Select(x => new
{
Wins = x.Count(a => a.Result == "Hello"),
Losses = x.Count(a => a.Result != "Hello")
});
The return type for this is an IQueryable whereas I want it to just be a single object with a Win and Loss property.
Doing a GroupBy on the Results would put all the Wins for the current team into one group and then separate groups for each team they lost to in their own separate group.
Using a LINQ query you're going to end up with a collection, but what you care about is essentially a list of keys and values. I believe this will supply you the information you're looking for:
var winLoss = _teamService.GetGames()
.Where(x => x.Result != "Tie").GroupBy(x => x.Result)
.ToDictionary(e => e.Key, e => e.Count());
int wins = 0;
int losses = 0;
winLoss.TryGetValue("WIN", out wins);
winLoss.TryGetValue("LOSS", out losses);
I just went with two simple count calls to the SQL database.
Wins = _teamService.GetGames().Count(x => x.Result == "Name");
Loses = _teamService.GetGames().IsNotTie().Count(x => x.Result != "Name");
It's not 100% what I wanted but to do it in one call involved more complicated LINQ and therefore more complicated SQL.
You need to count the wins and losses for each team:
var winLoss = _teamService.GetGames()
.GroupBy(x => x.Team)
.Where(gg => gg.Key == "Hello")
.Select(gg => new
{
Wins = gg.Count(g => g.Result == "Hello"),
Losses = gg.Count(g => g.Result != "Hello")
});
Add FirstOrDefault() at the end of your linq query so you will get only the first element:
var winLoss = _teamService.GetGames()
.Where(x => x.Result != "Tie").GroupBy(x => x.Result);
var win = winLoss.Select(x => x.Count(a => a.Result == "Hello")).FirstOrDefault();
var loose = winLoss.Select(x => x.Count(a => a.Result != "Hello")).FirstOrDefault();
I'm trying to figure out the BRE NRules and got some examples working but having a hard time to match a collection.
IEnumerable<Order> orders = null;
When()
.Match<IEnumerable<Order>>(o => o.Where(c => c.Cancelled).Count() >= 3)
.Collect<Order>(() => orders, o => o.Cancelled);
Then()
.Do(ctx => orders.ToList().ForEach(o => o.DoSomething()));
Basically what I want is if there are 3 orders cancelled then do some action. But I can't seem get a match on a collection, single variables do work.
The program:
var order3 = new Order(123458, customer, 2, 20.0);
var order4 = new Order(123459, customer, 1, 10.0);
var order5 = new Order(123460, customer, 1, 11.0);
order3.Cancelled = true;
order4.Cancelled = true;
order5.Cancelled = true;
session.Insert(order3);
session.Insert(order4);
session.Insert(order5);
session.Fire();
What am I doing wrong here?
With the 0.3.1 version of NRules, the following will activate the rule when you collected 3 or more canceled orders:
IEnumerable<Order> orders = null;
When()
.Collect<Order>(() => orders, o => o.Cancelled)
.Where(x => x.Count() >= 3);
Then()
.Do(ctx => orders.ToList().ForEach(o => o.DoSomething()));
Update:
For posterity, starting with version 0.4.x the right syntax is to use reactive LINQ. Matching a collection will look like this:
IEnumerable<Order> orders = null;
When()
.Query(() => orders, q => q
.Match<Order>(o => o.Cancelled)
.Collect()
.Where(x => x.Count() >= 3));
Then()
.Do(ctx => DoSomething(orders));
In your example, it should be pretty straightforward
IEnumerable<Order> orders = null;
When()
.Collect<Order>(() => orders, o => o.Cancelled == true);
Then()
.Do(ctx => orders.ToList().ForEach(o => o.DoSomething()));
I think the important part is the o.Cancelled alone without the == true. I know this sound wack, but somehow the property evaluation alone that is not an expression (i.e. just the property) is not well supported in NRules.
I ran into this problem and when I added the == true everything fell into place.
How to join Multiple Collection based on some expression like
IEnumerable<RawMsp> rawMsps = null;
IEnumerable<AsmMasterView> asmMasterViews = null;
IEnumerable<AsmInvestor> asmInvestors = null;
When()
.Match<AsmInvestor>(() => rawMsps)
.Match<AsmInvestor>(() => asmInvestor, i => i.InvestorId.ToString() == rawMsp.INVESTOR_CODE)
.Match<AsmMasterView>(() => asmMasterView, x => x.ChildAssumptionHistId == asmInvestor.AssumptionHistId);
Match is applicable individual object , Not sure apply equals of Enumerable Objects
I retrieve data from database with lambda like
var obj = DBContext.MyTable.Where(x => x.ID == 2).SingleOrDefault().MyColumn;
Actually , in MyTable , there is no ID with 2 .
So I got this message .
Object reference not set to an instance of an object.
How can I validate it properly ?
Just capture result of query into separate variable and check if any item found before accessing its properties:
var yourItem = DBContext.MyTable.Where(x => x.ID == 2).SingleOrDefault();
if (yourItem != null)
obj = yourItem.MyColumn;
BTW you can pass predicate into SingleOrDefault method:
var yourItem = DBContext.MyTable.SingleOrDefault(x => x.ID == 2);
Also you can select your property before applying SingleOrDefault
var obj = DBContext.MyTable.Where(x => x.ID == 2)
.Select(x => x.MyColumn)
.SingleOrDefault();
Another way to do this is using the "null-coalescing" ?? operator, which will use the second argument if the first argument is null.
var obj = (DBContext.MyTable.FirstOrDefault(x => x.ID == 2) ?? new MyTable()).MyColumn;
You have to check first if returned value is null and then access it:
var temp = DBContext.MyTable.Where(x => x.ID == 2).SingleOrDefault();
if (temp != null)
{
var obj = temp.MyColumn;
}
Not the most elegant way but I do not know any other.
I prefer DefaultIfEmpty() instead of .FirstOrDefault()
and by doing so we can avoid if check
for example
var obj = DBContext.MyTable.Where(x => x.ID == 2).DefaultIfEmpty().MyColumn;
or
var obj = DBContext.MyTable.Where(x => x.ID == 2).DefaultIfEmpty(string.Empty).MyColumn;
The concept of DefaultIfEmpty is simple: it replaces an empty collection with a collection of one default value.
Default value of int is 0. Thus, DefaultIfEmpty on a List yields a List with one zero element.
Hope it helps.
var yourItem = DBContext.MyTable.Where(x => x.ID == 2).ToArray()
if( yourItem.Length > 0)
//do stuff