SelectMany/Select - Flatten a many-to-many relationship - c#

I have a main table called Task and each Task can be connected to zero or more Customers so I also have a Customer_Task many-to-many table. Basically what I want is to flatten the many-to-many relation to get the following result:
Task.Field1, ... ,Task.FieldN, Customer.Name, Customer.Number
So basically I just want the Task entity plus two more fields that should come from the many-to-many relation with the Customer entity.
I have only succesfully used Select/SelectMany once before, and that was a very simple case, so I figured I just ask the experts. Can anyone help me with this?
My guess would be something like this, but that does not work:
var tasks = _database.Task.SelectMany(t=>t.Customer_Task.SelectMany(c=>c.Customer.Name)).ToList();
Requested class structure (I have removed a lot of irrelevant information):
public partial class Customer
{
public Customer()
{
this.Customer_Task = new HashSet<Customer_Task>();
}
public int Id { get; set; }
public string Number { get; set; }
public string Name { get; set; }
}
public partial class Customer_Task
{
public int CustomerId { get; set; }
public int TaskId { get; set; }
public virtual Customer Customer { get; set; }
public virtual Task Task { get; set; }
}
public partial class Task
{
public Task()
{
this.Customer_Task = new HashSet<Customer_Task>();
}
public int Id { get; set; }
public int Number { get; set; }
public string Header { get; set; }
public string Description { get; set; }
public virtual ICollection<Customer_Task> Customer_Task { get; set; }
}
The exception I get is ArgumentException:
System.ArgumentException was unhandled
Message=DbExpressionBinding requires an input expression with a collection ResultType.

If I understand your requirements, then something like this should do:
var _database = new List<Task>
{
new Task
{
Customer_Task = new List<Customer_Task>
{
new Customer_Task
{
Customer = new Customer {Id = 1, Name = "a"},
Task = new Task {Id = 1, Number = 1}
},
new Customer_Task
{
Customer = new Customer {Id = 1, Name = "b"},
Task = new Task {Id = 1, Number = 1}
},
new Customer_Task
{
Customer = new Customer {Id = 2, Name = "a"},
Task = new Task {Id = 2, Number = 2}
},
new Customer_Task
{
Customer = new Customer {Id = 2, Name = "b"},
Task = new Task {Id = 2, Number = 2}
}
},
}
};
var tasks = _database.SelectMany(t => t.Customer_Task.Select(c => new { Task = c.Task.Id, Name =c.Customer.Name})).ToList();
foreach (var t in tasks)
{
Console.WriteLine(t.Task + " "+t.Name);
}
Console.ReadLine();
Where the result is:
1 a
1 b
2 a
2 b
So you can change your query from
var tasks = _database.Task.SelectMany(t=>t.Customer_Task.SelectMany(c=>c.Customer.Name)).ToList();
to
var tasks = _database.Task.SelectMany(t=>t.Customer_Task.Select(c=>c.Customer.Name)).ToList();

Related

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.

ASP.NET Core 3.0 MVC update model with list objects - add object

I have the following classes:
public class Invoice
{
[key]
public Guid InvoiceID { get; set; }
public string Name { get; set; }
public List<Invoice_item> Invoice_items { get; set; }
public Invoice()
{
InvoiceID = Guid.NewGuid();
Invoice_items = new List<Invoice_item>();
}
}
and:
public class Invoice_item
{
[Key]
public Guid Invoice_itemID { get; set; }
public string Description{ get; set; }
public int Price { get; set; }
public int qty { get; set; }
public Invoice_item()
{
Invoice_itemID = Guid.NewGuid();
}
}
It works fine. I can edit this model.
Controller: ...
Invoice invoice = await _context.Invoice.Include(i => i.Invoice_items).Where(x => x.InvoiceID == id).FirstOrDefaultAsync();
invoice.Name = "2020/122";
invoice.Invoice_items[0].Description = "Some name";
invoice.Invoice_items[0].Price = 1;
invoice.Invoice_items[0].qty = 1;
_context.Update(invoice);
await _context.SaveChangesAsync();
// WORK OK
But the problem is when I want to add an item to the list:
Controller: ...
Invoice invoice = await _context.Invoice.Include(i => i.Invoice_items).Where(x => x.InvoiceID == id).FirstOrDefaultAsync();
invoice.Name = "2020/122";
invoice.Invoice_items[0].Description = "Description 1";
invoice.Invoice_items[0].Price = 1;
invoice.Invoice_items[0].qty 1;
Invoice_item item = new Invoice_item()
{
Description = "Description 2",
Price = 2,
qty = 2
};
invoice.Invoice_items.Add(item); // PROBLEM
_context.Update(invoice);
await _context.SaveChangesAsync();
When editing the "Invoice" record, it is possible to edit all elements of the record and the elements of the "Invoice_item" list attached to it. The problem occurs when I want to add an item to the "Invoice_item" list attached to the "Invoice" record.
Please help.
You just need to add a Invoice_itemID in your item ,change your code like below.
Invoice_item item = new Invoice_item()
{
//add this line.
Invoice_itemID = new Guid(),
Description = "Description 2",
Price = 2,
qty = 2
};

