EF & LINQ: Get object from database cell - c#

I use Entity Framework Code First and I have three tables (for example):
public class Motorbike()
{
public int Id {get; set;}
public string Producent {get; set;}
public Engine Motor {get; set;}
public Tire Tires {get; set;}
}
public class Engine()
{
public int Id {get; set;}
public int Power {get; set;}
public decimal Price {get;set;}
}
public class Tire()
{
public int Id {get; set;}
public int Size {get; set;}
public decimal Price {get; set;}
}
It's just example, in fact it's more complicated.
Entity Frmaework generates table for me, but tables Motorbike has column: Id, Power, Engine_Id (where storing only number - id engine, not whole object) and Tire_Id (where storing only number - id tire, not whole object).
I know how to insert data - just create new Motorbike object, save to his fields data (for Engine and Tire fields I save whole objects not only id) and use .Add() method from my context.
But how to get data for row where motorbike id is (for example) 1?
I've tried something like this:
List<Motorbike> motorbikes= new List<Motorbike>();
var list = _context.Motorbike.Where(p => p.Id == 1);
motorbikes.AddRange(list);
but always I've got null for Engine and Tire fields (fields Id and Producent are fill properly).

Use Include to load related entities like:
var list = _context.Motorbike
.Include(m=> m.Engine)
.Include(m=> m.Tire)
.Where(p => p.Id == 1);
See: Entity Framework - Loading Related Entities

You're looking for the Include() method.
List<Motorbike> motorbikes = _context.Motorbike
.Include(p => p.Engine)
.Include(p => p.Tire)
.Where(p => p.Id == 1)
.ToList();

Related

SQL to LINQ Conversion Involving Max()

