Entity.HasRequired returning items with NULL property - c#

I have two related entities built and linked with Fluent API.
public class EDeal : IEntityBase
{
public int ID { get; set; }
public string Customer_id { get; set; }
public virtual ECustomer Customer { get; set; }
...etc
}
public class ECustomer : IEntityBase
{
public int ID { get; set; }
public string Customer_id { get; set; }
public string Customer_name { get; set; }
public virtual ICollection<EDeal> Deals { get; set; }
...etc
}
linked with
modelBuilder.Entity<ECustomer>().HasKey(c => c.Customer_id);
modelBuilder.Entity<EDeal>().HasRequired<ECustomer>(s => s.Customer)
.WithMany(r => r.Deals)
.HasForeignKey(s => s.Customer_id);
I recognize that this is inefficient linking but I had to link it in this way because I don't have control over the db structure.
The important thing to note is that the EDeal requires an ECustomer (.HasRequired). The database contains many rows in EDeal that have a null Customer_id field and I do not want to ever pull those lines when I query the entity.
I thought that the .HasRequired would make sure that I never got back any EDeals that do not have ECustomers associated with them but that doesn't seem to be the case. Instead, it only seems to ignore those lines with NULL Customer_id values when I try to order by a property in the Customer. And even then, returning the .Count() of the query behaves strangely.
var count1 = db.Set<EDeal>().Count(); //returns 1112
var count2 = db.Set<EDeal>().ToList().Count(); //returns 1112
var count3 = db.Set<EDeal>().OrderBy(c => c.Customer.Customer_name).Count(); //returns 1112
var count4 = db.Set<EDeal>().OrderBy(c => c.Customer.Customer_name).ToList().Count(); //returns 967
I know I can add a .Where(c => c.Customer.Customer_id != Null) to make sure I only get back what I'm looking for, but I'm hoping for a solution in the Entity's configuration because I have many generic functions acting on my IEntityBase class that build dynamic queries on generic Entities and I don't want to use a workaround for this case.
Questions:
1) Is there a way to limit the entity to only return those EDeals that have a corresponding ECustomer?
2) In my example above, why do count3 and count4 differ?
Thanks in advance.

Related

EF Core: loading related entities as only ids

I'm not sure what I want is something one should expect from EF, but I think I've seen this in other ORMs and this should be quite common for it to be solved in EF - so I'm asking if it has been.
I want to be able to eagerly load foreign key entities when querying EF, but only their ids, as the other part of objects will be loaded much later.
I have:
class A {
public int Id { get; set; }
public B B { get; set; }
}
class B {
public int Id { get; set; }
// .... more properties
}
And a web API, that should return a list of all As, with this view model:
class AViewModel {
public int Id { get; set; }
public int BId { get; set; }
}
I want to make sure I do not include B table join when querying - for performance reasons. I'm also using automapper to map from A to AViewModel.
Currently the best way I found to do this is:
var a = context.As;
var aList = a.Select(x => new { model = x, bid = x.B.Id }).ToList();
return Ok(mapper.Map<List<AViewModel>(aList));
Unfortunately this means that I have to add mapping from new { model = x, bid = x.B.Id } to AViewModel, that is really unconvenient.
I'd prefer to just be able to write:
var a = context.As;
var aList = a.ToList();
return Ok(mapper.Map<List<AViewModel>(aList));
But in this case it fails with NullReferenceException, because every item of aList has B property null.
I could write context.As.Include(x => x.B) but this will join B table, that I would like to avoid.
I think I have seen some ORMs being able to fill in B objects with empty objects, except for ids - and that is the behavior I'm looking for in EF. Can it do that?
If not maybe one can suggest a nicer way of solving a problem? maybe I can fix this somehow with lazy proxies?
Put the foreignKey property on the A class:
class A {
public int Id { get; set; }
public int BId {get; set;}
[ForeignKey("BId")] //can do this in the fluent API instead
public virtual B B { get; set; }
}
Then you can just use the class A in the mapping with no need to load the B entity

Use Where Clause on navigation property. Many-to-many relationship

