LINQ data from view model using nested SQL [duplicate] - c#

This question already has answers here:
LEFT JOIN in LINQ to entities?
(7 answers)
Closed 4 years ago.
I can access the data using the following TSQL:
select Sweets.*, Qty
from Sweets
left join (select SweetID, Qty from carts where CartID = '7125794e-38f4-4ec3-b016-cd8393346669' ) t
on Sweets.SweetID = t.SweetID
But I am not sure of how to achieve the same results on my web application. Does anyone know how this could be achievable using LINQ?
So far i have:
var viewModel = new SweetViewModel
{
Sweet = db.Sweets.Where(s => db.Carts.Any(c => c.SweetID == s.SweetID))
};
Edit: Sorry I should of specified that I am using a View model of the 2 classes:
View model:
public class SweetViewModel
{
public IEnumerable<Sweet> Sweet { get; set; }
public IEnumerable<Cart> Cart { get; set; }
//public Cart OrderQty { get; set; }
}
public class Sweet
{
public int SweetID { get; set; }
public int CategoryID { get; set; }
public Category Category { get; set; }
public string SweetName { get; set; }
public bool Singular { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public virtual ICollection<Cart> Carts { get; set; }
}
public class Cart
{
[Key]
public int RecordID { get; set; }
public string CartID { get; set; }
public int SweetID { get; set; }
public int PemixID { get; set; }
public int Qty { get; set; }
public System.DateTime DateCreated { get; set; }
public Sweet Sweet { get; set; }
public PreMix PreMix { get; set; }
}

The following will work
from sweet in db.Sweets
join cart in db.Carts
on sweet.SweetID equals cart.SweetID into swct
from sc in swct.DefaultIfEmpty()
select new { Sweet = sweet, Qty = sweet.Key == sc.Key ? sc.Qty : 0 }

var res=(from sweet in db.Sweets
join cart in db.Carts.Select(x=>new{x.SweetID,x.Qty})
on sweet.SweetID equals cart.SweetID
into r11
from r1 in r11.DefaultIfEmpty()
select new {sweet,r1})
.Select(x=>new
{
Sweet=x.sweet,
Qty=x.r1?.Qty
})
.ToList();
This will get you the equivalent result to your sql query.
res will be List<a> where a is anonymous class and it's structure will be
{Sweet,Qty}.

You should be using the LINQ join function.
For my example, I have also used an altered version of your SQL query which I believe to be identical:
SELECT sweets.*, carts.Qty
FROM sweets LEFT JOIN carts ON sweets.SweetID = carts.SweetID
WHERE carts.CartID = '7125794e-38f4-4ec3-b016-cd8393346669'
Then I translated this new query to LINQ with the JOIN function.
var cartId = '7125794e-38f4-4ec3-b016-cd8393346669'
var query = db.Sweets // table in the "from" statement
.GroupJoin(db.Carts, // all carts for that sweet will be joined into [sweets -> Cart[]]
cart => cart.SweetID, // the first part of the "on" clause in an sql "join" statement
sweet => sweet.SweetID, // the second part of the "on" clause)
(sweet, carts) => new { Sweet = sweet, Carts = cart }) // create new compound object
.SelectMany(
sweetsCarts => sweetsCart.Carts.DefaultIfEmpty(), //show the sweet even if there is no cart
(x,y) => new { Sweet = x.Sweet, Cart = y });
.Where(sweetsCart => sweetsCart.Cart.CartID == cartId); // restrict your cartID
Basically, the Join function makes a list of compound objects that contain a Sweet object and a Cart object with each list entry, hence why you can access sweetsCart.Cart.CartID or sweetsCart.Sweets.SweetID.
The name on the left side of the => can be anything you want by the way, it's just an identifier for LINQ. I chose to call it "sweetsCart".
The GroupJoin with the SelectMany makes it possible to do a Left Join.

Related

One to Many LINQ Query - compiler error

Please see the LINQ query below:
var test = from s in db.Students
join c in db.Courses on s.courseid equals c.id into d
where s.name.StartsWith("Bert")
select new Student { id=s.id,name=s.name, Course = d.Select(x => x.name) };
A student links to one courses. Therefore the value of Course in the above should be a collection of courses. However, there is a compiler error (System.ArguementNullException). What am I doing wrong?
I am competent using SQL, however I am new to LINQ. Please see the SQL from the database below:
***Student Class***
public partial class Student
{
public int id { get; set; }
public string name { get; set; }
public Nullable<int> age { get; set; }
public Nullable<int> courseid { get; set; }
public virtual Course Course { get; set; }
}
Course Class
public partial class Course
{
public Course()
{
this.Students = new HashSet<Student>();
}
public int id { get; set; }
public string name { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
It seems you are just trying to get your query to also retrieve the Course navigation property for your students. As such, all you need to do is Include it:
var students = db.Students
.Include(s => s.Course)
.Where(s => s.Name.StartsWith("Bert");

Get data from nested table using entity framework

First of all this is my first question in the forum so please excuse me for any writing mistake.
I have 4 tables
attaching the table diagram
What I want is to get list of attraction name joining 'tblattraction' with 'tblattractionmaster' and count of the exact attraction for each place from 'tblattractions' using 'locationid' , I am using entity framework but don't know how to do that,
Disclaimer:
Each location can consist Multiple Places
Each Place can consist Multiple Attractions
What I have tried
return context.tblLocationMasters.Select(t => new details()
{
locationid = t.LocationId,
locationname = t.LocationName,
attractions =t.tblPlaces.SelectMany(a => a.tblAttractions).Select(b => new attractions(){
AttractionName=b.tblAttractionMaster.attractionname//(Not working),
TotalAttractions=0//???
}).ToList()
}).ToList();
I recreated your model (slightly different) using Code First. I came up with the following structure:
public class Location
{
public int LocationId { get; set; }
public string LocationName { get; set; }
public ICollection<Place> Places { get; set; }
}
public class Place
{
public int PlaceId { get; set; }
public string PlaceName { get; set; }
public int LocationId { get; set; }
public Location Location { get; set; }
public ICollection<AttractionPlace> Attractions { get; set; }
}
public class Attraction
{
public int AttractionId { get; set; }
public string AttractionName { get; set; }
}
public class AttractionPlace
{
public int AttractionPlaceId { get; set; }
public int PlaceId { get; set; }
public Place Place { get; set; }
public int AttractionId { get; set; }
public Attraction Attraction { get; set; }
}
Then, I could get the results in the way you needed with the following query:
var query = (from loc in db.Locations
join pla in db.Places.Include(x => x.Attractions) on loc.LocationId equals pla.LocationId
let count = pla.Attractions.Count()
select new
{
loc.LocationId,
loc.LocationName,
Attractions = pla.Attractions.Select(z => new
{
pla.PlaceName,
z.AttractionId,
z.Attraction.AttractionName
}),
AttractionsByPlaceCount = count
});
The query above returns data in this format
Just a side note though: I didn't went further to see the performance of this query. The SQL generated by Linq wasn't that bad, but you should consider analyzing it before actually using it in production.

ravendb index, query on child collection

I want to get a summary of all products, as only the latest OrderHistory is of interest where I want to use this. I have thousands of products with hundreds of OrderHistory each, but now I only want the product id and the latest OrderHistory for each product.
public class ProductSummary
{
public int ProductId { get; set; }
public OrderHistory LastOrderHistory { get; set; }
}
The OrderHistory is stored inside the Product document like this:
public class Product
{
public int Id { get; set; }
public int MarketGroupId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public List<OrderHistory> OrderHistory { get; set; }
}
And this is what OrderHistory looks like:
public class OrderHistory
{
public long OrderCount { get; set; }
public long Volume { get; set; }
public DateTime date { get; set; }
public double AvgPrice { get; set; }
}
Now I've tried a few approaches on the index and query to get this running, this is my latest try, but it returns no results.
public class LatestProductOrderHistory : AbstractIndexCreationTask<Product, ProductSummary>
{
public LatestProductOrderHistory()
{
Map = products => from p in products
from oh in p.OrderHistory
select new
{
ProductId = p.Id,
LastOrderHIstory = p.OrderHistory.OrderByDescending(o => o.date).Last()
};
StoreAllFields(FieldStorage.Yes);
}
}
And finally my query:
var results = session
.Query<ProductSummary, LatestProductOrderHistory>()
.ProjectFromIndexFieldsInto<ProductSummary>()
.Take(1024)
.Skip(start)
.ToList();
This combination gives me no results, I have never made indexes in ravendb before, so I'm sorry if this is a dumb question.
EDIT: Well, I'm not sure what I changed, but now I'm getting "Could not read value for property: Id"
EDIT2: The strange issue in previous edit was solved by restarting vs and ravendb, so current result is no result
As Ayende commented, you must add the Reduce function to your index as:
Reduce = results => from result in results
group result by result.Id into g
select new
{
Id = g.Key,
LastOrderHistory = g.SelectMany(x=> x.LastOrderHistory)
.OrderByDescending(o => o.Date).FirstOrDefault()
};
Just selecting wanted fields in Map function does not make your index Map/Reduce.
Then query your index as:
session.Query<ProductSummary, LatestProductOrderHistory>()

How to perform a join of two sets and populate navigation properties

Let's say I have two classes, Teapot and Cup:
public class Teapot
{
[Key]
[Column("Id", TypeName = "int")]
public int Id { get; set; }
public int MaterialId { get; set; }
public int ColorId { get; set; }
[ForeignKey("MaterialId")]
public virtual Material Material { get; set; }
[ForeignKey("ColorId")]
public virtual Color Color { get; set; }
}
and
public class Cup
{
[Key]
[Column("Id", TypeName = "int")]
public int Id { get; set; }
public int MaterialId { get; set; }
public int ColorId { get; set; }
[ForeignKey("MaterialId")]
public virtual Material Material { get; set; }
[ForeignKey("ColorId")]
public virtual Color Color { get; set; }
}
This is my ViewModel:
namespace ViewModel.Data
{
public class TeapotsWithInfo
{
public Model.Data.Teapot Teapot { get; set; }
public Model.Data.Cup Cup { get; set; }
}
}
For my ViewModel, I need to perform a join on MaterialId and ColorId and to have some navigation propeties like Teapot.Material.Manufacturer included. So, I've tried following queries:
This throws "LINQ to Entities only supports casting EDM primitive or enumeration types"
(from t in db.Teapots
join c in db.Cups
on new { t.MaterialId, t.ColorId } equals new { c.MaterialId, c.ColorId }
where t.Id == id
select new ViewModel.Data.TeapotsWithInfo { Teapot = t, Cup = c })
.Include(t => t.Material.Manufacturer).SingleOrDefault();
This seems to ignore the Include
(from t in db.Teapots.Include(t => t.Material.Manufacturer)
join c in db.Cups
on new { t.MaterialId, t.ColorId } equals new { c.MaterialId, c.ColorId }
where t.Id == id
select new ViewModel.Data.TeapotsWithInfo { Teapot = t, Cup = c }).SingleOrDefault();
Now I found some answers here that suggest to enumerate and then to perform another select, but I'd rather catch the data in one go.
You're having difficulties including navigation properties because queries with joins or projections ignore eager loading (see this).
Unfortunately, you will need to do what you seem to be avoiding: pull the data from the database first, then do an additional select to build your ViewModel (which will have the relationships loaded from the original query). The additional select should be a rather trivial operation, though, since you're not performing an additional load from the database, and the enumerable should only contain a single item.
(from t in db.Teapots.Include(t => t.Material.Manufacturer)
join c in db.Cups
on new { t.MaterialId, t.ColorId } equals new { c.MaterialId, c.ColorId }
where t.Id == id
select new
{
Teapot = t,
Cup = c,
Material = t.Material,
Manufacturer = t.Material.Manufacturer,
})
.AsEnumerable()
.Select(a => new ViewModel.Data.TeapotsWithInfo
{
Teapot = a.Teapot,
Cup = a.Cup
})
.SingleOrDefault();
Source
http://thedatafarm.com/data-access/use-projections-and-a-repository-to-fake-a-filtered-eager-load/

Linq Find Item by Primary Key and display Name(that lives in a different table)

I am displaying a record from my database. The record pulls data from other tables and uses a Int in the main table to represent the value so Item table has a Division equal to 1 and the Division table 1 = ALL . Now that i am displaying the records i am trying to turn the 1 into all. All the ID fields show the int. Which is what my code is telling it to do. But i am trying to display the name and when i do that i get a lot of red. It cannot find the name. CatagoryID should be CategoryName.
Hope that makes sense.
if (!IsPostBack)
{
string v = Request.QueryString["ContactID"];
int itemid;
int.TryParse(v, out itemid);
var customerInfo = GetCustomerInfo(itemid);
CONTACTID.Text = customerInfo[0].ContactID.ToString();
ContactTitle.Text = customerInfo[0].ContactTitlesID.ToString();
ContactNameB.Text = customerInfo[0].ContactName;
DropDownAddCategory.Text = customerInfo[0].CategoryID.ToString();
DDLAddDivision.Text = customerInfo[0].DivisionID.ToString();
ContactPhoneBox.Text = customerInfo[0].ContactOPhone;
ContactCellBox.Text = customerInfo[0].ContactCell;
ContactEmailBox.Text = customerInfo[0].ContactEmail;
CextB.Text = customerInfo[0].Ext;
}
private List<Solutions.Models.Contact> GetCustomerInfo(int itemid)
{
using (ItemContext context = new ItemContext())
{
return (from c in context.Contacts
where c.ContactID == itemid
select c).ToList();
}
}
This is the model
public class Contact
{
[ScaffoldColumn(false)]
public int ContactID { get; set; }
public System.DateTime ContactCreated { get; set; }
public string ContactName { get; set; }
public int? ContactTitlesID { get; set; }
public string ContactOPhone { get; set; }
public bool cApproved { get; set; }
public string User { get; set; }
public string ContactCell { get; set; }
public string ContactEmail { get; set; }
public int? DivisionID { get; set; }
public int? CategoryID { get; set; }
[StringLength(5)]
public string CExt { get; set; }
public virtual Division Division { get; set; }
public virtual Category Category { get; set; }
public virtual ContactTitle ContactTitle { get; set; }
public string Ext { get; set; }
}
With Entity Framework you can include related entities in query results:
return (from c in context.Contacts.Include("Catagory")
where c.ContactID == itemid
select c).ToList();
This will return contacts with Catagory objects: customerInfo.Catagory.CategoryName
BTW instead of returning list of contacts and selecting first one by index (thus possibly having index out of range exception), modify your method to return first contact (or default, if not found):
private Solutions.Models.Contact GetCustomerInfo(int itemid)
{
return (from c in context.Contacts.Include("Catagory")
where c.ContactID == itemid
select c).FirstOrDefault();
}
And use it this way:
var customerInfo = GetCustomerInfo(itemid);
if (customerInfo != null)
{
CONTACTID.Text = customerInfo.ContactID.ToString();
// etc
}
Are you using LINQ to SQL or Entity Framework? Check your model again and make sure the relationship between the two tables are setup correctly. The relationship may be missing from the model, and causing this problem.

Categories