EF Core and ASP.net MVC Query too large - entity-framework

I'm trying to determine the best way to either change my data model or linq query to reduce redudant SQL columns being queried.
i have a customer model , item model, and customerItem join entity.
i need the customer name and item name from customer model and item model respectively.
if i view the SQL with SQL profiler, its Select * from all 3 tables.
How I can select * from customerITem ONLY include customer.customerName,Item.ItemName ?
var aMRSContext = _context.CustomerItems.Include(c => c.Customer).Include(c => c.Item);

You could use a ViewModel to display the data:
public class CustomerItemVM
{
public string CustomerName { get; set; }
public string ItemName { get; set; }
}
Controller
var aMRSContext = _db.CustomerItems
.Select(c=>new CustomerItemVM {
CustomerName=c.Customer.CustomerName,
ItemName=c.Item.ItemName
})
.ToList();

Related

EF Core - unincluding binrary fields

I have an EF Core model that has a binary field
class SomeModel {
string Id;
string otherProperty;
byte[] blob;
};
Usually, when I query the DB, I want to return a list of this Model - and then, on subsequent calls, query just a single entity, but return the blob.
I can't see a way in either data or code first to prevent EF Core paying the cost of retrieving the blob field always.
I really want to be able to say something like:
var list = await Context.SomeModels.ToListAsync();
// later
var item = await Context.SomeModels
.Where(m=>m.Id==someId)
.Include(m=>m.blob)
.FirstOrDefaultAsync();
I think I might have to put the blobs into a 2nd table so I can force a optional join.
The only way you could get a separate loading is to move the data to a separate entity with one-to-one relationship.
It doesn't need to be a separate table though. Although the most natural choice looks to be owned entity, since owned entities are always loaded with the owners, it has to be a regular entity, but configured with table splitting - in simple words, share the same table with the principal entity.
Applying it to your sample:
Model:
public class SomeModel
{
public string Id { get; set; }
public string OtherProperty { get; set; }
public SomeModelBlob Blob { get; set; }
};
public class SomeModelBlob
{
public string Id { get; set; }
public byte[] Data { get; set; }
}
Configuration:
modelBuilder.Entity<SomeModelBlob>(builder =>
{
builder.HasOne<SomeModel>().WithOne(e => e.Blob)
.HasForeignKey<SomeModelBlob>(e => e.Id);
builder.Property(e => e.Data).HasColumnName("Blob");
builder.ToTable(modelBuilder.Entity<SomeModel>().Metadata.Relational().TableName);
});
Usage:
Code:
var test = context.Set<SomeModel>().ToList();
SQL:
SELECT [s].[Id], [s].[OtherProperty]
FROM [SomeModel] AS [s]
Code:
var test = context.Set<SomeModel>().Include(e => e.Blob).ToList();
SQL:
SELECT [e].[Id], [e].[OtherProperty], [e].[Id], [e].[Blob]
FROM [SomeModel] AS [e]
(the second e.Id in the select looks strange, but I guess we can live with that)

Entity Framework Conditional Include()