I have been looking through other examples on SO and I am still unable to resolve this.
I have the following model structure
public class Event
{
[Key]
public int ID { get; set; }
public ICollection<EventCategory> EventCategories{ get; set; }
}
public class Category
{
[Key]
public int ID { get; set; }
public ICollection<EventCategory> EventCategories{ get; set; }
}
public class EventCategory
{
[Key]
public int ID { get; set; }
public int EventID{ get; set; }
public Event Event{ get; set; }
public int CategoryID{ get; set; }
public Category Category{ get; set; }
}
From my Events controller I am trying to use a LINQ query to only show Events where the CategoryID is equal to 1 but i keep on coming into errors with my WHERE clause I think.
UPDATE:
I have been trying multiple queries but at present it is
var eventsContext = _context.Events
.Include(e => e.EventCategories)
.Include(e=>e.EventCategories.Select(ms => ms.Category))
.Where(e=>e.ID==1)
.Take(15)
.OrderByDescending(o => o.StartDate);
This is the error I get
TIA
First, the lambda passed to Include must be a model expression. Specifically, that means you cannot use something like Select. If you're trying to include EventCategories.Category, then you should actually do:
.Include(e => e.EventCategories).ThenInclude(ms => ms.Category)
That will fix your immediate error. The next issue is that the way in which you're attempting to query the category ID is incorrect. The lamdas don't carry over from one clause to the next. In other words, when you're doing Where(e => e.ID == 1), e is Event, not Category. The fact that you just included Category doesn't limit the where clause to that context. Therefore, what you actually need is:
.Where(e => e.EventCategories.Any(c => c.CategoryID == 1))
For what it's worth, you could also write that as:
.Where(e => e.EventCategories.Any(c => c.Category.ID == 1))
Notice the . between Category and ID. Now this where clause requires joins to be made between all of Event, EventCategories, and Category, which then means you don't actually need your Include(...).ThenInclude(...) statement, since all this does is tell EF to make the same JOINs it's already making. I will still usually do the includes explicitly, though, as otherwise, if your where clause were to change in some future iteration, you may end up no longer implicitly including everything you actually want included. Just food for thought.

Automapper projection returns empty list when query is not empty

I'm implementing automapper for view models in a project and I'm just getting started with it. A hurdle I'm finding myself up against is a type of behavior I'm not familiar with in Automapper. I've been using automapper on a couple projects now and I haven't come across this issue before.
Basically in code this is what's happening:
var q = dbContext.Orders.Where(x => x.Name == 'name');
var l = q.ToList(); // count == 10
var shared = q.ProjectTo<OrderSharedModel>().ToList(); // count == 0
I figured out that when mapping a property that's null, it doesn't map anymore and it's like it just skips the mapping of that entity entirely.
For example:
class Order {
public int OrderId { get; set; }
public string Name { get; set; }
public int? OrderTypeId { get; set; }
public virtual OrderType { get; set; }
}
class OrderSharedModel {
public int OrderId { get; set; }
public string Name { get; set; }
public OrderTypeSharedModel OrderType { get; set; }
}
If OrderType is null in Order then it will cause ProjectTo<OrderSharedModel>().ToList() to return an empty list. If I commented out OrderType in the shared model, ProjectTo<OrderSharedModel>().ToList() will return the full list. This is weird to me.
Why would ProjectTo<OrderSharedModel>().ToList() return an empty list if the original query before ProjectTo() is not empty? Why won't automapper map a null property as null rather than skip the mapping entirely and return an empty list?
--UPDATE--
However if I use the other way mapping:
var l = q.ToList();
var sm = Mapper.Map<List<Order>, List<OrderSharedModel>>(l);
It works great. I'm doing this for now until I find exactly why the queryable extension breaks.
Just encountered the same issue, an inner property of the dto was null and for some reason it was not in the result list. I upgraded from AutoMapper 8.1 to 9.0 and it works fine now.

Linq query to nest 3 tables and select certain records

