I have an order collection that looks like :
{
"_id" : ObjectId("5d732c3eb52e554e5ce19d96"),
"UpdatedDate" : ISODate("2019-09-07T04:04:14.223Z"),
"customerId" : ObjectId("5d579deac9406a6db80960b7"),
"items" : [
{
"product" : ObjectId("5d231c3fb52e554e5ce12d14"),
"quantity" : 2
},
{
"product" : ObjectId("5836bc0b291918eb42966320"),
"quantity" : 1
}
]
}
while product collection :
[
{
"_id" : ObjectId("5d231c3fb52e554e5ce12d14"),
"name" : "Coffee"
},
{
"_id" : ObjectId("5836bc0b291918eb42966320"),
"name" : "Cake"
}
]
How should I do the join through MongoDB .Net driver in order to get the product name and customer name projected into the result ?
Thank you
here's a full example of how to join 2 collections using the IMongoQueryable interface of the driver. below code uses MongoDB.Entities for brevity but joining part is exactly the same for the official driver. simply replace DB.Queryable<Order>() with collection.AsQueryable()
using MongoDB.Entities;
using MongoDB.Driver.Linq;
using System;
using System.Linq;
namespace StackOverflow
{
public class Program
{
public class Customer : Entity
{
public string Name { get; set; }
public Many<Order> Orders { get; set; }
public Customer() => this.InitOneToMany(() => Orders);
}
public class Order : Entity
{
public One<Customer> Customer { get; set; }
public OrderItem[] Items { get; set; }
}
public class Product : Entity
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class OrderItem //embedded entity inside order
{
public One<Product> Product { get; set; }
public int Quantity { get; set; }
}
private static void Main(string[] args)
{
//initialize db connection
new DB("cafe-db", "localhost", 27017);
//create some products
var cake = new Product { Name = "Cake", Price = 9.99m };
var coffee = new Product { Name = "Coffee", Price = 6.66m };
cake.Save();
coffee.Save();
//create a customer
var customer = new Customer { Name = "Mike Posner" };
customer.Save();
//create an order
var order = new Order
{
Customer = customer.ToReference(),
Items = (new[]
{
new OrderItem {
Product = cake.ToReference(),
Quantity = 2
},
new OrderItem {
Product = coffee.ToReference(),
Quantity = 1
}
})
};
order.Save();
//link the order to the customer
customer.Orders.Add(order);
// retrieve the data needed to generate an order summary/ invoice given just an order id
var orderID = order.ID;
var result = DB.Queryable<Order>()
.Where(o => o.ID == orderID)
.SelectMany(o => o.Items,
(o, i) => new { productID = i.Product.ID, productQTY = i.Quantity })
.Join(
DB.Queryable<Product>(), //foregign collection
x => x.productID, //local field
p => p.ID, //foreign field
(x, p) => new //projection
{
productName = p.Name,
productPrice = p.Price,
productQty = x.productQTY
})
.ToList();
var customerName = DB.Find<Order>()
.One(orderID)
.Customer.ToEntity()
.Name;
Console.WriteLine($"CUSTOMER: {customerName}");
foreach (var x in result)
{
Console.WriteLine($"PRODCUT: {x.productName} | QTY: {x.productQty} | PRICE: {x.productPrice}");
}
Console.Read();
}
}
}
Related
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.
Supposed that I have these classes
public class Subject
{
public int Id { get; set; }
public string Category { get; set; }
public string Type { get; set; }
}
public class Student
{
public int Id { get; set; }
public List<MySubject> MySubjects { get; set; }
}
public class MySubject
{
public int Id { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public string Schedule { get; set; }
public string RoomNumber { get; set; }
}
sample data
var subjects = new List<Subject>()
{
new Subject(){ Id = 1, Category = "Mathematics", Type = "Algebra" },
new Subject(){ Id = 2, Category = "Computer Science", Type = "Pascal" }
};
var student = new Student()
{ Id = 1, MySubjects = new List<MySubject>() {
new MySubject() {Id = 1, Category = "Mathematics", Type = "Algebra" },
new MySubject() {Id = 3, Category = "Mathematics", Type = "Trigonometry"},
}
};
//TODO: Update list here
student.MySubjects.ForEach(i => Console.WriteLine("{0}-{1}-{2}\t", i.Id, i.Category, i.Type));
the above line of code returns
1-Mathematics-Algebra
3-Mathematics-Trigonometry
which is incorrect. I need to return this
1-Mathematics-Algebra
2-Computer Science-Pascal
Basically I would like to modify and iterate the student.MySubjects and check its contents against subjects.
I would like to remove the subjects (3-Mathematics-Trigonometry) that are not present in the subjects and also ADD subjects that are missing (2-Computer Science-Pascal).
Can you suggest an efficient way to do this by searching/comparing using Category + Type?
Try like below.
// Remove those subjects which are not present in subjects list
student.MySubjects.RemoveAll(x => !subjects.Any(y => y.Category == x.Category && y.Type == x.Type));
// Retrieve list of subjects which are not added in students.MySubjects
var mySubjectsToAdd = subjects.Where(x => !student.MySubjects.Any(y => y.Category == x.Category && y.Type == x.Type))
.Select(x => new MySubject() {
Id = x.Id,
Category = x.Category,
Type = x.Type
}).ToList();
// If mySubjectsToAdd has any value then add it into student.MySubjects
if (mySubjectsToAdd.Any())
{
student.MySubjects.AddRange(mySubjectsToAdd);
}
student.MySubjects.ForEach(i => Console.WriteLine("{0}-{1}-{2}\t", i.Id, i.Category, i.Type));
// make an inner join based on mutual values to filter out wrong subjects.
var filteredList =
from mySubject in student.MySubjects
join subject in subjects
on new { mySubject.Category, mySubject.Type }
equals new { subject.Category, subject.Type }
select new MySubject { Id = mySubject.Id, Category = mySubject.Category, Type = mySubject.Type };
// make a left outer join to find absent subjects.
var absentList =
from subject in subjects
join mySubject in filteredList
on new { subject.Category, subject.Type }
equals new { mySubject.Category, mySubject.Type } into sm
from s in sm.DefaultIfEmpty()
where s == null
select new MySubject { Id = subject.Id, Category = subject.Category, Type = subject.Type };
student.MySubjects = filteredList.ToList();
student.MySubjects.AddRange(absentList.ToList());
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());
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)));
I'm approaching to document database and I'm little bit confused how to map documents relationship, in a situation as follow
public class Person
{
public Person()
{
}
public int Id { get; set; }
public string Name { get;set;}
public string Surname { get; set; }
public DateTime? BirthDate { get; set; }
}
public class Car
{
public Car() { }
public int Id { get; set; }
public string Name { get; set; }
public int PersonId { get; set;}
}
A person has one or more cars for example in this way I can query the db as follow
public Car Get(int id)
{
Car car = null;
using (IDocumentSession session = store.OpenSession())
{
car = session.Include<Car, Person>(x => x.PersonId).Load<Car>(id);
bool isLoaded = session.Advanced.IsLoaded("people/" + car.PersonId); // true!
}
return car;
}
and it's everything ok, the client makes just one request, but if I have a person and I want to show all his cars how can I query the db to do just a request?
I think tha I must modify the model putting a List<int> Cars in Person for reference his cars.
Note that I don't want to embed Cars in the Person document because Cars can be referenced from others document.
Thanks.
You can do it like this:
using (IDocumentSession session = store.OpenSession())
{
var carsForOne = session.Query<Car>()
.Include(x=>x.PersonId)
.Where(x=>x.PersonId == "people/1")
.ToList();
var person = session.Load<Person>("people/1");
}
This make just a single db request.
You can index the Cars collection and load all the cars from the index.
The index would look like this:
public class CarIndex : AbstractIndexCreationTask<Car, CarView>
{
public CarIndex()
{
Map = cars => from car in cars
select new
{
car.Id,
car.Name,
car.PersonId,
};
}
}
The CarView class is identical to the Car class, but can be changed to better fit the indexing needs.
public class CarView
{
public int Id { get; set; }
public string Name { get; set; }
public int PersonId { get; set; }
}
You'll need to execute the index before being able to use it:
new CarIndex().Execute(store);
Loading the cars for a certain person would look like this:
using (IDocumentSession session = store.OpenSession())
{
session.Store(new Person { Id = 1, Name = "A", Surname = "A" });
session.Store(new Car { Id = 1, Name = "A", PersonId = 1 });
session.Store(new Car { Id = 2, Name = "B", PersonId = 1 });
session.Store(new Car { Id = 3, Name = "C", PersonId = 2 });
session.SaveChanges();
}
WaitForIndexing(store); // from RavenTestBase
using (IDocumentSession session = store.OpenSession())
{
var resultsForId1 = session
.Query<CarView, CarIndex>()
.ProjectFromIndexFieldsInto<CarView>()
.Where(x => x.PersonId == 1);
Assert.Equal(2, resultsForId1.Count());
var resultsForId2 = session
.Query<CarView, CarIndex>()
.ProjectFromIndexFieldsInto<CarView>()
.Where(x => x.PersonId == 2);
Assert.Equal(1, resultsForId2.Count());
}
If you want to load the person and their cars in a single database request, use lazy loading:
var resultsForId1 = session
.Query<CarView, CarIndex>()
.ProjectFromIndexFieldsInto<CarView>()
.Where(x => x.PersonId == 1).Lazily();
var person = session.Advanced.Lazily.Load<Person>(1);
var personValue = person.Value;
var resultsValue = resultsForId1.Value;
Complete test (needs xunit and RavenDB.Tests.Helpers nugets):
using Raven.Client;
using Raven.Client.Indexes;
using Raven.Tests.Helpers;
using System;
using System.Linq;
using Xunit;
namespace SO41547501Answer
{
public class SO41547501 : RavenTestBase
{
[Fact]
public void SO41547501Test()
{
using (var server = GetNewServer())
using (var store = NewRemoteDocumentStore(ravenDbServer: server))
{
new CarIndex().Execute(store);
using (IDocumentSession session = store.OpenSession())
{
session.Store(new Person { Id = 1, Name = "A", Surname = "A" });
session.Store(new Car { Id = 1, Name = "A", PersonId = 1 });
session.Store(new Car { Id = 2, Name = "B", PersonId = 1 });
session.Store(new Car { Id = 3, Name = "C", PersonId = 2 });
session.SaveChanges();
}
WaitForAllRequestsToComplete(server);
WaitForIndexing(store);
using (IDocumentSession session = store.OpenSession())
{
var resultsForId1 = session
.Query<CarView, CarIndex>()
.ProjectFromIndexFieldsInto<CarView>()
.Where(x => x.PersonId == 1);
Assert.Equal(2, resultsForId1.Count());
var resultsForId2 = session
.Query<CarView, CarIndex>()
.ProjectFromIndexFieldsInto<CarView>()
.Where(x => x.PersonId == 2);
Assert.Equal(1, resultsForId2.Count());
}
using (IDocumentSession session = store.OpenSession())
{
server.Server.ResetNumberOfRequests();
var resultsForId1 = session
.Query<CarView, CarIndex>()
.ProjectFromIndexFieldsInto<CarView>()
.Where(x => x.PersonId == 1).Lazily();
var person = session.Advanced.Lazily.Load<Person>(1);
var personValue = person.Value;
var resultsValue = resultsForId1.Value;
Assert.Equal("A", personValue.Name); // person data loaded
Assert.Equal("A", resultsValue.First().Name); // cars data loaded
Assert.Equal(1, server.Server.NumberOfRequests); // only one request sent to the server
}
}
}
}
public class CarIndex : AbstractIndexCreationTask<Car, CarView>
{
public CarIndex()
{
Map = cars => from car in cars
select new
{
car.Id,
car.Name,
car.PersonId,
};
}
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public DateTime? BirthDate { get; set; }
}
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public int PersonId { get; set; }
}
public class CarView
{
public int Id { get; set; }
public string Name { get; set; }
public int PersonId { get; set; }
}
}