c# Static Member inside ForEach - c#

I have the following:
blocks.Where(x => x.BlockName == NAVIGATION)
.ForEach(block => block.Data = db.Pages.Select(x => x.Name).ToList());
If where finds more than one block, how can I prevent the db.Pages.Select from performing the database hit for every block and simply reuse the List for the second and subsequent blocks.
Can you have static variables inside lambda functions?
UPDATE: I don't want to perform the fetch at all if the where finds zero blocks.
UPDATE: blocks is an in-memory List

var blocksResult = blocks.Where(x => x.BlockName == NAVIGATION);
if (blocksResult.Any())
{
var blockData = db.Pages.Select(x => x.Name).ToList();
blocksResult.ForEach(block => block.Data = blockData);
}
OR
List<string> blockData = null;
blocks.Where(x => x.BlockName == NAVIGATION).ForEach(block => block.Data = (blockData ?? (blockData = db.Pages.Select(x => x.Name).ToList())))

Related

EF Core Reuse subquery in different queries

I have a problem trying to reuse some subqueries. I have the following situation:
var rooms = dbContext.Rooms.Select(r => new
{
RoomId = r.Id,
Zones = r.Zones.Select(zr => zr.Zone),
Name = r.Name,
Levels = r.Levels.Select(lr => lr.Level),
IdealSetpoint = (double?)r.Group.Setpoints.First(sp => sp.ClimaticZoneId == dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId).Setpoint??int.MinValue,
Devices = r.Devices.Select(rd => rd.Device)
}).ToList();
var tagsTypes = rooms.Select(r => r.Devices.Select(d => GetSetpointTagTypeId(d.DeviceTypeId))).ToList().SelectMany(x => x).Distinct().ToList();
predicate = predicate.And(pv => tagsTypes.Contains(pv.TagSettings.TagTypeId) &&
pv.ClimaticZoneId == dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId);
var setpoints = valuesSubquery.Include(t=>t.TagSettings).Where(predicate).ToList();
This works fine, and generates the exact queries as wanted. The problem is that I want to have this subquery dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId to be taken from a method and not repeat it every time I need it.
I've tested it with the database, where I have values in the corresponding tables, and I've tested the query with the database without any data in the corresponding tables. It works fine with no problems or exceptions.
But when I try to extract the repeating subquery in a separate method and execute it against empty database tables (no data) the .First() statement throws error. Here is the code:
protected long GetClimaticZoneId()
{
return dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId;
}
and the query generation:
var rooms = dbContext.Rooms.Select(r => new
{
RoomId = r.Id,
Zones = r.Zones.Select(zr => zr.Zone),
Name = r.Name,
Levels = r.Levels.Select(lr => lr.Level),
IdealSetpoint = (double?)r.Group.Setpoints.First(sp => sp.ClimaticZoneId == GetClimaticZoneId()).Setpoint??int.MinValue,
Devices = r.Devices.Select(rd => rd.Device)
}).ToList();
var tagsTypes = rooms.Select(r => r.Devices.Select(d => GetSetpointTagTypeId(d.DeviceTypeId))).ToList().SelectMany(x => x).Distinct().ToList();
predicate = predicate.And(pv => tagsTypes.Contains(pv.TagSettings.TagTypeId) &&
pv.ClimaticZoneId == GetClimaticZoneId());
var setpoints = valuesSubquery.Include(t=>t.TagSettings).Where(predicate).ToList();
After execution I get InvalidOperationException "Sequence do not contain any elements" exception in the GetClimaticZoneId method:
I'm sure that I'm not doing something right.
Please help!
Regards,
Julian
As #Gert Arnold suggested, I used the GetClimaticZoneId() method to make a separate call to the database, get the Id and use it in the other queries. I gust modified the query to not generate exception when there is no data in the corresponding table:
protected long GetClimaticZoneId()
{
return dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).FirstOrDefault()?.ClimaticZoneId??0;
}

Unexpected behavior of the `Where` clause