I'm building a simple tour booking app.
It has 3 linked tables, setup as below.
Given a specific TourCategoryId and a date (dte), I'm trying to build an object which I can then go on to populate a viewmodel with.
However I'm not understanding how to check the TourDate field against dte.
Am I trying to do too much in one statement? Can anyone help me with what I thought would be a simple query?
Thanks for any advice:
My linq is:
var tours = db.Tours
.Include(x => x.TourDate)
.Include(x => x.TourDate.Select(b => b.Booking))
.Where(t => t.TourCategoryId == id &&
t.TourDate.Any(td => td.Date==dte));
(also pseudo code)
.Where(v => v.Booking.NumberBooked.Sum() < v.Tours.PlacesAvailable)
I've updated this with suggestions below, but it is returning all td.Date - and not filtering, as you can see from the screenshot below, showing all 3 records returned, and not just those where td.Date == dte:
Is it the .Any that is maybe not correct?
The end result should be
List of Tours => List of Dates => List of Bookings - where the dates are filtered to the dte eg:
Tour1- night tours
--TourDate - filtered to 02/03/2015
----Booking1
----Booking2
----Booking3
Tour2- day tours
--TourDate - filtered to 02/03/2015
----Booking1
----Booking2
Tour3- multi-day tours
--TourDate - filtered to 02/03/2015
----Booking1
----Booking2
----Booking3
----Booking4
Thanks again, Mark
public class Tour
{
public int TourId { get; set; }
public int TourCategoryId { get; set; }
public string TourName { get; set; }
public int PlacesAvailable { get; set; }
public virtual ICollection<TourDate> TourDate { get; set; }
}
public class TourDate
{
public int TourDateId { get; set; }
public int TourId { get; set; }
public DateTime Date { get; set; }
public virtual Tour Tour { get; set; }
public virtual ICollection<Booking> Booking { get; set; }
}
public class Booking
{
public int BookingId { get; set; }
public int TourDateId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int NumberBooked { get; set; }
public virtual TourDate TourDate { get; set; }
}
From what I understand you want any Tour in TourCategoryId which has a TourDate on dte.
This may work for you.
var tours =
db.Tours
.Include(x => x.TourDate)
.Include(x => x.TourDate.Select(b => b.Booking))
.Where(t => t.TourCategoryId == id &&
t.TourDate.Any(td => td.Date == dte));
To filter the TourDate.
foreach(var tour in tours)
{
tour.TourDate = tour.TourDate.Where(td => td.Date == dte).ToList()
}
The problem you are facing is that the business object you work with does not fit in the class hierarchy you use.
Try not to work strictly with the database mapped classes. Your business objects in this case are something different from just a list of instances of the Tour class that are matched to the db strucure. Just use the db classes to form a query, with the help of all the navigation properties you created, but dont try to fit all the queries you need to the same class hierarchy as you need for a view model.
You should build your query around bookings, not tours. select the bookings matching your criteria, including the one in pseudocode, then group by the dates and tours and select some kind of anonimous object, representing the groups. Then translate those to view model classes you need, not necessary all of which are database mapped.
In many cases, you do not even need ro hydrate an entity just to get some property loaded.
No need to load an elephant just to be able to take a picture.
Include is really just a workaround, like killing the elephant and loading by parts.

Entity Framework - set model property value dynamically from a query

I have the following models:
public class A_DTO
{
[Key]
public string Id { get; set; }
**public virtual B_DTO B { get; set; }**
public virtual List<B_DTO> Bs { get; set; }
}
public class B_DTO
{
[Key]
public string Id { get; set; }
public string AId { get; set; }
public string UserId {get; set; }
[ForeignKey("AId"]
public virtual A_DTO A { get; set; }
[ForeignKey("UserId"]
public virtual User User { get; set; }
}
I am trying to get a list of object A_DTO but also including property B:
using AutoMapper.QueryableExtensions;
public IQueryable<A_DTO> GetAllA_DTO()
{
string userId = "8b6e9332-7c40-432e-ae95-0ac052904752";
return context.A_DTO
.Include("Bs")
.Include("B")
.Project().To<A_DTO>()
.Where(a => a.Bs.Any(b => b.UserId == userId));
}
How do I dynamically set this property according to set UserId and A_DTO.Id?
Here is a bag of observations in which you may be lucky enough to find your solution:
The B property in a code first model will result in there being a foreign key in the database table for A_DTOs that contains a reference to the B_DTOs table. Entity Framework will expect to own the responsibility for filling the B navigation property with an object populated with the data from the referenced row in the B_DTOs table, hence you would not be able to change it dynamically.
There is no need to use the Automapper Project method if your source type and destination type are the same. In your example they would both appear to be A_DTO. Are you sure you don't actually intend to have an entity "A" that is included in the context and "A_DTO" that is mapped from "A" via Automapper? If that is what you really want then you could have code in a .Select call mapping A.Bs.FirstOrDefault(b => b.UserId == userId) to A_DTO.B. However, you would not be able to apply filtering on the basis of the userId in an Automapper map.
Without seeing any of the Automapper Map setup code, it is difficult to get an idea of intent here.
As an aside, when using .Include it is better, in my opinion, to use the overload that takes an expression. In your case the includes would be rewritten:
.Include(a => a.B)
.Include(a => a.Bs)
Using this overload ensures that you will get compile time errors if you rename a property but fail to update the string in the .Include statement.

Categories