public partial class Table
{
public int TableId { get; set; }
public Nullable<int> Num { get; set; }
public int NbSeats { get; set; }
public Nullable<int> R_LocationId { get; set; }
public virtual ICollection<Availibility> Availibilities { get; set; }
public virtual R_Locations R_Locations { get; set; }
}
public partial class Availibility
{
public System.DateTime DayRes { get; set; }
public Nullable<bool> Available { get; set; }
public int TableId { get; set; }
public virtual Table Table { get; set; }
}
I would like to implement this request with entity Framework:
select *
from Tables join Availibilities
on Availibilities.TableId = Tables.TableId
where Availibilities.Available=1
One of the slower parts of fetching data from a database is the transport of the data from the DBMS to your local process. Hence you should not transport more values than you actually plan to use.
Your Table has zero or more Availabilities. Your database implements this by giving the Availability table a foreign key to the Table that it belongs to.
So if you have a table with Id 4, which has 100 Availabilities, and you would query the Table with its Availabilities using a Join and Include you would transfer the foreign key Availability.TableId a 100 times, while you already know they will all have the value of Table.Id. You even know this value, because you asked for table with Id 4.
Hence, unless you plan to Update retrieved values, always use Select instead of querying complete classes.
Back to your question
Given a tableId, you want information of the table together with (some or all) its Availabilities.
Thanks to entity framework you don't have to use Join. If you use the collections, entity framework will do the join for you.
var tableWithAvailabilities = myDbContext.Tables
.Where(table => table.TableId == tableId)
.Select(table => new
{
// select only the properties you plan to use
Id = table.TableId,
Num = table.Num,
...
Availabilities = table.Availabilities
.Where(availability => ...) // if you don't want all Availabilities
.Select(availability => new
{
// again: select only the properties you plan to use
// not needed, you know it equals Table.TableId
// Id = availability.TableId,
Date = availability.DayRes,
Available = availability.Avialable,
})
.ToList(),
});
Entity framework knows which Join is needed for this. One of the nicer things when using the Collections instead of a Join is that you make your database more abstract: you really are thinking of a Table that has zero or more Availabilities. It is a shortage of a DBMS that it needs two tables to implement this. If your internal tables changes, for instance the name of the foreign key, your query does not change, because you don't use the foreign key
If you are not planning to update a fetched value, then it is seldom wise to fetch
There is two way to filter include Entity.
Using a projection (See #Harald answer)
Using a third party library
Disclaimer: I'm the owner of the project Entity Framework Plus
(
The EF+ Query IncludeFilter allow easily filter included entities. It works with EF6.x
return context.Tables
.IncludeFilter(x => x.Availibilities .Where(c => c.Available == 1))
.ToList();
Under the hood, the library does exactly a projection.
One limitation is all Include but now be called with IncludeFilter even if no filter is specified such as the account.
Wiki: EF+ Query Include Filter

Linq to Entities Many to Many return nested objects

If I have three tables in a many to many relationship, including the junction table, Students, StudentCourses, Courses:
How can I return the Student objects with their associated course objects?
To further clarify, here is the Student View model:
public class StudentViewModel
{
public int StudentID { get; set; }
public string Title { get; set; }
public string Name { get; set; }
...
...
...
public ICollection<StudentCourseViewModel> StudentCourses { get; set; }
}
The following Linq to Entities query only returns student objects, hence my problem!
var query = from student in context.Students
from studentcourse in student.StudentCourses
where studentcourse.CourseID == 4
select student;
Unfortunately, in the View when I debug the student object, there are no studentcourses being returned. Looking at the syntax of the query, this makes sense as it is only returning students.
I've tried projection, e.g.
var query = from student in context.Students
from studentcourse in student.StudentCourses
where studentcourse.CourseID == 4
select new
{
student,
StudentCourses = studentcourse
}
But projection reshapes the output Student object and does not meet the Student shape(type) since I'm using the Student view model.
Here is a snippet of my View code:
#foreach (var item in Model)
{
#Html.DisplayFor(s => item.Name)
...
...
#foreach (var subItem in item.StudentCourses)
{
#Html.DisplayFor(sc => subItem.Description)
#Html.DisplayFor(c => subItem.Course.Name)
...
So, really stuck at this point. I would have thought this would be very simple, but I've spent an entire day researching, trial and error.
You can force the related entities to be loaded by using Include:
var query = from student in context.Students
.Include("StudentCourses")
.Include("StudentCourses.Course")
where studentcourse.CourseID == 4
select student;

Why does Entity Framework 5 query different tables when executing a .ToList() versus a .Count() on the same entity?

I am using Entity Framework to map two tables together using Entity Splitting as outlined here and here.
I have found that if I execute a .ToList() on an IQueryable<SplitEntity> then the results are from an Inner Join. However, If I take that same IQueryable and execute a .Count() it will return the number of records returned by a Full Join.
Here is a unit test that fails:
[TestMethod]
public void GetCustomerListTest()
{
// arrange
List<Customer> results;
int count;
// act
using (var context = new DataContext())
{
results = context.Customers.ToList();
count = context.Customers.Count();
}
// assert
Assert.IsNotNull(results); // succeeds
Assert.IsTrue(results.Count > 0); // succeeds. Has correct records from inner join
Assert.AreEqual(count, results.Count); // This line fails. Has incorrect count from full join.
}
This strikes me as very bad. How can I get the .Count() method to return the results from an Inner Join like the .ToList()?
Update - SQL
I was wrong about the full vs inner joins.
The .ToList() results in:
SELECT
[Extent1].[CustomerNumber] AS [CustomerNumber],
-- ...etc...
[Extent2].[CustomerName] AS [CustomerName],
-- ... etc...
FROM [dbo].[CustomerTable1] AS [Extent1]
INNER JOIN [dbo].[CustomerTable2] AS [Extent2] ON [Extent1].[CustomerNumber] = [Extent2].[CustomerNumber]
The .Count() results in:
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM [dbo].[customerTable2] AS [Extent1]
) AS [GroupBy1]
Update - DataContext and entity code
The DataContext:
public class DataContext : DbContext
{
public DataContext() { Database.SetInitializer<DataContext>(null); }
public DbSet<Customer> Customers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new CustomerMapping());
}
}
}
The Customer Mapping (FluentAPI):
public class CustomerMapping : EntityTypeConfiguration<Customer>
{
public CustomerMapping()
{
this.Map( m => {
m.Properties( x => new { x.CustomerNumber, /*...etc...*/});
m.ToTable("CustomerTable1");
})
.Map( m => {
m.Properties( x => new { x.CustomerName, /*...etc...*/});
m.ToTable("CustomerTable2");
});
}
}
The Customer entity:
public class Customer
{
[Key]
public string CustomerNumber { get; set; }
public string CustomerName { get; set; }
}
If the database and all records in CustomerTable1 and CustomerTable2 have been created by Entity Framework and SaveChanges calls in your application code this difference must not happen and you can go straight ahead and report this as a bug.
If you are mapping to an existing database or if other applications write records into the tables and you actually expect that not every record in CustomerTable1 has a corresponding record in CustomerTable2 and vice versa then Entity Splitting is the wrong mapping of your database schema.
Apparently the difference means that you can have Customers with a CustomerNumber (etc.), but without a CustomerName (etc.) - or the other way around. The better way to model this would be a one-to-one relationship where one side is required and the other side is optional. You will need an additional entity and a navigation property for this, for example like so:
[Table("CustomerTable1")]
public class Customer
{
[Key]
public string CustomerNumber { get; set; }
// + other properties belonging to CustomerTable1
public AdditionalCustomerData AdditionalCustomerData { get; set; }
}
[Table("CustomerTable2")]
public class AdditionalCustomerData
{
[Key]
public string CustomerNumber { get; set; }
public string CustomerName { get; set; }
// + other properties belonging to CustomerTable2
}
With this Fluent API mapping:
public class CustomerMapping : EntityTypeConfiguration<Customer>
{
public CustomerMapping()
{
this.HasOptional(c => c.AdditionalCustomerData)
.WithRequired()
.WillCascadeOnDelete(true);
}
}
I am querying a local table and I get the same count for both. I believe there is a problem with your context and that's why your results are inconsistent.
screenshot of essentially the same code just querying a simple dataset.
UPDATE:
I don't know why the SQL that is generated is different. You would think that they would be the same except for simply executing Count(*) instead of returning all the rows. That is obviously why you are getting a different counts. I just can't say why the SQL is different.
Maybe Jon Skeet or other genius will see this and answer! :)