I wanted to retrieve the top 5 merchants with the latest order and the grand total amount. However, I can't seem to get the Grand total amount. I think the issue lies within LastOrderGrandTotal= x.FirstOrDefault().GrandTotal. The code below syntax is in LINQ method syntax
var list = _ApplicationDbContext.OrderTransaction
.Where(x => x.Status != 0)
.GroupBy(x => x.MerchantUserId)
.Select(x => new DashboardActiveMerchantStore { MerchantUserId = x.Key, CreatedDate = x.Max(c => c.CreatedDate), LastOrderGrandTotal= x.FirstOrDefault().GrandTotal })
.OrderByDescending(x => x.CreatedDate)
.Take(5)
.ToList();
For further clarification the SQL syntax for the query above is
select TOP(5) MerchantUserId,MAX(CreatedDate),GrandTotal from OrderTransaction
group by MerchantUserId
order by CreatedDate desc
I have a class to store the data retrieve from the LINQ syntax
public class DashboardActiveMerchantStore
{
public string MerchantName { get; set; }
public double LastOrderGrandTotal { get; set; }
public Guid MerchantUserId { get; set; }
public DateTime CreatedDate { get; set; }
}
As you are using entity framework, a totally different, more natural approach is possible. You can use the virtual ICollection<...>.
I wanted to retrieve the top 5 merchants with the latest order and the grand total amount.
The classes in entity framework
It seems there is a one-to-many relation between Merchants and OrderTransactions: every Merchant has zero or more OrderTransactions, and every OrderTransaction is a transaction of exactly when Merchant, namely the Merchant that the foreign key MechantId refers to.
If you've followed the entity framework conventions, you will have classes similar to:
public class Merchant
{
public Guid Id {get; set;} // primary key
public string Name {get; set;}
...
// Every Merchant has zero or more TransactionOrders (one-to-many)
public virtual ICollection<TransactionOrder> TransactionOrders {get; set;}
}
public class TransactionOrder
{
public Guid Id {get; set;} // primary key
public DateTime CreatedDate {get; set;}
public int Status {get; set;}
public double GrandTotal {get; set;}
...
// every TransactionOrder is an order of a Merchant, using foreign key
public Guid MerchantId {get; set;}
public virtual Merchant Merchant {get; set;}
}
And of course the DbContext:
public class OrderDbContext : DbContext
{
public DbSet<Merchant> Merchants {get; set;}
public DbSet<TransactionOrder> TransactionOrders {get; set;}
}
This is all that entity framework needs to detect the tables, the columns of the tables and the one-to-many relation between the table. Only if you deviate from the conventions, like you want to use different property names, or different table names, you need attributes or fluent API.
In entity framework the columns of a table are represented by non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many)
Get top 5 Merchants
If you want some information about "merchants that ... with some information about some of their OrderTransactions" you start at dbContext.Merchants;
If you want some information about "OrderTransactions that ..., each with some information about their Merchant", you start at dbContext.OrderTransactions.
I want to query the top 5 merchants with the latest order and the grand total amount
So you want Merchants. From these Merchants you want at least their Id, and their Name, and some information about their OrderTransactions.
Not all OrderTransactions, only information about their last OrderTransaction with a non-zero Status.
From this last OrderTransaction you want the CreatedDate and the GrandTotal.
Now that you've got Merchants with their last non-zero Status Order, you don't want all these Merchants, you only want the five Merchants with the newest CreatedDate.
I hope the above is your requirement. It is not what your SQL said, but your SQL didn't fetch Merchants, it fetched groups of TransactionOrders.
var result = dbContext.Merchants
.Select(merchant => new
{
Id = merchant.Id,
Name = merchant.Name,
// from Every Merchant, get the Date and Total of their last
// non-zero status Order
// or null if there is no such order at all
LastOrder = merchant.OrderTransactions
.Where(orderTransaction => orderTransaction.Status != 0)
.OrderByDescending(orderTransaction => oderTransaction.CreatedDate)
.Select(orderTransaction => new
{
CreatedDate = orderTransaction.CreatedDate,
GrandTotal = orderTransaction.GrandTotal,
})
.FirstOrDefault(),
})
Entity Framework knows your one-to-many relation. Because you use the virtual ICollection<...> it will automatically create the (Group-)Join for you.
Now you don't want all Merchants, you don't want Merchants without LastOrder. They didn't have any Order with a non-zero Status. From the remaining Merchants you only want the five Merchants with the newest LastOrder.CreatedDate.
So remove all Merchants that have no LastOrders, order by descending LastOrder.CreatedDate and take the top 5.
Continuing the LINQ:
.Where(merchant => merchant.LastOrder != null) // at leas one Order with non-zero Status
.OrderbyDescending(merchant => merchant.LastOrder.CreatedDate)
.Take(5).
You will have "Merchants (Id and Name) with their LastOrder (CreatedData and GrandTotal)", if you want, add an extra Select to convert this into five DashboardActiveMerchantStores
I would rewrite the query like this. You may not even need FirstOrDefault because with GroupBy I believe there is always at least a record.
var result = data
.Where(x => x.Status != 0)
.GroupBy(x => x.MerchantUserId)
.Select(q => new
{
q.Key,
Orders = q.OrderByDescending(q => q.CreatedDate),
})
.Select(x => new DashboardActiveMerchantStore {
MerchantUserId = x.Key,
CreatedDate = x.Orders.FirstOrDefault().CreatedDate,
LastOrderGrandTotal = x.Orders.FirstOrDefault().GrandTotal,
})
.Take(5)
.ToList();
var lastFiveProducts = products.OrderByDescending(p => p.LastOrderGrandTotal).Take(5)

How to only return a list of inherited instances if defined, otherwise return the base class

