I have a table which named movies and it has genres column. In genres column, I store the movie's genres id like [0,5,10] type. Those are foreign keys but stored in different way. Is there any way to use those foreign keys with EF.
I want to use it like List<genres> x = movie.genres.
Thanks.
"I thought that, store the genres id like this way can be more efficient."
Nope. Keep it simple and let the tools (EF and Database) do what they do. The efficiency is already built there, no need to try reinventing something. Start with the simplest, most standard thing, then look at alternatives to address concerns as those concerns are proven to be valid.
As far as EF is concerned, your Movies can reference a collection of Genres, and your Genre can optionally reference a collection of Movies. With the inclusion of a MovieGenre table containing just a composite key consisting of a MovieId and GenreId, EF can map this relationship automatically without needing to define a MovieGenres entity.
So for entities like:
[Table("Movies")]
public class Movie
{
[Key]
public int MovieId { get; set; }
public DateTime ReleaseDate { get; set; }
public string Name { get; set; }
public virtual ICollection<Genre> Genres { get; private set; } = new List<Genre>();
}
[Table("Genres")]
public class Genre
{
[Key]
public int GenreId { get; set; }
public string Name { get; set; }
public virtual ICollection<Movie> Movies { get; private set; } = new List<Movie>();
}
Then in the OnModelCreating (or using entity type configuration):
modelBuilder.Entity<Movie>()
.HasMany(x => x.Genres)
.WithMany(x => x.Movies)
.Map(x => x.ToTable("MovieGenres").MapLeftKey("MovieId").MapRightKey("GenreId"));
When you go to send Movies to a UI or out via an API for instance and you can flatten genre information as you see fit. For instance to display movies released in a date range with their genres:
var movies = context.Movies
.Where(x => x.ReleaseDate >= startDate && x.ReleaseDate <= endDate)
.Select(x => new MovieViewModel
{
MovieId = x.MovieId,
Name = x.Name,
Genres = x.Genres.Select(g => new GenreViewModel {GenreId = g.GenreId, Name = g.Name).ToList()
}).ToList();
If you're expecting a large list of movies and the UI might display the genre name, but you don't want to transmit copies of the genre for each and every movie:
// get genres for the applicable movies
var genres = context.Movies
.Where(x => x.ReleaseDate >= startDate && x.ReleaseDate <= endDate)
.SelectMany(x => x.Genres)
.Distinct(new GenreEqualityComparer())
.Select(x => new GenreViewModel{ GenreId = x.GenreId, Name = x.Name})
.ToList();
// Get the movies with their Genre IDs.
var movies = context.Movies
.Where(x => x.ReleaseDate >= startDate && x.ReleaseDate <= endDate)
.Select(x => new MovieViewModel
{
MovieId = x.MovieId,
Name = x.Name,
GenreIds = x.Genres.Select(g => g.GenreId).ToList()
}).ToList();
From here, the UI/Consumer will know what they need about the Genres, and can associate the movies to their Genre by ID without the excess payload of repeated genre information for each movie record. Again, this may not be necessary, but in cases where you might be selecting a lot of references to a relative few and/or large pieces of data you have the option to do something like above.
Related
How to give DTO to select or SelectMany
public class PersonDTO
{
public int Id { get; set; }
public string Name { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
}
var person = _context.Persons.Where(x=>x.PersonId==1).SelectMany(PersonDTO);
I want to build a service that I give model or DTO and it returns only those raws that are given in the model or DTO but only from the person table or user detail table
which means something like graphql that get data by DTO, and DTO could be Only Name or UserName or Email or all of them, any Idea?
_context.Persons.Where(x => x.PersonId == 1).Select(x => typeof(PersonDTO)); won't automagically map your type.
What you probably want is something like this, when doing it "manually":
_context.Persons
.Where(x => x.PersonId == 1)
.Select(x => new PersonDto(){
Id = x.Id,
Name = x.Name,
UserName = x.UserName,
Email = x.Email
});
I need to fetch from the database this:
rack
it's type
single shelf with all its boxes and their box types
single shelf above the previous shelf without boxes and with shelf type
Shelves have VerticalPosition which is in centimeters from the ground - when I am querying for e.g. second shelf in rack, I need to order them and select shelf on index 1.
I have this ugly EF query now:
var targetShelf = await _warehouseContext.Shelves
.Include(s => s.Rack)
.ThenInclude(r => r.Shelves)
.ThenInclude(s => s.Type)
.Include(s => s.Rack)
.ThenInclude(r => r.Type)
.Include(s => s.Rack)
.ThenInclude(r => r.Shelves)
.Include(s => s.Boxes)
.ThenInclude(b => b.BoxType)
.Where(s => s.Rack.Aisle.Room.Number == targetPosition.Room)
.Where(s => s.Rack.Aisle.Letter == targetPosition.Aisle)
.Where(s => s.Rack.Position == targetPosition.Rack)
.OrderBy(s => s.VerticalPosition)
.Skip(targetPosition.ShelfNumber - 1)
.FirstOrDefaultAsync();
but this gets all boxes from all shelves and it also shows warning
Compiling a query which loads related collections for more than one collection navigation, either via 'Include' or through projection, but no 'QuerySplittingBehavior' has been configured. By default, Entity Framework will use 'QuerySplittingBehavior.SingleQuery', which can potentially result in slow query performance.
Also I would like to use AsNoTracking(), because I don't need change tracker for these data.
First thing: for AsNoTracking() I would need to query Racks, because it complains about circular include.
Second thing: I tried conditional include like this:
.Include(r => r.Shelves)
.ThenInclude(s => s.Boxes.Where(b => b.ShelfId == b.Shelf.Rack.Shelves.OrderBy(sh => sh.VerticalPosition).Skip(shelfNumberFromGround - 1).First().Id))
but this won't even translate to SQL.
I have also thought of two queries - one will retrieve rack with shelves and second only boxes, but I still wonder if there is some single call command for this.
Entities:
public class Rack
{
public Guid Id { get; set; }
public Guid RackTypeId { get; set; }
public RackType Type { get; set; }
public ICollection<Shelf> Shelves { get; set; }
}
public class RackType
{
public Guid Id { get; set; }
public ICollection<Rack> Racks { get; set; }
}
public class Shelf
{
public Guid Id { get; set; }
public Guid ShelfTypeId { get; set; }
public Guid RackId { get; set; }
public int VerticalPosition { get; set; }
public ShelfType Type { get; set; }
public Rack Rack { get; set; }
public ICollection<Box> Boxes { get; set; }
}
public class ShelfType
{
public Guid Id { get; set; }
public ICollection<Shelf> Shelves { get; set; }
}
public class Box
{
public Guid Id { get; set; }
public Guid ShelfId { get; set; }
public Guid BoxTypeId { get; set; }
public BoxType BoxType { get; set; }
public Shelf Shelf { get; set; }
}
public class BoxType
{
public Guid Id { get; set; }
public ICollection<Box> Boxes { get; set; }
}
I hope I explained it good enough.
Query Splitting
First, I'd recommend benchmarking the query as-is before deciding whether to attempt any optimization.
It can be faster to perform multiple queries than one large query with many joins. While you avoid a single complex query, you have additional network round-trips if your DB isn't on the same machine, and some databases (e.g. SQL Server without MARS enabled) only support one active query at a time. Your mileage may vary in terms of actual performance.
Databases do not generally guarantee consistency between separate queries (SQL Server allows you to mitigate that with the performance-expensive options of serializable or snapshot transactions). You should be cautious using a multiple-query strategy if intervening data modifications are possible.
To split a specific query, use the AsSplitQuery() extension method.
To use split queries for all queries against a given DB context,
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
#"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;ConnectRetryCount=0",
o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
}
Reference.
Query that won't translate
.Include(r => r.Shelves)
.ThenInclude(s => s.Boxes.Where(b => b.ShelfId == b.Shelf.Rack.Shelves.OrderBy(sh => sh.VerticalPosition).Skip(shelfNumberFromGround - 1).First().Id))
Your expression
s.Boxes.Where(b => b.ShelfId == b.Shelf.Rack.Shelves.OrderBy(sh => sh.VerticalPosition).Skip(shelfNumberFromGround - 1).First().Id
resolves to an Id. ThenInclude() expects an expression that ultimately specifies a collection navigation (in other words, a table).
Ok, from your question I'm assuming you have a method where you need these bits of information:
single shelf with all its boxes and their box types
single shelf above the previous shelf without boxes and with shelf type
rack and it's type
Whether EF breaks up the queries or you do doesn't really make much of a difference performance-wise. What matters is how well the code is later understood and can adapt if/when requirements change.
The first step I would recommend is to identify the scope of detail you actually need. You mention that you don't need tracking, so I would expect you intend to deliver these results or otherwise consume the information without persisting changes. Project this down to just the details from the various tables that you need to be served by a DTO or ViewModel, or an anonymous type if the data doesn't really need to travel. For instance you will have a shelf & shelf type which is effectively a many-to-one so the shelf type details can probably be part of the shelf results. Same with the Box and BoxType details. A shelf would then have an optional set of applicable box details. The Rack & Racktype details can come back with one of the shelf queries.
[Serializable]
public class RackDTO
{
public int RackId { get; set; }
public int RackTypeId { get; set; }
public string RackTypeName { get; set; }
}
[Serializable]
public class ShelfDTO
{
public int ShelfId { get; set; }
public int VerticalPosition { get; set; }
public int ShelfTypeId { get; set; }
public string ShelfTypeName { get; set; }
public ICollection<BoxDTO> Boxes { get; set; } = new List<BoxDTO>();
public RackDTO Rack { get; set; }
}
[Serializable]
public class BoxDTO
{
public int BoxId { get; set; }
public int BoxTypeId { get; set; }
public string BoxTypeName { get; set; }
}
Then when reading the information, I'd probably split it into two queries. One to get the "main" shelf, then a second optional one to get the "previous" one if applicable.
ShelfDTO shelf = await _warehouseContext.Shelves
.Where(s => s.Rack.Aisle.Room.Number == targetPosition.Room
&& s.Rack.Aisle.Letter == targetPosition.Aisle
&& s.Rack.Position == targetPosition.Rack)
.Select(s => new ShelfDTO
{
ShelfId = s.ShelfId,
VerticalPosition = s.VerticalPosition,
ShelfTypeId = s.ShelfType.ShelfTypeId,
ShelfTypeName = s.ShelfType.Name,
Rack = s.Rack.Select(r => new RackDTO
{
RackId = r.RackId,
RackTypeId = r.RackType.RackTypeId,
RackTypeName = r.RackType.Name
}).Single(),
Boxes = s.Boxes.Select(b => new BoxDTO
{
BoxId = b.BoxId,
BoxTypeId = b.BoxType.BoxTypeId,
BoxTypeName = b.BoxType.Name
}).ToList()
}).OrderBy(s => s.VerticalPosition)
.Skip(targetPosition.ShelfNumber - 1)
.FirstOrDefaultAsync();
ShelfDTO previousShelf = null;
if (targetPosition.ShelfNumber > 1 && shelf != null)
{
previousShelf = await _warehouseContext.Shelves
.Where(s => s.Rack.RackId == shelf.RackId
&& s.VerticalPosition < shelf.VerticalPosition)
.Select(s => new ShelfDTO
{
ShelfId = s.ShelfId,
VerticalPosition = s.VerticalPosition,
ShelfTypeId = s.ShelfType.ShelfTypeId,
ShelfTypeName = s.ShelfType.Name,
Rack = s.Rack.Select(r => new RackDTO
{
RackId = r.RackId,
RackTypeId = r.RackType.RackTypeId,
RackTypeName = r.RackType.Name
}).Single()
}).OrderByDescending(s => s.VerticalPosition)
.FirstOrDefaultAsync();
}
Two fairly simple to read queries that should return what you need without much problem. Because we project down to a DTO we don't need to worry about eager loading and potential cyclical references if we wanted to load an entire detached graph. Obviously this would need to be fleshed out to include the details from the shelf, box, and rack that are relevant to the consuming code/view. This can be trimmed down even more by leveraging Automapper and it's ProjectTo method to take the place of that whole Select projection as a one-liner.
In SQL raw it could look like
WITH x AS(
SELECT
r.*, s.Id as ShelfId, s.Type as ShelfType
ROW_NUMBER() OVER(ORDER BY s.verticalposition) as shelfnum
FROM
rooms
JOIN aisles on aisles.RoomId = rooms.Id
JOIN racks r on r.AisleId = aisles.Id
JOIN shelves s ON s.RackId = r.Id
WHERE
rooms.Number = #roomnum AND
aisles.Letter = #let AND
r.Position = #pos
)
SELECT *
FROM
x
LEFT JOIN boxes b
ON
b.ShelfId = x.ShelfId AND x.ShelfNum = #shelfnum
WHERE
x.ShelfNum BETWEEN #shelfnum AND #shelfnum+1
The WITH uses room/aisle/rack joins to locate the rack; you seem to have these identifiers. Shelves are numbered in increasing height off ground. Outside the WITH, boxes are left joined only if they are on the shelf you want, but two shelves are returned; the shelf you want with all it's boxes and the shelf above but box data will be null because the left join fails
As an opinion, if your query is getting this level of depth, you might want to consider either using views as a shortcut in your database or use No-SQL as a read store.
Having to do lots of joins, and doing taxing operations like order by during runtime with LINQ is something I'd try my best to avoid.
So I'd approach this as a design problem, rather than a code/query problem.
In EF, All related entities loaded with Include, ThenInclude etc. produce joins on the database end. This means that when we load related master tables, the list values will get duplicated across all records, thus causing what is called "cartesian explosion". Due to this, there was a need to split huge queries into multiple calls, and eventually .AsSplitQuery() was introduced.
Eg:
var query = Context.DataSet<Transactions>()
.Include(x => x.Master1)
.Include(x => x.Master2)
.Include(x => x.Master3)
.ThenInclude(x => x.Master3.Masterx)
.Where(expression).ToListAsync();
Here we can introduce splitquery
var query = Context.DataSet<Transactions>()
.Include(x => x.Master1)
.Include(x => x.Master2)
.Include(x => x.Master3)
.ThenInclude(x => x.Master3.Masterx)
.Where(expression).AsSplitQuery.ToListAsync();
As an alternate to include this to all existing queries, which could be time consuming, we could specify this globally like
services.AddDbContextPool<EntityDataLayer.ApplicationDbContext>(options =>
{
options.EnableSensitiveDataLogging(true);
options.UseMySql(mySqlConnectionStr,
ServerVersion.AutoDetect(mySqlConnectionStr), x =>
x.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)
x.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null));
});
This will ensure that all queries are called as split queries.
Now in case we need single query, we can just override this by stating single query explicitly in individual queries. This may be done vice-versa though.
var data = await query.AsSingleQuery().ToListAsync();
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 do I filter out Grandchild elements with a Linq EF Query? This Customer has multiple transactions, and only need subchild elements with Certain ProductTypeId. Its currently bringing All ProductType Ids ignoring the filter .
var result = db.Customer
.Include(b => b.Transactions)
.Where(a => a.Transactions.Select(c=> c.ProductTypeId== productTypeId).Any())
Sql query, that I want:
select distinct c.customerName
from dbo.customer customer
inner join dbo.Transactions transaction
on transaction.customerid = customer.customerid
where transaction.ProductTypeId= 5
Customer (need 7 ProductTypeId)
Transaction ProductTypeId 2
Transaction ProductTypeId 4
Transaction ProductTypeId 5
Transaction ProductTypeId 7 <--- only need this 7 in the filter, example
Transaction ProductTypeId 7 <--- only need this 7 in the filter, example
Transaction ProductTypeId 8
Transaction ProductTypeId 8
Transaction ProductTypeId 9
*Mgmt prefers Include syntax, rather than linq to Sql .
To filter related entities you need to use projection. The purpose of an EF entity graph is to reflect the complete data state. What you want is a filtered data state. This is usually to provided relevant data to a view. That is a separate purpose.
Given a Customer/Transaction entity, use a Customer/Transaction ViewModel containing just the PKs and the properties that your view/consumer is going to need. For example:
[Serializable]
public class CustomerViewModel
{
public int CustomerId { get; set; }
public string Name { get; set; }
// ...
public ICollection<TransactionViewModel> ApplicableTransactions { get; set; } = new List<TransactionViewModel>();
}
[Serializable]
public class TransactionViewModel
{
public int TransactionId { get; set; }
// ...
}
Then, when you go to load your customers & filtered transactions:
var result = db.Customer
.Where(a => a.Transactions.Select(c=> c.ProductTypeId== productTypeId).Any())
.Select(a => new CustomerViewModel
{
CustomerId = a.CustomerId,
Name = a.Name,
// ...
ApplicableTransactions = a.Transactions
.Where(c => c.ProductTypeId == productTypeId)
.Select(c => new TransactionViewModel
{
TransactionId == c.TransactionId,
// ...
}).ToList();
}).ToList();
Leveraging Automapper for your projections can Simplify this considerably, as you can configure the entity to view model mapping (which are one-liners if the field naming is the same) then call ProjectTo and Automapper will resolve the fields needed for the SQL and construct the view models for you:
I.e.
var mappingConfig = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerViewModel>()
.ForMember(dest => dest.ApplicableTransactions,
opt => opt.MapFrom(src => src.Transactions.Where(t => t.ProductTypeId == productTypeId)
));
cfg.CreateMap<Transaction, TransactionViewModel>();
});
var result = db.Customer
.Where(a => a.Transactions.Select(c=> c.ProductTypeId== productTypeId).Any())
.ProjectTo<CustomerViewModel>(mappingConfig)
.ToList();
For the view models I would use a naming convention that reflects the view you are supplying them for as they are really only applicable to serve that view. For instance if this is reviewing transactions by customer then something like ReviewTransactionsCustomerViewModel or ReviewTransactionsCustomerVM. Different views can source different view models vs. trying to have one size fit all.
Alternatively if your code is already sending Entities to views (which I strongly discourage) there are a couple alternatives, but these do have drawbacks:
Using a wrapper view model with a filtered sub-set:
For example:
[Serializable]
public class ReviewTransactionsViewModel
{
public Customer Customer { get; set; }
public ICollection<Transaction> ApplicableTransactions { get; set; } = new List<Transaction>();
}
Then when selecting:
var result = db.Customer
.Where(a => a.Transactions.Select(c=> c.ProductTypeId== productTypeId).Any())
.Select(a => new ReviewTransactionsViewModel
{
Customer = a,
ApplicableTransactions = a.Transactions
.Where(c => c.ProductTypeId == productTypeId)
.ToList();
}).ToList();
Then in your view, instead of the #Model being a Customer, it becomes this view model and you just need to tweak any references to use Model.Customer.{property} rather than Model.{property} and importantly, any references to Model.Transactions should be updated to Model.ApplicableTransactions, not Model.Customer.Transactions.
The caveat to this approach is that for performance you should disable lazy loading on the DbContext instance populating your model to send back, and only eager-load the data your view will need. Lazy loading will get tripped by code serializing entities to send to a view which can easily be a major performance hit. This means any references to Model.Customer.Transactions will be empty. It also means that your model will not represent a complete entity, so when passing this model back to the controller you need to be aware of this fact and not attempt to attach it to use as a complete entity or pass to a method expecting a complete entity.
Filter the data into a new entity: (Treat entity as view model)
For example:
var result = db.Customer
.Where(a => a.Transactions.Select(c=> c.ProductTypeId== productTypeId).Any())
.Select(a => new Customer
{
CustomerId = a.CustomerId,
Name = a.Name,
// ... Just the values the view will need.
Transactions = a.Transactions
.Where(c => c.ProductTypeId == productTypeId)
.ToList();
}).ToList();
This can be an attractive option as it requires no changes to the consuming view but you must be cautious as this model now does not reflect a complete data state when/if passed back to the controller or any method that may assume that a provided Customer is a complete representation or a tracked entity. I believe you can leverage an Automapper confguration for <Customer, Customer> to help facilitate the filtering and copying across only applicable columns, ignoring unneeded related entities etc.
In any case, this should give you some options to weigh up risks vs. effort.
a better approach is to use Transactions dbset to start the query with Include Customer and then apply your filters on transactions, after that Select customerName from results with Distinct expression to get unique customers name.
So, try to write queries as you expect them from SQL.
var namesAll =
from customer in db.Customer
from transaction in customer.Transactions
where transaction.ProductTypeId == 5
select customer.CustomerName;
var result = namesAll.Distinct();
Lambda syntax (method chain), IMHO which is worst readable.
var result = db.Customer
.SelectMany(customer => customer.Transactions,
(customer, transaction) => new {customer, transaction})
.Where(pair => pair.transaction.ProductTypeId == 5)
.Select(pair => pair.customer.CustomerName)
.Distinct();
If I correctly understand what do you need, try a solution like this:
My test models:
public sealed class Person
{
public Guid Id { get; set; }
public DateTime? Deleted { get; set; }
public string Email { get; set; }
public string Name { get; set; }
public int? B { get; set; }
public IList<Vehicle> Vehicles { get; set; } = new List<Vehicle>();
}
public sealed class Vehicle
{
public Guid Id { get; set; }
public int ProductTypeId { get; set; }
public Guid PersonId { get; set; }
public Person Person { get; set; }
}
Query:
var queueItem = await _context.Persons
.Where(item => item.Vehicles.Any(i => i.ProductTypeId == 1))
.Select(item => new Person
{
Id = item.Id,
//Other props
Vehicles = item.Vehicles.Where(item2 => item2.ProductTypeId == 1).ToList()
})
.ToListAsync();
Sql from profiler:
SELECT [p].[Id], [t].[Id], [t].[PersonId], [t].[ProductTypeId]
FROM [Persons] AS [p]
LEFT JOIN (
SELECT [v].[Id], [v].[PersonId], [v].[ProductTypeId]
FROM [Vehicle] AS [v]
WHERE [v].[ProductTypeId] = 1
) AS [t] ON [p].[Id] = [t].[PersonId]
WHERE EXISTS (
SELECT 1
FROM [Vehicle] AS [v0]
WHERE ([p].[Id] = [v0].[PersonId]) AND ([v0].[ProductTypeId] = 1))
ORDER BY [p].[Id], [t].[Id]
One more variant:
var queueItem1 = await _context.Vehicle
.Where(item2 => item2.ProductTypeId == 1)
.Include(item => item.Person)
.Distinct()
.ToListAsync();
var list = queueItem1
.GroupBy(item => item.Person)
.Select(item => new Person
{
Id = item.First().Person.Id,
//Other props
Vehicles = item.ToList()
})
.ToList();
Sql from profiler:
SELECT [t].[Id], [t].[PersonId], [t].[ProductTypeId], [p].[Id], [p].[B],
[p].[Deleted], [p].[Email], [p].[Name]
FROM (
SELECT DISTINCT [v].[Id], [v].[PersonId], [v].[ProductTypeId]
FROM [Vehicle] AS [v]
WHERE [v].[ProductTypeId] = 1
) AS [t]
INNER JOIN [Persons] AS [p] ON [t].[PersonId] = [p].[Id]
I am just sending this data to reporting Engine(SSRS) in Asp.net MVC5.
Everything is fine, but this query is taking a lot of time since I have to loop through ListProducts (I guess ListProducts is the size of the database matches).
I am just looking for a way to optimize this query.
I have tried any and contains (as seen below), but they do not seem to work in single table.
context.Products.Where(w => w.ProductDetail.Any(a => a.startDate >= startDate
&& a.endDate <= endDate))
I got this from here
2)I tried this as well
context.Products.Where(w => ListProducts.Any(x =>w.Contains(x)))
but this also does not work and generates a compile time error that
System.Guid does not contains definition of 'Contains'
Is there any other way, or i am doing it the only correct way?
foreach (var item in ListProducts)
{
List.AddRange(_context.Products.Where(w => w.ProductId== item).Select(q => new ProductVM
{
Name = q.Name,
Quantity = q.Quantity,
}).ToList().Select(item=> new ProductVM
{
Name = item.Name,
Quantity = item.Quantity,
}).ToList());
}
public class Product
{
public Nullable<System.Guid> ProductId { get; set; }
public string Name { get; set;}
public decimal Quantity { get; set; }
}
Ok, can you do:
var thelist = _context.Products.Where(n => ListProducts.Contains(n.ProductId)).Select(n => new ProductVM
{
Name = n.Name,
Quantity = n.Quantity
}).ToList();