Linq GroupJoin select query - c#

I have the following model (comments define fields within given object)
public class ServiceModel
{
public List<ShippingRequest> ShippingRequest { get; set; }
public QuotesResult QuotesResult { get; set; }
}
public class ShippingRequest
{
public Address Address { get; private set; } // AddressId
public List<ShippingPackage> ShippingPackages { get; private set; }
}
public class ShippingPackage
{
public Package Package { get; private set; } // PackageId
public List<ShippingItem> ShippingItems { get; private set; } // IsSkipped
}
public class QuotesResult
{
public List<Quote> Quotes { get; set; } // PackageId, Cost
}
Suppose I have the following input, I need to get a list of AddressId's and corresponding Quotes that refer to that address (via PackageId). Quotes are already populated at this point.
Quote.PackageId = Package.PackageId
INPUT:
Suppose I have following input with three ShippingRequests
Address1 = {Package1, Package2, Package3}
Address2 = {Package5, Package8}
Address3 = {Package11, Package12}
To get the all the quotes for a given address I need to Join PackageId of "Package" with PackageId of Quote. That way I will know that this Quote belongs to this Address.
I've tried this but i get an error:
var addrQuotes = ServiceModel.ShippingRequest
.GroupJoin(ServiceModel.QuotesResult.Quotes, c1 => c1.ShippingPackages
.SelectMany(y => y.Package.Id), c2 => c2.PackageId, (c1, c2) =>
new {
c1.Address.Id,
Quotes = c2.Select(e =>
{
e.Price = c1.ShippingPackages.Any(
x => x.ShippingItems.All(y => y.IsSkipped))
? 0
: e.Price + ExtraCost;
e.Provider = GetName(e.Code);
return e;
})
}).OrderBy(q => q.Id);
One caviar to this is that I also need to check ShippingItems(s) that go in a Package. If ALL the ShippingItems within a ShippingPackage have boolean flag "IsSkipped" set to true, the Quote's Price should be set to 0, otherwise add Extra cost to Quote.Price.
OUTPUT:
Address1 = [Quote1, Quote20, Quote21, Quote50, ...]
Address2 = [Quote3, Quote100...]
Address3 = [Quote5, Quote33, Quote12]
Any help greatly appreciated.

I didn't do entire job but got something to compile and run without errors. This should get you pointed in right direction.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
ServiceModel serviceModel = new ServiceModel()
{
ShippingRequest = new List<ShippingRequest>(){
new ShippingRequest() {
Address = "Address 1",
ShippingPackages = new List<ShippingPackage>() {
new ShippingPackage() { Package = "Package1"},
new ShippingPackage() { Package = "Package2"},
new ShippingPackage() { Package = "Package3"}
}
},
new ShippingRequest() {
Address = "Address 2",
ShippingPackages = new List<ShippingPackage>() {
new ShippingPackage() { Package = "Package5"},
new ShippingPackage() { Package = "Package8"},
}
},
new ShippingRequest() {
Address = "Address 3",
ShippingPackages = new List<ShippingPackage>() {
new ShippingPackage() { Package = "Package11"},
new ShippingPackage() { Package = "Package12"},
}
}
},
QuotesResult = new QuotesResult()
{
Quotes = new List<Quote>() {
new Quote() { Cost = 123, Id = "Package1"},
new Quote() { Cost = 123, Id = "Package2"},
new Quote() { Cost = 123, Id = "Package3"},
new Quote() { Cost = 123, Id = "Package11"},
new Quote() { Cost = 123, Id = "Package11"}
}
}
};
var addrQuotes = (from requests in serviceModel.ShippingRequest.Select(x => x.ShippingPackages.Select(y => new { address = x.Address, package = y})).SelectMany(z => z)
join quote in serviceModel.QuotesResult.Quotes
on requests.package.Package equals quote.Id
select new { quote = quote, package = requests }).ToList();
var results = addrQuotes.GroupBy(m => m.package.address)
.Select(n => new {
quotes = n.Select(c => c).Select(c1 => new {
address = c1.package.address,
quote = c1.quote
}).ToList()
}).ToList();
}
}
public class ServiceModel
{
public List<ShippingRequest> ShippingRequest { get; set; }
public QuotesResult QuotesResult { get; set; }
}
public class ShippingRequest
{
public string Address { get; set; } // AddressId
public List<ShippingPackage> ShippingPackages { get; set; }
}
public class ShippingPackage
{
public string Package { get; set; } // PackageId
public List<string> ShippingItems { get; set; } // IsSkipped
}
public class QuotesResult
{
public List<Quote> Quotes { get; set; } // PackageId, Cost
}
public class Quote
{
public string Id { get; set; }
public decimal Cost { get; set; }
}
}