I am trying to extend an existing application. I need to implement the ability to define multiple instances of a product while keeping it backward compatible with the existing implementation of the product class.
Current Product class:
public class Product {
public int Id {get; set;}
public string Name {get; set;}
public decimal Price {get; set;}
}
Getting a list is done by a simple EF call context.Products.ToList().
Now I want to extend the existing class to be able to define "subproducts". My first try:
public class Product {
public int Id {get; set;}
public string Name {get; set;}
public decimal Price {get; set;}
public List<SubProduct> SubProducts {get; set;}
}
public class SubProduct : Product {
public string Identifier {get; set;}
}
Now, If I have Product instance ProductA (€5, with no subproducts) and instance ProductB (€7) with 2 Subproducts "1" and "2", I need to get the following list when fetching all products:
Name Price
ProductA €5
ProductB-1 €7
ProductB-2 €7
How can I fetch a list of all products, but returning a mix of subproducts (without their ancestor) and products?
This has to be returned in a way that this list is compatible with all already existing calls within the application (so that the simple EF call as above would get this list)
It seems you need SelectMany:
var resss = produc
.SelectMany(p => p.SubProducts ?? new List<SubProduct>() { new SubProduct { Identifier = "" } },
(parent, child) => new { Name = !string.IsNullOrWhiteSpace(child.Identifier) ? parent.Name + "-" + child.Identifier : parent.Name, parent.Price});
The Result is:
In order to be backwards compatible you need HasDiscriminator in order to know which row is what entity

Linq union with group by perrformnces issue

I have 3 Tables defined as below:
public Class Product
{
public long Key {get; set;}
public CountryKey {get; set;}
}
public Class ProductCountry
{
pullic long Key {get; set;}
public long ProductKey {get; set;}
public LocalProductKey {get; set;}
public long CountryKey {get; set;}
}
public Class Country
{
public long Key {get; set;}
public string Name {get; set;}
}
I'm using EF database first, for each class we have a view to get data from database ( VW_Product to get the products)
I have a method filtering a collection of product depends on a criteria.
IQueryable<VW_Product> query1 = FilterQuery(Object criteria); gets all products matching the criteria;
Now I want concat the filtred collection by adding the folowing collection:
var countriesKey = new List<long>() {45, 36, 6974, 366,....};
var keys = Context.VW_ProductCountries
.GroupBy(pc => pc.ProductKey)
.Where(grp => grp.Any(pc => countriesKey.Contains(pc.CountryKey) && !grp.Any(x => x.LocalProductKey != null)))
.SelectMany(grp => grp.Select(pc => pc.Productkey))
.Distinct();
var query2 = Context.VW_Product.Where(p => keys.Contains(p.ProductKey));
var result = query1.Concat(query2);
Is the another way to improve this query because it takes a lot of time to execute.
Try adding a .ToList() after .Distinct() to transform the list of keys from an ObjectQuery to an in-memory object. In query2, you're running multiple .Contains() against the 'keys' ObjectQuery, possibly executing the query every time.
Also, i presume
var result = query1.Concat(query1);
should be
var result = query1.Concat(query2);

When Including child elements LINQ query returns less results