I have the following Linq statement which is used to retrieve the list of all rows in the table which satisfy a particular condition.
var set = db.TcSet
.Where(x => x.SetName.Equals(original.SetName))
.AsEnumerable()
.Where(x => x.SetName == original.SetName))
This particular statement is used to edit all entities in the table with a particular name. So the initial name of the property is retrieved in the original and checked for all other entries in the database which matches the condition.
Current Output
If I have two entries with the name Play123 and Edit one of the entry the output of the where query only contains one element.
If there is an entry Play123 and Play1234, and I try to edit Play123 to Play1234 , the output of the where query contains two elements : Play123 and Play1234 .
What am I missing that results in this unexpected behavior.
Update
var original = db.TcSet.Find(tcSet.TcSetID);
foreach (var set in db.TcSet
.Where(x => x.SetName.Equals(original.SetName))
.AsEnumerable()
.Where(x => x.SetName == original.SetName)))
{
if (set.SetName == original.SetName) // This was added again due to the unexpected behavior
{
set.ModifiedBy = User.Identity.Name;
set.ModifiedOn = DateTime.Now;
set.PhysicalUnit = tcSet.PhysicalUnit;
db.Entry(set).State = EntityState.Modified;
db.Entry(set).Property(x => x.CreatedBy).IsModified = false;
db.Entry(set).Property(x => x.CreatedOn).IsModified = false;
db.Entry(set).Property(x => x.TechnicalCharacteristicID).IsModified = false;
}
}
Now I have solved the problem by iterating through all the elements in the table which I know isn't the best practice.
Working code
var editlist = db.TcSet.Where(x => x.SetName == original.SetName).AsEnumerable().Where(x=>x.SetName==original.SetName).ToList();
foreach(var set in editlist)
// do save
When I removed the if condition it worked.
You want to do a case-insensitive check in your first .Where() clause, if you are to treat abc and ABC differently.
var set = db.TcSet
.Where(x => x.SetName.Equals(original.SetName, StringComparison.OrdinalIgnoreCase));
I would also suggest to cache the results before iterating in the foreach, as you are changing the source list while iterating through it which is never a good idea.
var original = db.TcSet.Find(tcSet.TcSetID);
var sets = db.TcSet
.Where(x => x.SetName.Equals(original.SetName, StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var set in sets)
{
set.ModifiedBy = User.Identity.Name;
set.ModifiedOn = DateTime.Now;
set.PhysicalUnit = tcSet.PhysicalUnit;
db.Entry(set).State = EntityState.Modified;
db.Entry(set).Property(x => x.CreatedBy).IsModified = false;
db.Entry(set).Property(x => x.CreatedOn).IsModified = false;
db.Entry(set).Property(x => x.TechnicalCharacteristicID).IsModified = false;
}

LINQ: how to get an intersection of two sets of ints?

There must be a way to compare two sets of results while staying in LINQ. Here's my existing code that uses a HashSet to do the comparison after two separate queries:
public static void AssertDealershipsShareTransactionGatewayCredentialIds(long DealershipLocationId1,
long DealershipLocationId2)
{
using (var sqlDatabase = new SqlDatabaseConnection())
{
var DealershipCredentials1 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId1)
.Select(x => x.TransactionGatewayCredentialId);
var DealershipCredentials2 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId2)
.Select(x => x.TransactionGatewayCredentialId);
var doSetsOfCredentialsMatch = new HashSet<int>(DealershipCredentials1).SetEquals(DealershipCredentials2);
Assert.IsTrue(doSetsOfCredentialsMatch,
"The sets of TransactionGatewayCredentialIds belonging to each Dealership did not match");
}
}
Ideas? Thanks.
Easy answer (This will make 1, possibly 2 database calls, both of which only return a boolean):
if (list1.Except(list2).Any() || list2.Except(list1).Any())
{
... They did not match ...
}
Better answer (This will make 1 database call returning a boolean):
var DealershipCredentials1 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId1)
.Select(x => x.TransactionGatewayCredentialId);
var DealershipCredentials2 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId2)
.Select(x => x.TransactionGatewayCredentialId);
if (DealershipCredentials1.GroupJoin(DealershipCredential2,a=>a,b=>b,(a,b)=>!b.Any())
.Union(
DealershipCredentials2.GroupJoin(DealershipCredential1,a=>a,b=>b,(a,b)=>!b.Any())
).Any(a=>a))
{
... They did not match ...
}
The second method works by unioning a left outer join that returns a boolean indicating if any unmatching records were found with a right outer join that does the same. I haven't tested it, but in theory, it should return a simple boolean from the database.
Another approach, which is essentially the same as the first, but wrapped in a single LINQ, so it will always only make 1 database call:
if (list1.Except(list2).Union(list2.Except(list1)).Any())
{
}
And another approach:
var common=list1.Intersect(list2);
if (list1.Except(common).Union(list2.Except(common)).Any()) {}

About LINQ and LAMBDA Chain usage

