Query:
var result = await this.Context.ShopProducts
.Include(prd => prd.Category)
.ThenInclude(cat => cat.Culture)
.Include(prd => prd.InfoItems)
.SingleOrDefaultAsync(prd => prd.Id.Equals(id) && prd.CategoryId.Equals(culture));
Edit: Updated the entities and query to reflect the new design and added a sql query
Entities:
Product:
[Table("ShopProduct")]
public class Product : ShopBase
{
public bool Active { get; set; } = true;
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public ICollection<ProductInfo> InfoItems { get; set; } = new HashSet<ProductInfo>();
}
ProductInfo:
[Table("ShopProductInfo")]
public class ProductInfo : ShopBase
{
public int ProductId { get; set; }
public int CultureId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Sum { get; set; }
public ICollection<GraphicItem> GraphicItems { get; set; }
}
What I want is to only select the ProductInfo objects with CultureId that equals the Category CultureId property. When selecting I provide the product Id and Category Id.
I want to replicate something like this sql query:
DECLARE #prdId INT,
#catId INT
SET #prdId = 1
SET #catId = 1
SELECT prd.*,
info.*,
cat.*
FROM ShopProduct prd,
ShopProductInfo info,
ShopCategory cat
WHERE prd.Id = #prdId
AND prd.CategoryId = cat.Id
AND cat.Id = #catId
AND cat.CultureId = info.CultureId
this error mean, linq query return some value otherwise give exception error
Related
I'm fairly new to EF Core, so please forgive me if I'm missing something easy here or went about this the wrong way. I have the following models (I have removed irrelevant relationships / data for brevity):
public class SaleOrder
{
public int SaleOrderId { get; set; }
public ICollection<SaleOrderRule> Rules { get; set; }
public ICollection<Carton> ScannedCartons { get; set; }
}
public class SaleOrderRule
{
public int SaleOrderRuleId { get; set; }
public int SaleOrderId { get; set; }
public SaleOrder SaleOrder { get; set; }
public int ProductCodeId { get; set; }
public ProductCode ProductCode { get; set; }
public int Quantity { get; set; }
}
public class ProductCode
{
public int ProductCodeId { get; set; }
public string Value { get; set; }
}
public class Carton
{
public int CartonId { get; set; }
public string BoxID { get; set; }
public int ProductCodeId { get; set; }
public ProductCode ProductCode { get; set; }
public int? SaleOrderId { get; set; }
public SaleOrder? SaleOrder { get; set; }
}
A Product Code and Quantity make up a rule, and a Sale Order can have many Rules and Cartons, Cartons can only be added to the Sales Order if the Product Code matches one of the rules, I am trying to query a breakdown of the data for a single Sale Order, including:
The SaleOrderRuleId
The Product Code
The Total Cartons scanned for this Rule
The Total Quantity required for this Rule And
A list of the Scanned Cartons for the rule (just the BoxID and CartonId)
I came up with the following query:
var saleOrderCartonCountBreakdown = await
(from c in _context.Cartons
where c.SaleOrderId == id
group c by new
{
c.ProductCodeId,
} into g
select new
{
ProductCodeId = g.Key.ProductCodeId,
CartonInfo = g.Select(x =>
new SaleOrderCartonBreakdownCarton {
CartonId = x.CartonId,
BoxID = x.BoxID
}).ToList(),
Count = g.Count()
} into gv
join pc in _context.ProductCodes on gv.ProductCodeId equals pc.ProductCodeId
join sor in _context.SaleOrderRules on gv.ProductCodeId equals sor.ProductCodeId
select new SaleOrderCartonBreakdownModel
{
SaleOrderRuleId = sor.SaleOrderRuleId,
ProductCode = pc.Value,
TotalScanned = gv.Count,
TotalRequired = sor.Quantity,
CartonList = gv.CartonInfo.ToList()
}).ToListAsync();
This works, but it only includes data if there is atleast one scanned carton for a product code, the intention is to also include the product codes for rules which do not have any cartons scanned in yet.
I'm fairly certain this is because I am starting on the Cartons table, however my attempts to rewrite the query to start on the SaleOrderRules and output the same result have been unsuccessful.
Any help is much appreciated :)
I have the following Code:
public class ProductTbl
{
public override int Id { get; set; }
public string ProductName { get; set; }
public List<ProductManufacturer> ProductManufacturer { get; set; } //M2M
}
public class Manufacturer_LKP
{
public override int Id { get; set; }
public string ManufacturerName { get; set; }
public List<ProductManufacturer> ProductManufacturer { get; set; } //M2M
}
public class ProductManufacturer
{
public ProductTbl Product { get; set; }
public int ProductID { get; set; }
public Manufacturer_LKP Manufacturer { get; set; }
public int ManufacturerID { get; set; }
}
public class SupplierTbl
{
public int SupplierID { get; set; }
public string SupplierName { get; set; }
}
public class ProductSuppliertbl
{
public int Id { get; set; }
public ProductTbl Product { get; set; }
public int ProductID { get; set; }
public SuppilerTbl Supplier { get; set; }
public int SupplierID { get; set; }
}
*I need to write Linq query to join all 3 tables (Product,Manufacture,ProductManufacturer) to get ProductName and ManufatureName together in one DB trip
*When I do the following I missed the Manufacture object (Manufacture=Null)
DbSet<ProductTbl>()
.Where(a => a.Id == 5)
.AsNoTracking()
.Include(a => a.ProductType)
.Include(a => a.ProductManufacturer)
Above Linq Just joint Product table with ProductManufacture Table So I cannot Get "ManufactureName"
So Is there is any way to join the 3 tables to get ManufactureName beside the ProductName in one DB trip?
Projection is your friend when trying to load related data. The issue with many-to-many is that you are saying a product has many manufacturers, while at the same time it has many suppliers
The Product would need a reference to the ProductSuppliers for that product to easily manage the many suppliers requirement.
var productData = context.Products
.Select(p => new
{
p.ProductName,
ManufacturerNames = p.ProductManufacturers.Select(pm => pm.Manufacturer.ManufacturerName).ToList(),
SupplierNames = x.ProductSuppliers.Select(ps => ps.Supplier.SupplierName).ToList()
}).ToList();
This gives you a list of products, with each product's associated manufacturer names and supplier names. With that data you can format output how you see fit.
If you want the entities themselves, then the missing bit is ThenInclude:
var products = context.Products
.Include(p => p.ProductManufacturers)
.ThenInclude(pm => pm.Manufacturer)
.Include(p => p.ProductSuppliers)
.ThenInclude(ps => ps.Supplier)
.AsNoTracking()
.ToList();
This would load the entire entity graph.
If you don't want or cannot put a ProductSuppliers collection in product then you can build the query entirely from the ProductSupplier, but it's a bit messier.
If you are using EF Core 5 and your joining entities (ProductManufacturer/ProductSupplier) are just simply the FK references to their respective entities, then you can do away with the joining entity and let EF manage it behind the scenes. Product would just contain a collection of Manufacturers and a collection of Suppliers. These can be configured still with a HasMany..WithMany, but makes queries a lot cleaner to look at without the intermediate entities.
I.e.
var productData = context.Products
.Select(p => new
{
p.ProductName,
ManufacturerNames = p.Manufacturers.Select(m => m.ManufacturerName).ToList(),
SupplierNames = x.Suppliers.Select(s => ps.SupplierName).ToList()
}).ToList();
and
var products = context.Products
.Include(p => p.Manufacturers)
.Include(p => p.Suppliers)
.AsNoTracking()
.ToList();
... respectively. Intermediate joining entities are only needed if there are additional properties you want to access in the joining entity. (I.e. CreatedBy/At, etc.)
try this
var list = context.ProductManufactures
.Select(i => new
{
ProductName = i.Product.ProductName,
ManufacturerName = i.Manufacturer.ManufacturerName,
SupplierNames = i.Product.ProductSuppliers.Select(s => s.SupplierName).ToList()
}).ToList();
or you can try this too
var productData = context.Products
.Select(i => new
{
ProductName= i.ProductName,
ManufacturerNames = i.ProductManufacturers.Select(m => m.Manufacturer.ManufacturerName),
SupplierNames = i.ProductSuppliers.Select(s => s.Supplier.SupSupplierName)
}).ToList();
but before this you have to fix some navigation properties
public class Product
{
public int Id { get; set; }
public string ProductName { get; set; }
public List<ProductManufacturer> ProductManufacturers { get; set; }
public List<ProductSupplier> ProductSuppliers { get; set; }
}
public class Supplier
{
public int SupplierID { get; set; }
public string SupplierName { get; set; }
public List<ProductSupplier> ProductSuppliers { get; set; }
}
public class ProductSupplier
{
public int Id { get; set; }
public Product Product { get; set; }
public int ProductID { get; set; }
public Supplier Supplier { get; set; }
public int SupplierID { get; set; }
}
public class Manufacturer_LKP
{
public int Id { get; set; }
public string ManufacturerName { get; set; }
public List<ProductManufacturer> ProductManufacturer { get; set; }
}
public class ProductManufacturer
{
public Product Product { get; set; }
public int ProductID { get; set; }
public Manufacturer_LKP Manufacturer { get; set; }
public int ManufacturerID { get; set; }
}
I write a MySql join code, and want to retrive same value from the Dotnetcore linq methods.
My Join code is below:
SELECT GL.Id AS GradeLevels,
CRS.Name AS CourseName,
GL.Title AS GradlevelName,
AVG (ASTSTU.ObtainedMarks)
FROM GradeLevels GL
INNER JOIN Courses AS CRS ON CRS.GradeLevelsID = GL.Id
INNER JOIN Units AS UNT ON UNT.CourseID = CRS.ID
INNER JOIN Lessons AS LSN ON LSN.UnitsId = UNT.Id
INNER JOIN Assignments AS AST ON AST.LessonId = LSN.id
INNER JOIN AssignmentStudents AS ASTSTU ON ASTSTU.AssignmentId = AST.id
WHERE CRS.SchoolSystemsID = "08d6a1f2-26df-4ad5-25d3-2a26960aa3fd" -- School System id.
GROUP BY GL.Id;
Now I want to change above MySQL Join into Dotnet core linq method to create an API that will be Showing, I try to write code for this
public async Task<ICollection<GradeLevels>> GetSchoolSystemGradLevelsAverage(Guid schoolSystemId)
{
List<GradeLevels> dashboadOverAllAverage = new List<GradeLevels>();
var dashboadOverAllAverage1 = await _GpsContext.GradeLevels
.Include(d=>d.Departments)
.ThenInclude(c=>c.Courses.Where(s=>s.SchoolSystemsID ==schoolSystemId))
.ThenInclude(u=>u.Units)
.ThenInclude(l=>l.Lessons)
.ThenInclude(a=>a.Assignment)
.ThenInclude(a=>a.assignmentStudents)
.GroupBy(g=>g.ID)
.ToListAsync();
return dashboadOverAllAverage;
}
Now I want to show the data though API and want to call to fields GradeLvels name and Average Marks.
[HttpGet()]
public async Task<IActionResult> GetCEOGradeLevelAverage(string schoolSystemId)
{
var overallgradeAverages = await _ceoDashboadRepository.GetSchoolSystemGradLevelsAverage(Guid.Parse(schoolSystemId));
List<GetGradeLevelAverageVm> getOverallAverageVms = new List<GetGradeLevelAverageVm>();
foreach (GradeLevels overallgradeAverage in overallgradeAverages)
{
getOverallAverageVms.Add(new GetGradeLevelAverageVm
{
Marks = overallgradeAverage.Id.ToString(), //Want to show lable of AvrageMark
Name = overallgradeAverage.Name //Want to show Gradelevel name
});
}
return Ok(getOverallAverageVms);
}
You do select too much from your DB. Here an example, how to select the nessecary values:
using (TestDbContext ctx = new TestDbContext())
{
var tmp = ctx.AssignmentStudents
.Include(s => s.Assignment) // Include all Childs..
.ThenInclude(a => a.Lesson)
.ThenInclude(l => l.Unit)
.ThenInclude(u => u.Course)
.ThenInclude(c => c.GradeLevel)
.Where(a => a.LessonId == 123)
.GroupBy(g => // Group by your Key-Values Grade and Course (You could take names instead of ids. Just for simplification)
new
{
GradeLevel = g.Assignment.Lesson.Unit.Course.GradeLevel.Id,
Course = g.Assignment.Lesson.Unit.Course.Id
})
.Select(s => // Select the result into an anonymous type
new
{
GradeLevels = s.Key.GradeLevel, // with same keys like grouping
Course = s.Key.Course,
AverageObtainedMarks = s.Average(a => a.ObtainedMarks) // and an average ObtainedMarks from objects matching the key
})
.Where(s => s.GradeLevel == 1);
foreach (var t in tmp)
{
Console.WriteLine(t.GradeLevels + " " + t.Course + ": " + t.AverageObtainedMarks);
}
}
Here a the classes and dbcontext I used:
public class GradeLevel
{
public int Id { get; set; }
public List<Course> Courses { get; set; }
}
public class Course
{
public int Id { get; set; }
public int GradeLevelId { get; set; }
public GradeLevel GradeLevel { get; set; }
public List<Unit> Units { get; set; }
}
public class Unit
{
public int Id { get; set; }
public int CourseId { get; set; }
public Course Course { get; set; }
public List<Lesson> Lessons { get; set; }
}
public class Lesson
{
public int Id { get; set; }
public int UnitId { get; set; }
public Unit Unit { get; set; }
public List<Assignment> Assignments { get; set; }
}
public class Assignment
{
public int Id { get; set; }
public int LessonId { get; set; }
public Lesson Lesson { get; set; }
public List<AssignmentStudent> AssignmentStudents { get; set; }
}
public class AssignmentStudent
{
public int Id { get; set; }
public int AssignmentId { get; set; }
public Assignment Assignment { get; set; }
public decimal ObtainedMarks { get; set; }
}
public class TestDbContext : DbContext
{
public DbSet<AssignmentStudent> AssignmentStudents { get; set; }
public DbSet<Assignment> Assignments { get; set; }
public DbSet<Lesson> Lessons { get; set; }
public DbSet<Unit> Units { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<GradeLevel> GradeLevels { get; set; }
}
I have the following requirements:
One rating can have zero or many RatingPictures
One Rating can have zero or many Comments
One Comment belongs to one User
This is what I have so far:
from rating in Ratings
where rating.LocationID == 1007
join ratingpicture in RatingPictures
on rating.ID equals ratingpicture.RatingID into j3
from ratingpicture in j3.DefaultIfEmpty()
join comment in Comments
on rating.ID equals comment.RatingID into j1
from comment in j1.DefaultIfEmpty()
join user in Users
on comment.UserID equals user.ID into j2
from user in j2.DefaultIfEmpty()
group new { ratingpicture, comment, user } by rating into g
select new { rating = g.Key, ratingpicture= g.Key, comment = g.ToList() }
If you model your entity classes like this:
public class Comment
{
public int CommentId { get; set; }
public int RatingId { get; set; }
public virtual Rating Rating { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
}
public class User
{
public int UserId { get; set; }
}
public class Rating
{
public Rating()
{
RatingPictures = new HashSet<RatingPicture>();
Comments = new HashSet<Comment>();
}
public int RatingId { get; set; }
public virtual ICollection<RatingPicture> RatingPictures { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
public int LocationID { get; set; }
}
public class RatingPicture
{
public int RatingPictureId { get; set; }
public int RatingId { get; set; }
public virtual Rating Rating { get; set; }
}
Then your query would be as simple as this:
var query = context.Ratings.Where(r => r.LocationID == 1007)
.Include(r => r.RatingPictures)
.Include(r => r.Comments.Select(c => c.User));
var result = query.ToList();
Under the hood, this query will be translated into a join query. But this is the beauty of ORMs, we get to work at a more abstract level.
Take a look at this reference for more information about relationships and navigation properties in Entity Framework.
I'm using EF4.1 code first to create a simple database app with SQL CE 4 backend. I have a Product class and a CallItem class defined as so:
class CallItem
{
public int id { get; set; }
public float discount { get; set; }
public virtual Product Product { get; set; }
}
class Product
{
public int id { get; set; }
public decimal BaseCost { get; set; }
public int UnitSize { get; set; }
public bool isWasteOil { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Ingredients { get; set; }
}
edit - When I am creating a collection of CallItems using a LINQ query, I cannot access the attributes of the Product attached to each CallItem, eg
var callItems = from ci in context.CallItems select ci;
foreach(CallItem callItem in callItems)
{
RunSheet nrs = new RunSheet();
nrs.prodCode = callitem.Product.Code;
}
Interrogating the database shows that Productid in CallItems is being populated. However, the following line generates a NullReferenceException during run time:
nrs.prodCode = callitem.Product.Code;
Because callitem.Product is evaluating to null. Is this something to do with lazy loading and if so how can I resolve the issue?
RunSheet is another class, nrs is an instance whose attribute 'prodCode' I want to populate with the CallItem's Product's code.
Thanks!
From that code what you've showed it should work. Have you tried explicit loading?
var callItems = from ci in context.CallItems.Include(c => c.Product) select ci;
foreach(CallItem callItem in callItems)
{
RunSheet nrs = new RunSheet();
nrs.prodCode = callitem.Product.Code;
}
public class CallItem
{
public int Id { get; set; }
public float Discount { get; set; }
public virtual Product Product { get; set; }
}
public class Product
{
public int Id { get; set; }
public decimal BaseCost { get; set; }
public int UnitSize { get; set; }
public bool IsWasteOil { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Ingredients { get; set; }
}
using (var context = new StackOverFlowContext())
{
var p = new Product
{
Id = 1,
BaseCost = 200,
Code = "Hola",
Description = "Soe description",
Ingredients = "Some ingredients",
IsWasteOil = true,
Name = "My Product",
UnitSize = 10
};
var item = new CallItem
{
Id = 101,
Discount = 10,
Product = p
};
context.CallItems.Add(item);
context.SaveChanges();
var result = from temp in context.CallItems
select temp;
Console.WriteLine("CallItem Id"+result.First().Id);
Console.WriteLine("ProductId"+result.First().Product.Id);
}
I wrote the above code with the following output
CallItemId 1
ProductId 1
The sql Profiler showed this
SELECT TOP (1)
[c].[Id] AS [Id],
[c].[Discount] AS [Discount],
[c].[Product_Id] AS [Product_Id]
FROM [dbo].[CallItems] AS [c]
It was too long for a comment ,so i put it here .