I would like to do a LINQ query that returns all of the records for the Parent table, and includes the Child, if applicable. I know I can do a LINQ join with DefaultIfEmpty(), but then I must output a ViewModel. I'd like to get an IQuerable<Parent> of the actual Parent Class.
So, if I have these classes:
public class Parent
{
[Key]
public int ParentId {get; set;}
public string ParentName {get; set;}
public int? MyChildId {get; set;}
[ForeignKey("MyChildId")]
public virtual Child MyChild {get; set;}
public bool IsActive {get;set;}
}
public class Child
{
public int ChildId {get;set;}
public string ChildName {get;set;}
}
In LINQPad, if I do this:
var results = db.Parent.Where(ra => ra.IsActive);
results.Dump();
I get 111 records.
If I do this:
var results = db.Parent.Where(ra => ra.IsActive);
var results2 = (from r in results
select new
{
ParentId = r.ParentId,
ParentName = r.ParentName,
MyChildId = r.MyChildId
});
results2.Dump();
I also receive 111 records.
But if I do this:
var results = db.Parent.Where(ra => ra.IsActive);
var results2 = (from r in results
select new
{
ParentId = r.ParentId,
ParentName = r.ParentName,
MyChildId = r.MyChildId,
IsActive = r.IsActive,
MyChildName = r.MyChild == null ? null : r.MyChild.ChildName
});
results2.Dump();
I only get 50 records. These are the 50 Parent records that have a child. If they do not have a child, they don't come back.
The SQL generated looks like this:
SELECT
[Extent1].[ParentId] AS [ParentId],
[Extent1].[ParentName] AS [ParentName],
[Extent1].[IsActive] AS [IsActive],
[Extent2].[ChildName] AS [ChildName]
FROM [dbo].[Parent] AS [Extent1]
INNER JOIN [dbo].[Child] AS [Extent2] ON [Extent1].[MyChildId] = [Extent2].[ChildId]
WHERE [Extent1].[IsActive] = 1
How can I get a resultset that includes all 111 Parent records, even if they have no child, but does include the Child elements, if they are there?
UPDATE
So, I may have lied a bit. I posted the above for simplicity sake, but just in case it helps, here is a closer sample of what the code does:
public class Parent
{
[Key]
public int ParentId {get; set;}
public string ParentName {get; set;}
public int? MyChildId {get; set;}
[ForeignKey("MyChildId")]
public virtual Child MyChild {get; set;}
[ForeignKey("MyChildId")]
public virtual StepChild MyStepChild {get; set;}
public bool IsActive {get;set;}
}
public class Child
{
public int ChildId {get;set;}
public string ChildName {get;set;}
}
public class StepChild
{
public int StepChildId {get;set;}
public string StepChildName {get;set;}
}
Sometimes it's hard to tell what's going on behind the scenes of EF, and you can encounter some non-obvious behaviors, what I usually do in such cases - is check the actually generated SQL query, and tweak the LINQ query until it is logically equivalent to the query I expect.
This is not the best approach as it is dependent on implementation details, that can change, but sometimes it is the only way to overcome a EF bug.
You can use ObjectQuery or EF logging and interception of DB calls to get to actual SQL query
Your foreign key is non nullable by default (or required), so you need to tell the EF that it should be optional. To make it you need to override the following modelBuilder method:
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
and configure your Entity foreign key:
modelBuilder.Entity<Parent>().HasOptional(e => e.MyChild).WithMany();
Details you can look here: http://blog.staticvoid.co.nz/2012/7/17/entity_framework-navigation_property_basics_with_code_first
You need two separate FK properties one int? personAssigneeId and another int? organizationAssigneeId. These FKs are pointing to two completely different entities. EF is not able to work properly if you reuse the same FK for two separate entities, it needs a FK per entity.

Nhibernate Lazy load on multiple columns

If I set LazyLoad on multiple properties-columns with NHiberante and access those properties one after the other, would it query the database for each property?
Example:
public class Product
{
public virtual int ID {get; set;}
public virtual string Name {get; set;}
public virtual string FullName {get; set;}
public virtual float Price {get; set;}
}
public class ProductMap : ClassMap<Product>
{
Id(p => p.ID);
Map(p => p.Name).Not.LazyLoad();
Map(p => p.FullName).LazyLoad(); // Redundant - I know...
Map(p => p.Price).LazyLoad(); // Redundant - I know...
}
if I query the DB like this:
var product = session.Load<Prodct>(2);
if (product.FullName == "*" && product.Price = 111)
Will there be 3 queries
The Product entity
The FullName property
The Price property
or when NHibernate query the DB for FullName it will query all the columns of the row?
NHibernate will load all the lazy properties of an entity in a single query (you can try for yourself...)
The main use case for this feature is blobs.
Lazy references, on the other hand, are loaded as needed.
As a side note, session.Load does not query the DB; it just creates a proxy, which will be loaded lazily. Use session.Get.
there will be 2 queries
The Product entity
All LazyLoaded Properties

Categories