Entity splitting with optional relation - (EF code first)

I have 2 tables: an Orders table and an OrderActivity table. If no activity has been taken on an order, there will be no record in the OrderActivity table. I currently have the OrderActivity table mapped as an optional nav property on the Order entity and I handle updates to OrderActivity like this:
if (order.OrderActivity == null)
{
order.OrderActivity = new OrderActivity();
}
order.OrderActivity.LastAccessDateTime = DateTime.Now;
Is it possible to consolidate this such that the columns of the OrderActivity table are mapped to properties on the Orders entity, and will default if there is no OrderActivity record? Configuration for entity splitting only appears to work if records exist in both tables. If it is not possible, what is the best practice to obscure the child entity from my domain model? My goal is to keep the model as clean as possible while interacting with a DB schema that I have no control over.
You can create the mapping and specify the type of LastAccessDate as Nullable<DateTime>. The mapping will create one-to-one with LastAccessDate being optional.
public class Order {
[Key]
public int Id { get; set; }
public string Name { get; set; }
public DateTime? LastAccessDate { get; set; }
}
modelBuilder.Entity<Order>().Map(m => {
m.Properties(a => new { a.Id, a.Name });
m.ToTable("Order");
}).Map(m => {
m.Properties(b => new { b.Id, b.LastAccessDate });
m.ToTable("OrderActivity");
});
In this case, specifying LastAccessDate property is optional when inserting new orders.
var order = new Order();
order.Name = "OrderWithActivity";
order.LastAccessDate = DateTime.Now;
db.Orders.Add(order);
db.SaveChanges();
order = new Order();
order.Name = "OrderWithoutActivity";
db.Orders.Add(order);
db.SaveChanges();
Note this will always create one entry in each table. This is necessary because EF creates INNER JOIN when you retrieve Orders and you want to get all orders in this case. LastAccessDate will either have a value or be null.
// Gets both orders
var order = db.Orders.ToList();
// Gets only the one with activity
var orders = db.Orders.Where(o => o.LastAccessDate != null);