From a one to many situation how do I get common items in Entity Framework

I just started with Entity Framework and I was having difficulty generating a query for the following situation.
I currently have two model classes Student and Sport. A student can play multiple sports. This is what my models look like
public class DbContext : DbContext
{
public DbContext(): base("name=DbContext")
{
}
public DbSet<Student> MyStudents { get; set; }
public DbSet<Sport> MySports { get; set; }
}
public class Student
{
public List<Sport> Actions { get; set; }
public string Name { get; set; }
}
public class Sport
{
public string SportName { get; set; }
}
My question is how do I get a list of all sports played by all the students? In short I am looking for common sports. So basically in the following case
Student A played Sports : Soccer , Tennis , Bowling
Student B played Sports : Soccer , Tennis ,
Student C played Sport : Tennis
Then only Tennis should be returned
Using the DB schema you've provided you can get the common sports checking sports of each student:
var sports = new[]
{
new Sport { SportName = "Tennis" },
new Sport { SportName = "Soccer" },
new Sport { SportName = "Bowling" }
};
var students = new[]
{
new Student
{
Name = "Student 1",
Actions = sports
},
new Student
{
Name = "Student 2",
Actions = new[] { sports[0], sports[1] }
},
new Student
{
Name = "Student 3",
Actions = new[] { sports[0] }
}
};
// Or
var sports = context.Sports;
var students = context.Students;
// In case students' sports are objects (as in this sample) you can use such a query:
var commonSports = sports.Where(sport =>
students.All(student => student.Actions.Contains(sport)));
// In case you're going to check the sports by name, this:
var commonSports = sports.Where(sport =>
students.All(student => student.Actions.Any(studSport =>
studSport.SportName == sport.SportName)));
Console.WriteLine($"Comon sports: {string.Join(",", commonSports.Select(i => i.SportName))}");
// To get only names of common sports:
var sportNames = commonSports.Select(i => i.SportName);
Console.Read();
If you use a relational database it would be easier and (as for me) more logical to implement many-to-many relationship as described here:
var context = new DbContext()
var unique = context.MyStudents.SelectMany(student => student.Actions.Select(sport => sport.SportName)).Distinct();
you just do this :
var commonSports = Context.Students.SelectMany(x=>x.Actions).GroupBy(x => x.SportName).Where(x=>x.Count()==items.Count(c=>c.Actions!=null)).Select(x=>x.Key).ToList();
I hope it been helpful .
To achieve this you might want to first set up some kind of model class, this isn't strictly necessary but might make things clearer for you:
public class StudentWithSports()
{
public string Name {get;set;}
public List<string> Sports {get;set;}
}
You can then populate your model from your context:
using(var context = new DbContext())
{
List<StudentWithSports> list = context
.Students
.Include(stu => stu.Actions)
.Select(stu => new StudenWithSports
{
Name = stu.Name,
Sports = stu.Actions.Select(act => act.SportName).ToList()
}).ToList();
}
If you don't want to create a model you could just do:
var list = context
.Students
.Include(stu => stu.Actions)
.Select(stu => new {
Name = stu.Name,
Sports = stu.Actions.Select(act => act.SportName).ToList()
}).ToList();
Which will give you a list of anonymous objects with the same properties.
The essence of my answer is the linq query, but I created a couple of classes to model your EF classes to show it works.
Student student1 = new Student
{
Name = "John",
Actions = new List<Sport>
{
new Sport { SportName = "Tennis" },
new Sport { SportName = "Soccer" },
new Sport { SportName = "Bowling" }
}
};
Student student2 = new Student
{
Name = "Mary",
Actions = new List<Sport>
{
new Sport { SportName = "Tennis" },
new Sport { SportName = "Soccer" }
}
};
Student student3 = new Student
{
Name = "Jane",
Actions = new List<Sport>
{
new Sport { SportName = "Tennis" }
}
};
IEnumerable<Student> students = new List<Student>
{
student1,
student2,
student3
};
var query = from s in students
select new
{
s.Name,
Sports = from sp in s.Actions
select sp.SportName
};
var result = query.ToList();
for (int i = 0; i < result.Count(); i++)
{
Console.Write(result[i].Name + " played sports: ");
foreach (var sport in result[i].Sports)
Console.Write(" " + sport);
Console.WriteLine();
}
Well your Db design isn't right because you have many to many relation between MyStudents and MySports tables. You have to add joint table between Students and Sports. You can call it StudentsSports
public class DbContext : DbContext
{
public DbContext(): base("name=DbContext")
{
}
public DbSet<Student> MyStudents { get; set; }
public DbSet<StudentsSport> StudentsSports { get; set; }
public DbSet<Sport> MySports { get; set; }
}
public class Student
{
public int ID { get; set; }
public List<StudentsSport> Actions { get; set; }
public string Name { get; set; }
}
public class Sport
{
public int ID { get; set; }
public string SportName { get; set; }
}
public class StudentsSport
{
public int ID { get; set; }
[ForeignKey(Student)]
public int StudentID { get; set; }
[ForeignKey(Sport)]
public int SportID { get; set; }
}
Then you can just do
var listOfActions = MyStudents.Select(s => s.Actions.Select(a => a.SportID));
var intersection = listOfActions
.Skip(1)
.Aggregate(
new HashSet<T>(listOfActions.First()),
(h, e) => { h.IntersectWith(e); return h; }
);
EDIT:
If you have students without sports then you will always get empty intersection list. If you don't want that then you will have to filter them
var listOfActions = MyStudents.Select(s => s.Actions.Select(a => a.SportID)).Where(c => c.Any());

