EF core join using include - entity-framework

I am trying to join related data using Include but I am having some difficulties. My models are the following
public partial class GBTObject
{
public uint Id { get; set; }
public uint OrganizationId { get; set; }
public string Name { get; set; }
public virtual Device Device { get; set; }
public uint? DeviceId { get; set; }
}
public partial class Device
{
public uint Id { get; set; }
public uint OrganizationId { get; set; }
public string UUID { get; set; }
public bool? Enabled { get; set; }
}
public partial class DeviceState
{
public uint Id { get; set; }
public uint OrganizationId { get; set; }
public uint DeviceId { get; set; }
public string State { get; set; }
public DateTime? Timestamp { get; set; }
public byte? Event { get; set; }
public ulong TotalDistance { get; set; }
public string UserAgent { get; set; }
}
var data = _context.GBTObject
.Where(x => x.DeviceId != null && x.OrganizationId == _user.OrganizationId)
.Include(x => x.Device)
.Include(x => x.State)
Then I tried to create a shadow property inside Device
[ForeignKey("Id")]
public virtual DeviceState State{ get; set; }
var data = _context.GBTObject
.Where(x => x.DeviceId != null && x.OrganizationId == _user.OrganizationId)
.Include(x => x.Device)
.ThenInclude(x => x.State)
But it doesn't work cause the it joins using the the DeviceId from GBTObject with Id from DeviceState. Changing the foreign key to DeviceId results in weird naming errors(it names the GBTObject.DeviceId to GBTObject.DeviceId1 and then it complains that it doesn't exist and looks like a bug).
Am I doing this wrong?

Try the following:
var data = from gbt in _context.GBTObject
join ds in _context.DeviceState
on gbt.DeviceId equals ds.DeviceId
where gbt.DeviceId != null && gbt.OrganizationId == _user.OrganizationId
select gbt;
Also check this link for further info about joining in LINQ:
What is the syntax for an inner join in LINQ to SQL?

Related

EF Lambda How to make projection for GroupJoin

I am trying to query EF models. (GameBank and GameCouponBank) How can I make a projection for left outer join (GoupJoin)?
Can I make projection for Coupons?
Here is my query
var gameBankResult = context.GameBanks.GroupJoin(context.GameCouponBanks, g => g.GameBankID, gc => gc.GameBankID,
(g,gc) => new {
g.quantity,
g.currency,
g.initiationResultCode,
g.productCode,
g.productDescription,
g.referenceId,
g.responseDateTime,
g.unitPrice,
g.totalPrice,
Coupons = gc
})
.Where(g => g.productCode == initiate.productCode)
.Select(s => s);
Here is models:
public class GameBank
{
public int GameBankID { get; set; }
public string referenceId { get; set; }
public string productCode { get; set; }
public int quantity { get; set; }
public string version { get; set; }
public DateTime? requestDateTime { get; set; } = DateTime.Now;
public int? customerID { get; set; }
public string password { get; set; }
public DateTime? responseDateTime { get; set; } = DateTime.Now;
public string initiationResultCode { get; set; }
public string companyToken { get; set; }
public int used { get; set; }
public string productDescription { get; set; }
public string currency { get; set; }
public double unitPrice { get; set; }
public double totalPrice { get; set; }
public virtual List<GameCouponBank> coupons { get; set; }
}
public class GameCouponBank
{
public int Id { get; set; }
public int GameBankID { get; set; }
public DateTime? expiryDate { get; set; }
public string Serial { get; set; }
public string Pin { get; set; }
}
You don't need to use GroupJoin explicitly. You can simply project your query as follows:
var gameBankResult = context.GameBanks.Where(g => g.productCode == initiate.productCode)
.Select(g => new {
g.quantity,
g.currency,
g.initiationResultCode,
g.productCode,
g.productDescription,
g.referenceId,
g.responseDateTime,
g.unitPrice,
g.totalPrice,
Coupons = g.coupons.Select(c => new {c.Id, c.GameBankID,...}).ToList() //<-- Here is the projection for coupons
}).FirstOrDefault(); // I assume you are returning single entity, if not then use `.ToList()` instead of `.FirstOrDefault()`

Method based linq queries