I'm trying to understand how Linq chain or lambda chain is working but I couldn't get it. I wrote a sample code below. I can actually run the code with long way like that but I want to learn the other way. I used AWS SDK for the code. I'm trying to do single line code that is managed exactly the same thing while using lambda chain. There is a "_dict" variable for what I want to get from lambda Chain. I must use multiple "GroupBy"s and Select commands for "reservedList" variable but how can I do that?
Dictionary<string, Dictionary<string, int>> _dict = new Dictionary<string, Dictionary<string, int>>();
//Dict<AvailabilityZone, Dict<InstanceType, Count>>
var reservedList = _ec2Client.DescribeReservedInstances(new DescribeReservedInstancesRequest { }).ReservedInstances
.GroupBy(q => q.AvailabilityZone);
foreach (var _availabilityZoneItems in reservedList)
{
if (!_dict.ContainsKey(_availabilityZoneItems.Key))
_dict.Add(_availabilityZoneItems.Key, new Dictionary<string, int>());
var typeGroup = _availabilityZoneItems.GroupBy(q => q.InstanceType);
foreach (var _type in typeGroup)
{
var selectionCount = _type.Where(q => q.State == ReservedInstanceState.Active).Sum(q=>q.InstanceCount);
_dict[_availabilityZoneItems.Key].Add(_type.Key, selectionCount);
}
}
Trying to do something like
var reservedList = var reservedList = _ec2Client.DescribeReservedInstances(new DescribeReservedInstancesRequest { }).ReservedInstances
.GroupBy(q => q.AvailabilityZone)...
.GroupBy(q => q.InstanceType)
....Select...Where..Count...
You are probably looking for the Enumerable.SelectMany() This will flatten lists inside a list..
Something like: (untested/incomplete yet)
var typeGroup = _ec2Client
.DescribeReservedInstances(new DescribeReservedInstancesRequest {})
.ReservedInstances.GroupBy(q => q.AvailabilityZone)
.SelectMany(availabilityZone =>
availabilityZone
.GroupBy(q => q.InstanceType)
.Select(type => new
{
AvailabilityZone = availabilityZone,
Type = type
}));
foreach (var _type in typeGroup)
{
var selectionCount = _type.Type.Where(q => q.State == ReservedInstanceState.Active).Count();
_dict[_type.AvailabilityZoneItems.Key][_type.Type.Key] = selectionCount;
}
Maybe this points you into the right direction. I normally would use Linq for this..
I would try something along these lines
_ec2Client.DescribeReservedInstances(new DescribeReservedInstancesRequest()).ReservedInstances
.GroupBy(q => q.AvailabilityZone)
.ToList()
.ForEach (_availabilityZoneItems => {
_availabilityZoneItems.GroupBy(q => q.InstanceType)
.ToList()
.ForEach(_type => {
_dict[_availabilityZoneItems.Key][_type.Key] = _type.Where(q => q.State == ReservedInstanceState.Active).Count();
});
});
This is untested

FindAll conditions with different relevance

About the homework:
There are casters(witch(0)/fairy(1)) and they have spellpower(int). I stored them in a list.
I'm to find the best of both types. (There can be multiple casters with the same spellpower)
I've come up with this code, but there is a problem. If the caster with the most spellpower is a 1, then the first FindAll won't return anything, because it tries to find the caster with type 0 AND with the most spellpower. How can I get a list containing type 0 caster(s) with the most spellpower, if the caster with the most overall spellpower is type 1?
private List<Caster> BestCasters()
{
List<Caster> temp = new List<Caster>();
temp = casters.FindAll(x => x.SpellPower == casters.Max(y => y.SpellPower) && (x.TypeOfCaster == 0));
temp.AddRange(casters.FindAll(x => x.SpellPower == casters.Max(y => y.SpellPower) && (x.TypeOfCaster == 1)));
temp.OrderBy(x => x.TypeOfCaster).ThenBy(y => y.CasterName);
return temp;
}
The LINQ GroupBy behavior is perfect for this:
var strongest_casters = casters.GroupBy(c => c.TypeOfCaster)
.Select(grp => grp.OrderByDescending(x => x.SpellPower)
.First()
);
Or to return more than one of each type:
var strongest_casters = casters.GroupBy(c => c.TypeOfCaster)
.SelectMany(grp => grp.Where(y.SpellPower == grp.Max(x => x.SpellPower))
);
private List<Caster> BestCasters()
{
var witches = casters.Where(x => x.TypeOfCaster == 0).ToList();
var fairies = casters.Where(x => x.TypeOfCaster == 1).ToList();
int witchesMax = witches.Max(x => x.SpellPower);
int fairiesMax = fairies.Max(x => x.SpellPower);
var temp = witches.Where(x => x.SpellPower == witchesMax).ToList();
temp.AddRange(fairies.Where(x => x.SpellPower == fairiesMax));
return temp.OrderBy(x => x.TypeOfCaster).ThenBy(y => y.CasterName).ToList();
}
If you have to use FindAll like this you should invoke the Max on a subset only containing the casters of the right kind. Of course it would make more sense to split the initial list first and then fetch the strongest caster of each kind.
Since you did not tell what exactly you have to do I can only hope that you are allowed to split :-)

Categories

Resources