Related

Creating dynamic range aggregations in Nest

I have a set of POCO facets with ranges, that I have received from a client. I need to convert these ultimately into an AggregationDictionary. I cannot figure out the syntax for creating a dynamic set of aggregations (possilbly of type RangeAggregationDescriptor) and need help with this.
My POCO objects are below:
public class TypedFacets
{
public string Name { get; set; }
public string Field { get; set; }
public IReadOnlyCollection<Range> RangeValues { get; set; } = new List<Range>();
public int Size { get; set; }
}
public class Range
{
public string Name { get; set; }
public double? From { get; set; }
public double? To { get; set; }
}
The Nest generation looks like below:
var facets = new List<TypedFacets>()
{
new TypedFacets()
{
Name = "potatoRange",
Field = "potatoRange",
RangeValues = new List<Range>()
{
new Range()
{
From = 0,
To = null,
Name = "chips"
},
new Range()
{
From = 1,
To = null,
Name = "crisps"
}
}
}
};
var aggregations = new AggregationContainerDescriptor<Template>();
facets.Where(f => f.RangeValues.Any()).ToList().ForEach(f =>
{
var rad = new RangeAggregationDescriptor<Template>();
f.RangeValues.ToList().ForEach(rangeValue =>
{
rad = rad.Ranges(rs => rs.From(rangeValue.From).To(rangeValue.To).Key(rangeValue.Name));
});
// this line doesn't work and needs to change
aggregations.Range(f.Name, r => r
.Field(f.Field).Ranges(rs => rad.Ranges));
});
return ((IAggregationContainer)aggregations).Aggregations;
I'm not sure how to fix the above. Any help would be appreciated.
I eventually found the solution for this. You can create the dynamic ranges as per below
private Func<AggregationRangeDescriptor, IAggregationRange>[] CreateRangeRanges(TypedFacets rangedAgg)
{
var rangeRanges = new List<Func<AggregationRangeDescriptor, IAggregationRange>>();
rangedAgg.RangeValues.ToList().ForEach(rangeValue =>
{
rangeRanges.Add(rs => rs.From(rangeValue.From).To(rangeValue.To).Key(rangeValue.Name));
});
return rangeRanges.ToArray();
}
And then assing them like below
facets.Where(f => f.RangeValues.Any()).ToList().ForEach(f =>
{
aggregations.Range(f.Name, r => r
.Field(f.Field).Ranges(CreateRangeRanges(f)));
});

Filter data from 2 lists with diferent models C#