I have five tables in database whose Entity classes are as follows -
Product
public int ProductId { get; set; }
public string Name { get; set; }
public int Category { get; set; }
public string Description { get; set; }
public string Brand { get; set; }
public virtual ProductCategory ProductCategory { get; set; }
public virtual ICollection<ProductImage> ProductImages { get; set; }
public virtual ICollection<ProductVariantMapping> ProductVariantMappings
ProductCategory
public int CategoryId { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
ProductImage
public int ProductImageId { get; set; }
public int ProductId { get; set; }
public byte[] Image { get; set; }
public virtual Product Product { get; set; }
ProductVariantMapping
public int MappingId { get; set; }
public int ProductId { get; set; }
public int ProductVariantId { get; set; }
public string Value { get; set; }
public System.Guid GUID { get; set; }
public virtual Product Product { get; set; }
public virtual ProductVariant ProductVariant { get; set; }
ProductVariant
public int ProductVariantId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<ProductVariantMapping> ProductVariantMappings
I want to get Product Details which should include ProductId, ProductName, Category, Description, Brand, Image(Only 1 for now), and Variants*
*Variants would be a list of all the variants of a product. A single variant can be a combination of all the VariantIds with same GUIDs. (VariantName is in ProductVariant table and VariantValue is in ProductVariantMapping table and Price is in inventory table).
So, I used method-based linq for this purpose.
EkartEntities ekartEntities = new EkartEntities();
var productDetails = ekartEntities.Products.Include(p =>
p.ProductVariantMappings).Include(p => p.ProductImages).Include(p =>
p.ProductCategory).Where(p => p.ProductId ==
productDetailDTO.ProductId).ToList();
Now I have to convert my product into a ProductDetailDTO.
ProductDetailDTO
public class ProductDetailDTO
{
public int ProductId { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public byte[] Image { get; set; }
public string Description { get; set; }
public string Brand { get; set; }
public List<Variant> Variants { get; set; }
}
public class Variant
{
public string Name { get; set; }
public string Value { get; set; }
public System.Guid Guid { get; set; }
public decimal Price { get; set; }
}
I started doing this like this -
void ToDTO(List<Product> products)
{
EkartEntities ekartEntities = new EkartEntities();
ProductDetailDTO productDetailDTO = new ProductDetailDTO();
foreach (var item in products)
{
productDetailDTO.ProductId = item.ProductId;
productDetailDTO.Name = item.Name;
productDetailDTO.Category = item.ProductCategory.Name;
productDetailDTO.Description = item.Description;
productDetailDTO.Brand = item.Brand;
productDetailDTO.Image = item.ProductImages.ElementAt(0).Image;
foreach (var variant in item.ProductVariantMappings)
{
productDetailDTO.Variants = variant.ProductVariant // ?
}
}
}
I don't know how do I proceed further. How can I extract the variant based on the GUIDs?
The logic of combining of ProductVariant entries with same GUID in mapping table doesn't seem clear from the question, however you can group entries in ProductVariantMappings by GUID and then add any logc you like on group. Here is an example where I take first name and value in a groub of variant with the same GUID:
void ToDTO(List<Product> products)
{
EkartEntities ekartEntities = new EkartEntities();
ProductDetailDTO productDetailDTO = new ProductDetailDTO();
foreach (var item in products)
{
productDetailDTO.ProductId = item.ProductId;
productDetailDTO.Name = item.Name;
productDetailDTO.Category = item.ProductCategory.Name;
productDetailDTO.Description = item.Description;
productDetailDTO.Brand = item.Brand;
productDetailDTO.Image = item.ProductImages.ElementAt(0).Image;
productDetailDTO.Variants = item.ProductVariantMappings
.GroupBy(pm => pm.GUID)
.Select(g => new Variant
{
Guid = g.Key,
// Here should be some logic for getting a name of the combination of Variants
// I just take first
Name = g.FirstOrDefault()?.ProductVariant?.Name,
// Here should be some logic for getting a value of the combination of Variants
// Take first again
Value = g.FirstOrDefault()?.Value,
Price = // need inventory table to compute price
})
.ToList();
}
}
Also note that you need somehow add relation to inventory table, which is not presented in question. Hope it helps.

EF Core could not be translated and will be evaluated locally

I have a query in EF Core 1.1.2 that is evaluated on client side and would like to know if there is a better way to translate it into sql?
The query:
from l in _ctx.Locations
join i in _ctx.Inventories on l.Id equals i.LocationId
join it in _ctx.Items on i.ItemId equals it.Id
where l.ProjectId == projectid
group i by new {l.Id, l.LHA} into il
select new InventoryLocations() {
Id= il.Key.Id,
LHA = il.Key.LHA,
FlaggedItems = il.Any(x=>x.Item != null && x.Item.Flagged)
}
If not, what other options do I have?
As I know there's still no way mapping views.
FromSQL() method can return types already known in the context only and I can not mark one model as [NotMapped] for example.
Moving back to ef6 is not an option because .net core is the target framework.
Models:
public class Location
{
public Guid Id { get; set; }
[ForeignKey("Project")]
public Guid ProjectId { get; set; }
public Project Project {get; set; }
public string Name { get; set; }
public string LHA { get; set; }
[ForeignKey("ScanUser")]
public Guid? ScanUserId { get; set; }
public User ScanUser { get; set; }
[ForeignKey("CheckUser")]
public Guid? CheckUserId { get; set; }
public User CheckUser { get; set; }
[ForeignKey("GroupLeader")]
public Guid? GroupLeaderId { get; set; }
public User GroupLeader { get; set; }
public int State { get; set; }
}
public class Inventory
{
public Guid Id { get; set; }
[ForeignKey("Project")]
public Guid ProjectId { get; set; }
public Project Project {get; set; }
public string EANCode { get; set; }
[ForeignKey("Location")]
public Guid LocationId { get; set; }
public Location Location { get; set; }
public Double ScanQty { get; set; }
[ForeignKey("ScanUser")]
public Guid? ScanUserId { get; set; }
public User ScanUser { get; set; }
public DateTime? ScanDate { get; set; }
[ForeignKey("Item")]
public Guid? ItemId { get; set; }
public Item Item { get; set; }
[ForeignKey("InventoryTask")]
public Guid? InventoryTaskId { get; set; }
public InventoryTask InventoryTask { get; set; }
[ForeignKey("CheckUser")]
public Guid? CheckUserId { get; set; }
public User CheckUser { get; set; }
public DateTime? CheckDate { get; set; }
public Double PrevQty { get; set; }
}
public class Item
{
public Guid Id { get; set; }
[ForeignKey("Project")]
public Guid ProjectId { get; set; }
public Project Project {get; set; }
public string ItemNo { get; set; }
public string EANCode { get; set; }
public string Name { get; set; }
public Double Price { get; set; }
public bool Deleted { get; set; }
public DateTime ChangeTime { get; set; }
public Double BaseQty { get; set; }
public bool Flagged { get; set; }
}
Currently (and looks like also in the incoming EF Core v.2.0) the GroupBy queries are processed locally, so the key is to avoid them where possible.
And your query seems to be eligible for that - there is no need to first multiply the data set with joins and then group it back.
I've noticed you use only reference navigation properties and FKs in your entities, basically like database table record and SQL. But EF allows you to define also a corresponding collection navigation properties which allow you to start queries from the logical root, thus eliminating the need of joins and group by.
If you define navigation property from Location to Inventory
public class Location
{
// ...
public ICollection<Inventory> Inventories { get; set; }
}
then the equivalent query could be simply:
from loc in _ctx.Locations
where loc.ProjectId == projectid
select new InventoryLocations()
{
Id = loc.Id,
LHA = loc.LHA,
FlaggedItems = loc.Inventories.Any(inv => inv.Item != null && inv.Item.Flagged)
}
which will be fully translated to SQL.
If for some reason you can't create the above collection navigation property, still you can start with locations and manually correlate them with inventories:
from loc in _ctx.Locations
where loc.ProjectId == projectid
select new InventoryLocations()
{
Id = loc.Id,
LHA = loc.LHA,
FlaggedItems = _ctx.Inventories.Any(inv => loc.Id == inv.LocationId && inv.Item != null && inv.Item.Flagged)
}
If you add the navigation property as Ivan correctly suggests:
public class Location
{
// ...
public ICollection<Inventory> Inventories { get; set; }
}
Then you can simply create a query like this:
var locations = _ctx.Locations
.Include(x => x.Inventories)
.ThenInclude(x => x.Item)
.Where(x => x.ProjectId == projectId)
.Select(loc => new InventoryLocations
{
Id = loc.Id,
LHA = loc.LHA,
FlaggedItems = loc.Inventories.Any(inv => inv.LocationId == loc.Id && inv.Item?.Flagged)
});

Complex subquery in Entity Framework 6

I have an entity called Insurance like this:
public class Insurance : BaseEntity, IExpirationDocument
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public override int Id { get; set; }
[Column(TypeName = "NVARCHAR")]
[StringLength(256)]
public string PathToCertificate { get; set; }
[Column(TypeName = "NVARCHAR")]
[StringLength(50)]
public string Filename { get; set; }
public int Value { get; set; }
public string Name => InsuranceType.Name;
public DateTime ExpiryDate { get; set; }
public DateTime IssueDate { get; set; }
public bool Active { get; set; }
public int InsuranceTypeId { get; set; }
public virtual InsuranceType InsuranceType { get; set; }
public int InsurerId { get; set; }
public virtual Insurer Insurer { get; set; }
public int ApplicantId { get; set; }
public virtual Applicant Applicant { get; set; }
public int? DocumentEmailHistoryId { get; set; }
public virtual DocumentEmailHistory DocumentEmailHistory { get; set; }
public Insurance()
{
Active = true;
}
}
Would it be possible to do this type of query with Entity Framework:
SELECT *
FROM Insurances i1
INNER JOIN
(SELECT
insuranceTypeId, applicantid, MAX(IssueDate) as 'maxissuedate'
FROM
Insurances
GROUP BY
insuranceTypeId, applicantid) AS i2 ON i1.applicantid = i2.applicantid
AND i1.insuranceTypeId = i2.insuranceTypeId
WHERE
i1.issueDate = i2.maxissuedate
If you are trying to get latest issued Insurance according to InsuranceTypeId and ApplicantId you can group data according to needed properties, order by IssueDate descendingly and take only one Insurance info. Of course it will not give you the same query but it will give you the same result:
var result = context.Insurances
.GroupBy(m => new { m.InsuranceTypeId , m.ApplicantId })
.Select( g => new
{
MaxInsurance = g.OrderByDescending(m => m.IssueDate)
.Take(1)
})
.SelectMany(m => m.MaxInsurance);

Filtering related entities with Entity framework

I'm looking for the best way to to load and filter related child entities. I have something that works, but I'm unsure if it's the best or even the right way to achieve what I want. Working code example below. Pros and cons would be great! Thanks!
public Site Find(int siteID)
{
// Can't use include here, not possible to filter related (child) entities
// return _context.Sites.Where(x => x.ID == siteID)
// .Include("SiteLoggers")
// .Where(x => x.Deleted == false)
// .FirstOrDefault();
var site = _context.Sites.Where(x => x.ID == siteID).FirstOrDefault();
if(site != null)
{
site.SiteLoggers = site.SiteLoggers.Where(x => x.SiteID == siteID &&
x.Deleted == false)
.ToList();
}
return site;
}
EDIT:
Added POCOS
public class Site
{
public int ID { get; set; }
public int LocationID { get; set; }
public bool Active { get; set; }
public bool Deleted { get; set; }
public string Name { get; set; }
public virtual Location Location { get; set; }
public virtual ICollection<SiteLogger> SiteLoggers { get; set; }
public virtual ICollection<LinkDcSite> DcSiteLinks { get; set; }
}
public class SiteLogger
{
public int ID { get; set; }
public int UID { get; set; }
public int SiteID { get; set; }
public int LocationID { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public bool Deleted { get; set; }
public virtual Site Site { get; set; }
public virtual Location Location { get; set; }
public virtual ICollection<SiteLoggerSensor> SiteLoggerSensors { get; set; }
public virtual ICollection<LinkLoggerSiteLogger> LinkLoggerSiteLogger { get; set; }
}
Your method is fine I think you have just extra checking for x.SiteID == siteID:
....
site.SiteLoggers = site.SiteLoggers.Where(x => !x.Deleted).ToList();
....
Also if you searching by ID means you are sure there is no two element with same ID, so it's better to use SingleOrDefault instead of FirstOrDefault, to throw an exception in the case there are more than one item with one ID.
var site = _context.Sites.Where(x => x.ID == siteID).SingleOrDefault();
You can do that with a simple query:
var site = _context.SiteLoggers.Where(sl => sl.SiteId = siteId && !sl.Deleted).ToList();
If there's a relation between SiteLoggers and Sites, you don't need to chek that the site exists.