EF Core Any in Any client side evaluation

I have list of OrderInfo which contains Id of Product.
I'd want to load all Orders (that has List<Many2Many> inside)
which has at least one Product with Id from my OrderInfo list
Basically it is nested Any Any
The code that I wrote below as proof of concept works fine, but the problem is that when I try to do the same on EF Core then it is being evaluated on client side.
How can I make it to evaluate properly?
var list_of_details = new List<OrderInfo> {...};
context
.Orders
.Where(o => o.OrdersProducts.Any(p => list_of_details.Any(d => d.ProductId == p.ProductId)));
public class OrderInfo
{
public Guid ProductId { get; set; }
(...)
}
public class Order
{
public List<Many2Many> OrdersProducts = new List<Many2Many>();
(...)
}
public class Product
{
public List<Many2Many> OrdersProducts = new List<Many2Many>();
(...)
}
public class Many2Many
{
public Order Order { get; set; }
public Guid OrderId { get; set; }
public Product Product { get; set; }
public Guid ProductId { get; set; }
}
Here's example in non-ef way, but ugly and just as proof of concept
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public class OrderInfo
{
public int ProductId { get; set; }
}
public class Order
{
public int Id;
public List<Many2Many> OrdersProducts = new List<Many2Many>();
}
public class Product
{
public int Id;
public List<Many2Many> OrdersProducts = new List<Many2Many>();
}
public class Many2Many
{
public Order Order { get; set; }
public int OrderId { get; set; }
public Product Product { get; set; }
public int ProductId { get; set; }
}
public static void Main()
{
Console.WriteLine("List of ids that have to be in 'Order' in order to qualify him");
var list_of_details = Enumerable.Range(0, 5).Select(x => new OrderInfo(){ ProductId = x}).ToList();
foreach (var item in list_of_details)
{
Console.Write(item.ProductId);
}
Console.WriteLine();
// setup
var orders = new List<Order>();
var order = new Order(){Id = 2};
var product = new Product()
{
Id = 3,
};
order.OrdersProducts.Add(new Many2Many()
{
Order = order,
OrderId = order.Id,
Product = product,
ProductId = product.Id
});
var order2 = new Order(){Id = 3};
var product2 = new Product()
{
Id = 4,
};
order2.OrdersProducts.Add(new Many2Many()
{
Order = order2,
OrderId = order2.Id,
Product = product2,
ProductId = product2.Id
});
var order3 = new Order(){Id = 1};
var product3 = new Product()
{
Id = 5,
};
order3.OrdersProducts.Add(new Many2Many()
{
Order = order3,
OrderId = order3.Id,
Product = product3,
ProductId = product3.Id
});
orders.Add(order);
orders.Add(order2);
orders.Add(order3);
Console.WriteLine();
// end setup
foreach (var ord in orders)
{
Console.WriteLine();
Console.WriteLine($"Order Id: {ord.Id}");
Console.Write('\t' + "Product Ids: ");
foreach (var prod in ord.OrdersProducts)
{
Console.Write(prod.ProductId);
}
}
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("found orders");
foreach (var item in orders.Where(o => o.OrdersProducts.Any(p => list_of_details.Any(d => d.ProductId == p.ProductId))))
{
Console.WriteLine(item.Id);
}
}
}
Output:
List of ids that have to be in 'Order' in order to qualify him
01234
Order Id: 2
Products Ids: 3
Order Id: 3
Products Ids: 4
Order Id: 1
Products Ids: 5
found orders
2
3
One method is to reduce the list to just the ids,
var list_of_details = new List<OrderInfo> {...};
var orderInfoIds = list_of_details.Select(d => d.ProductId);
context
.Orders
.Where(o => o.OrdersProducts.Any(p => orderInfoIds.Contains(p.ProductId)));

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