I have this models
public class RoutingAttributeModel
{
public int Bus_No { get; set; }
public int Attribute_No { get; set; }
public string Attribute_Name { get; set; }
public string Status { get; set; }
public string Notes { get; set; }
}
public class AgentRoutingAttributeModel
{
public int Agent_No { get; set; }
public int Bus_No { get; set; }
public int Attribute_No { get; set; }
public string Attribute_Name { get; set; }
public string Status { get; set; }
}
List<RoutingAttributeModel> lstComplete = new List<RoutingAttributeModel>();
List<AgentRoutingAttributeModel> lstAssigned = new List<AgentRoutingAttributeModel>();
Filled this with some data
Is it possible to filter with Linq? I want to save in a new list the diferent content between lstComplete and lstAssigned
I was trying to join both lists but got stuck there
var results1 = from cl in lstComplete
join al in lstAssigned
on cl.Attribute_No equals al.Attribute_No
select cl;
you can use linq
as my understanding, you try to find linked by attribute_No records and have a list of not matching properties?
lstComplete.Add(new RoutingAttributeModel(){
Attribute_Name = "aaa",
Attribute_No = 1,
Bus_No = 1,
Notes = "",
Status = "status"
});
lstAssigned.Add(new AgentRoutingAttributeModel()
{
Attribute_No = 1,
Agent_No = 10,
Bus_No = 1,
Attribute_Name = "bbb",
Status = "status2"
});
var lst = lstComplete
.Join(lstAssigned,
complete => complete.Attribute_No,
assigned => assigned.Attribute_No,
(complete, assigned) => new { lstComplete = complete, lstAssigned = assigned })
.Select(s => new { s.lstComplete, s.lstAssigned})
.Where(w=>
w.lstAssigned.Attribute_Name != w.lstComplete.Attribute_Name
|| w.lstAssigned.Bus_No != w.lstComplete.Bus_No
)
.ToList()
.Dump();
so result would be
You could try the following query
var filteredList = lstComplete
.Where(x => !lstAssigned.Any(y => y.Attribute_No == x.Attribute_No));

Using where in LINQ select new statement for specific columns

I'm working on a class assignment and got a bit lost in LINQ.
I have 3 tables, 'oltandok' contains the data of persons, 'preferenciak' contains the preferred vaccine of that person with 3 columns:
an FK for table oltandok
a number indicating the order of preferences (1 is highest, 6 is lowest preferred)
an FK for another table containing the data on the vaccines called 'vakcinak'
I would like to display the data in a DataGridView the following way:
Personal data and the preferred vaccines in different columns:
Pref1 - Name of the vaccine where pref == 1
Pref2 - Name of the vaccine where pref == 2
etc.
This is where I am with my code, but I'm not sure how to select the preferences properly.
manu_rogz.DataSource = ( from x in context.oltandok
join y in context.preferencia on x.TAJ equals y.oltandok_FK
select new
{
TAJ = x.TAJ,
Nev = x.nev,
Szuletesnap = x.birthdate,
Pref1 = ???
Pref2 = ???
}
).ToList();
Because the preferenciak table contains multiple rows per person, you will need to perform some grouping.
Here is some very rough code which illustrates one way to do that.
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
var persons = new List<Person> { new Person { ID = 11, PersonName = "Alice" }, new Person { ID = 22, PersonName = "Bob" } };
var vaccines = new List<Vaccine> { new Vaccine(){ ID = 111, VaccineName= "Pfizer" }, new Vaccine(){ ID = 222, VaccineName = "Moderna" } };
var preferences = new List<VaccPref>
{
new VaccPref() { Person_FK = 11, Preference = 1, Vaccine_FK = 111 },
new VaccPref() { Person_FK = 11, Preference = 2, Vaccine_FK = 222 },
new VaccPref() { Person_FK = 22, Preference = 1, Vaccine_FK = 222 },
new VaccPref() { Person_FK = 22, Preference = 2, Vaccine_FK = 111 }
};
var prefsWithVaccNames = preferences.Join(vaccines, p => p.Vaccine_FK, v => v.ID, (pref, vaccine) => new Tuple<VaccPref, string>(pref, vaccine.VaccineName));
var groupedPrefs = prefsWithVaccNames.GroupBy(p => p.Item1.Person_FK);
var personPrefs = new List<PersonPrefs>();
foreach (var group in groupedPrefs)
{
personPrefs.Add(
new PersonPrefs()
{
Person_FK = group.Key,
Pref1 = group.Single(v => v.Item1.Preference == 1).Item2,
Pref2 = group.Single(v => v.Item1.Preference == 2).Item2,
});
}
var personPrefsWithPersonNames =
personPrefs.Join(
persons,
pp => pp.Person_FK,
p => p.ID,
(pp, p) => new NamedPersonPrefs() { Name = p.PersonName, Pref1 = pp.Pref1, Pref2 = pp.Pref2 }).ToArray();
}
}
class Person
{
public int ID { get; set; }
public string PersonName { get; set; }
}
class VaccPref
{
public int Person_FK { get; set; }
public int Preference { get; set; }
public int Vaccine_FK { get; set; }
}
class Vaccine
{
public int ID { get; set; }
public string VaccineName { get; set; }
}
class PersonPrefs
{
public int Person_FK { get; set; }
public string Pref1 { get; set; }
public string Pref2 { get; set; }
}
class NamedPersonPrefs
{
public string Name { get; set; }
public string Pref1 { get; set; }
public string Pref2 { get; set; }
}
This is a self-contained C# program which should produce a result similar to what you're after. You will of course need to adjust the class definitions (and change the table names) to suit your needs.
I've used LINQ's fluent syntax but you can use the SQL-like version if you prefer.

String StartsWith Order

I have a CSV File with Raw Data which I'm trying to match with multiple files, while sorting I need to match account codes to their accounts.
I'm using a List of Account and using StartsWith to try and match:
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var accounts = new List<Account> {
new Account {
Id = 9,
Code = "5-4",
Name = "Software",
},
new Account {
Id = 10,
Code = "5-4010",
Name = "Hardware"
}
};
var hardwareAccount = accounts.FirstOrDefault(x => "5-4010".StartsWith(x.Code));
Console.WriteLine(hardwareAccount.Name); // Prints Software - Should be Hardware
var softwareAccount = accounts.FirstOrDefault(x => "5-4020".StartsWith(x.Code));
Console.WriteLine(softwareAccount.Name); // Prints Software - Correct
}
}
public class Account {
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
They are obviously matching the first Account, is there a way to make it match in order?
Updated Solution:
Thanks #SirRufo
class Program
{
static void Main(string[] args)
{
var accounts = new List<Account>
{
new Account
{
Id = 9,
Code = "5-4",
Name = "Software",
},
new Account
{
Id = 10,
Code = "5-4010",
Name = "Hardware"
}
}.OrderBy(x => x.Code.Length);
var hardwareAccount = accounts.LastOrDefault(x => "5-4010".StartsWith(x.Code));
Console.WriteLine(hardwareAccount.Name);
var softwareAccount = accounts.LastOrDefault(x => "5-4020".StartsWith(x.Code));
Console.WriteLine(softwareAccount.Name);
Console.ReadKey();
}
}
public class Account
{
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
You have to order all matches by the code length
accounts
.Where(x => "5-4010".StartsWith(x.Code))
.OrderBy(x => x.Code.Length)
.LastOrDefault();

C# - Adding data to list inside list

How can I add the following data on the table into a list called Vehicles?
public class criterias
{
public double values { get; set; }
public double time { get; set; }
}
public class movChannels
{
public string name { get; set; }
public IList<criterias> criteria = new List<criterias>();
}
public class stepsList
{
public string steps { get; set; }
public IList<movChannels> stepChannelsCriteria = new List<movChannels>();
}
public class vehicles
{
public int vehID { get; set; }
public string vehDescription { get; set; }
public IList<stepsList> vehValCriteria = new List<stepsList>();
}
Now, how can I add the data that I have in the table shown into a list called Vehicles? I will create other vehicles later...
You had several bad decisions, some were design flaws and some were minor C# naming convention violations.
Couple of worth mentions flaws:
vehID should have been a string and not int (Example "XPT")
Movment has Name, Value and Time. It doesn't have a list of Values and Times.
Creation:
List<Vehicle> vehicles = new List<Vehicle>();
Vehicle vehicle = new Vehicle()
{
Id = "XPT",
Description = "Average Car",
Steps = new List<Step>()
{
new Step() {
Name = "move car",
Movements = new List<Movement>()
{
new Movement("engage 1st gear", 1, 1),
new Movement("reach 10kph", 10, 5),
new Movement("maintain 10kph", 10, 12),
}
},
new Step() {
Name = "stop car",
Movements = new List<Movement>()
{
new Movement("reach 0kph", 10, 4),
new Movement("put in neutral", 0, 1),
new Movement("turn off vehicle", 0, 0),
}
}
}
};
vehicles.Add(vehicle);
Entities:
public class Movement
{
public string Name { get; set; }
public double Values { get; private set; }
public double Time { get; private set; }
public Movement(string name, double values, double time)
{
Name = name;
Values = values;
Time = time;
}
}
public class Step
{
public string Name { get; set; }
public IList<Movement> Movements { get; set; }
}
public class Vehicle
{
public string Id { get; set; } // Should be changed to string
public string Description { get; set; }
public IList<Step> Steps { get; set; }
}
You should create your classes like the following:
public class criterias
{
public double values { get; set; }
public double time { get; set; }
}
public class movChannels
{
public movChannels
{
criteria = new List<criterias>();
}
public string name { get; set; }
public IList<criterias> criteria { get; set; }
}
public class stepsList
{
public stepsList
{
stepChannelsCriteria = new List<movChannels>();
}
public string steps { get; set; }
public IList<movChannels> stepChannelsCriteria { get; set; }
}
public class vehicles
{
public vehicles
{
vehValCriteria = new List<stepsList>();
}
public int vehID { get; set; }
public string vehDescription { get; set; }
public IList<stepsList> vehValCriteria { get; set; }
public movChannels movments { get; set; }
}
What about that?
public class VehiclesViewModel
{
public List<vehicles> Vehicles { get; private set; }
public void Initalize()
{
this.Vehicles = new List<vehicles>();
var vehicle = new vehicles
{
vehID = 1,
vehDescription = "firstDescription",
};
var stepsList = new stepsList
{
steps = "firstStep",
};
var movChannel = new movChannels
{
name = "firstChannel",
};
var criteria = new criterias
{
values = 0.5,
time = 0.5
};
movChannel.criteria.Add(criteria);
stepsList.stepChannelsCriteria.Add(movChannel);
vehicle.vehValCriteria.Add(stepsList);
this.Vehicles.Add(vehicle);
}
}
it seems in your table the VehicleId is of type string. Make sure your VehicleId property in Vehicle class also matches the same.
You can use the collection initializers to set the values of child objects like this way:
var data = new vehicles()
{
vehID = 1,
vehDescription = "Average Car",
vehValCriteria = new List<stepsList>()
{
new stepsList()
{
steps = "Move car",
stepChannelsCriteria = new List<movChannels>()
{
new movChannels()
{
name = "engage firstgear",
criteria = new List<criterias>()
{
new criterias()
{
values = 1,
time = 1
},
}
},
new movChannels()
{
name = "reach 10kph",
criteria = new List<criterias>()
{
new criterias()
{
values = 10,
time = 5
},
}
},
new movChannels()
{
name = "maintain 10kph",
criteria = new List<criterias>()
{
new criterias()
{
values = 10,
time = 12
},
}
}
}
},
new stepsList()
{
steps = "stop car",
stepChannelsCriteria = new List<movChannels>()
{
new movChannels()
{
name = "reach okph",
criteria = new List<criterias>()
{
new criterias()
{
values = 10,
time = 4
},
}
},
new movChannels()
{
name = "put in neutral",
criteria = new List<criterias>()
{
new criterias()
{
values = 0,
time = 1
},
}
},
new movChannels()
{
name = "turn off vehicle",
criteria = new List<criterias>()
{
new criterias()
{
values = 0,
time = 0
},
}
}
}
}
}
};
You can fill your list by moving from top to bottom, like
Create Criterias List then Create movChannel object and add that list
to Criterias object and so on
However if you want to avoid this way, there is another way. If you are using Linq To List then follow this
Get a simple flat object to a list object
var TableData = db.Tablename.Tolist();
Then fill your own object like this
Vehicles finalList = TableData.Select(a => new Vehicles()
{
vehID = a.Id,
vehDescription = a.des,
vehValCriteria = TableData.Where(b => b.StepslistId == a.StepslistId)
.Select(c => new StepsList()
{
steps = c.Steps,
stepChannelsCriteria = TableData.Where(d => d.channelId == c.channelId)
.select(e => new MovChannels()
{
name = e.name,
criteria = TableData.Where(f => f.criteriasId = e.criteriasId)
.Select(g => new Criterias()
{
values = g.Values,
time = g.Time
}).ToList()
}).ToList()
}).ToList()
}).ToList();
This is standard way to fill list within list

Categories