EF Improve performance of Many-to-Many / .Include .ThenInclude - sql-server

I've got a relatively basic model - Users and Tags. There is a fixed list of Tags. A User can have multiple Tags and a Tag can be used by multiple users.
I had gone with structure below and finding performance issues when returning results.
public class User
{
public string Id {get; set;}
public virtual List<UserTag> UserTags {get; set}
}
public class UserTag
{
public string UserId { get; set; }
public User User { get; set; }
public int TagId { get; set; }
public Tag Tag{ get; set; }
}
public class Tag
{
[Key]
public int TagId { get; set; }
public string Name { get; set; }
public virtual List<UserTag> UserTags { get; set; }
}
I have the following query which is takings a long time (several seconds):
var x = db.Users.Include(u => u.UserTags).ThenInclude(u => u.Trait).ToList<User>();
I have tried writing it as such, which has improved the time, however it is still taking too long:
db.UserTags.Load();
db.Tags.Load();
var x = db.Users.ToList<User>();
Is there any other way to speed this up? Running a query directly in SQL SMS is almost instant (e.g.
select * from Users u left outer join UserTags t on t.UserId = u.Id)
In terms of data rows, it is apx Tags: 100, UserTags:50,000, Users: 5,000

First you can check how EF translates your request to SQL Server - therefore use the "SQL Server Profiler"
Then you could use the genereated query to check if there might be an missing index which speeds up the query
You also can try to write a Join instead of ThenInclude and see how the query then behaves
best regards
Jimmy

Related

EF Core 6.0 Multiple relationships between two tables

Let's say I have two tables - User and TrainingGroup.
Relation between these two tables has to be like this:
User can be in only one TrainingGroup
One TrainingGroup can have multiple users
The problem happens because I need the following relationships as well:
User can coach many TrainingGroups (group coach, eg. kids group, adults group etc.)
Group can have only one user (coach)
Also, it is not possible for a user to be a coach and member of the group at the same time.
Is it possible to achieve that, and how? Any other design ideas?
Code block below roughly explains what I'm trying to achieve:
public class TrainingGroup
{
public User Coach { get; set; } // Coach of the group
public int CoachId { get; set; }
public List<User> Members { get; set; } //Members in the training group
}
public class User
{
public int TrainingGroupId { get; set; }
public TrainingGroup TrainingGroup { get; set; } // Group the user (Member) is training in
public List<TrainingGroup> CoachingGroups { get; set; } // Groups that the user is coaching
}

Azure Search - Query

So I'm using the C# nuget wrapper around Azure Search. My problem is I have a index of products:
public class ProductDocument
{
[System.ComponentModel.DataAnnotations.Key]
public string Key { get; set; }
[IsSearchable]
public string Sku { get; set; }
[IsSearchable]
public string Name { get; set; }
[IsSearchable]
public string FullDescription { get; set; }
[IsSearchable]
public List<CustomerSkuDocument> CustomerSkus { get; set; }
}
public class CustomerSkuDocument
{
[IsSearchable]
public int AccountId { get; set; }
[IsSearchable]
public string Sku { get; set; }
}
Example data would be:
new Product() { Key= 100,Name="Nail 101",Sku = "CCCCCCCC", CustomerSkus = new List<ProductCustomerSku>()
{
new ProductCustomerSku() {AccountId = 222, CustomerSku = "BBBB"},
new ProductCustomerSku() {AccountId = 333, CustomerSku = "EEEEEEE"}
}
So the problem is around CustomerSkuDocument.
When I Search I need to pass the AccountId in as well as the search term, however the AccountId is only used for when searching the ProductCustomerSkus.
Basically an Account can have different customer skus but it's only associated to that account - I don't want a separate index per account.
So my call would be something like /AccountId=222&term=BBBB which would find the match.
However /AccountId=333&term=BBBB would not find a match.
So I'm calling it like:
SearchParameters sp = new SearchParameters();
sp.SearchMode = SearchMode.Any;
sp.QueryType = QueryType.Full;
DocumentSearchResult<ProductDocument> results =
productIndexClient.Documents.Search<ProductDocument>(term, sp);
Where term is the normal search term, tried it with adding the AccountId but it doesn't work.
Azure Search does not support repeating data structures nested under a property of the outer document. We're working on this (see https://feedback.azure.com/forums/263029-azure-search/suggestions/6670910-modelling-complex-types-in-indexes), but we still have some work to do before we can release that.
Given that, the example you're showing is not probably indexing the nested parts. Can you post the search index definition you're using? While we work in direct support for complex types, you can see your options for approach here: https://learn.microsoft.com/en-us/azure/search/search-howto-complex-data-types
From the above you'll arribe at a index structure that will also guide your query options. If all you need is equality, perhaps you can simply include the accountId and the SKU in the same field and use a collection field so you can have multiple instances. For your query you would issue a search query that requires the accountId and has the rest as optional keywords.

Auto assign column value when row is created

I am using EF6 and SQL Server 2014.
I have something like the following simplified model:
public class Order
{
public int Id { get; set; }
public DateTime Created { get; set; }
public IEnumerable<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public int Id { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; }
public string ItemName { get; set; }
public int OrderItemNumber { get; set; }
}
The Id properties are standard auto-increment primary keys.
The OrderItem.OrderItemNumber property uniquely identifies an OrderItem within the context of a given Order.
When a new OrderItem row is inserted it needs to be assigned the next OrderItemNumber for the parent Order, something like
SELECT COALESCE(MAX(OrderItemNumber),0) FROM dbo.OrderItems WHERE OrderId = 2
Is there a means using EF6/SQL Server to auto-assign the OrderItemNumber at the point a row is inserted?
Although this proved an interesting learning experience on EF support for triggers (see https://github.com/NickStrupat/EntityFramework.Triggers) I eventually decided to re-model.
In this case my question was a result of incorrect modelling. The OrderItemNumber property was redundant so I removed it; the sequence the OrderItems were created can be inferred from the Id value, and there was no real requirement to allow re-sequencing.

Including the Foreign Key Id in Code First to save queries?

I'm using Entity Framework 6 Code First.
Right now my model looks the following:
public class Region
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<City> Cities { get; set; }
}
public class City
{
public int Id { get; set; }
public string Name { get; set; }
[Required]
public virtual Region Region { get; set; }
}
The thing is, I got a list of cities.
I need to lookup the region of each city (I have a local list of regions as well).
Now, I suppose I could do the following:
foreach (var c in cities)
{
if (regions.Any(x => x.Id == c.Region.Id))
}
Here I will have to look up a region from the database for each city (lazy loading).
However, I only need the Id of the region, therefore it seems wasteful to me to look up the region row at every loop.
If I changed my City model to be the following:
public class City
{
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Region")]
public int RegionId { get; set; }
[Required]
public virtual Region Region { get; set; }
}
I could do the following instead:
foreach (var c in cities)
{
if (regions.Any(x => x.Id == c.RegionId)) //no region lookup at Im using the foreign id key
}
Is this correct? I mean it will save me a query for every city right?
If so, is there any reason NOT to include the foreign id keys in the model when doing Code First at all?
Is this correct? I mean it will save me a query for every city right?
It will save a join to the Regions table. You have to check it out with the SQL Profiler! For example:
List of cities with Id City1, City2 (Region not loaded City1= has RegionId 5)
Now you are looking for any City has a Region with Id 5.
// EF does not have to join the tables because you have the RegionId
if (myDbContext.Cities.Any(c => c.RegionId == 5))
{
}
If so, is there any reason NOT to include the foreign id keys in the
model when doing Code First at all?
No for me this it is a good practice! Just keep it consistent and do it for all relationships of the type 1..n/1..0or1.

Entity Framework Code First - One to Many relations from multiple tables

I have a BlobEntity table that contains paths to files for many other tables (tableX, tableY, tableZ, etc...) in my application.
The relation between all the other tables to BlobEntity table is one to many.
Example:
tableX -> BlobTable (OTM)
tableY -> BlobTable (OTM)
tableZ -> BlobTable (OTM)
and the relation is:
public virtual ICollection<BlobEntity> BlobEntity { get; set; }
I'm not sure if this is an issue, but entity framework Code First creates a new FK column in BlobEntity table for each source table.
In my case, BlobEntity contains three FK columns for tableX, tableY and tableZ.
In order to be efficiency, i rather create one column in BlobEntity that contains the FK for the source tables.
Is it reasonable?
Please advise...
Thanks.
No, you can't do this even in plain old SQL.
You can have a foreing key pointing to more than one table; that's why you need
three columns.
If you want to do a "trick" like this, you have to manually manage the relation (I mean, no real FK), but you can't map it into EF.
What about this?
public class EntityA
{
public int Id { get; set; }
public int MyFileID {get;set;}
public virtual MyFiles MyFile { get; set; }
}
public class EntityB
{
public int Id { get; set; }
public int MyFileID {get;set;}
public virtual MyFiles MyFile { get; set; }
}
public class MyFiles
{
public MyFiles()
{
// ReSharper disable once VirtualMemberCallInContructor
FilesForEntityA = new List<EntityA>();
// ReSharper disable once VirtualMemberCallInContructor
FilesForEntityB = new List<EntityB>();
}
public int Id { get; set; }
public int? EntityAId {get;set;}
public int? EntityBId {get;set;}
public virtual ICollection<EntityA> FilesForEntityA { get; set; }
public virtual ICollection<EntityB> FilesForEntityB { get; set; }
}
This way you can have the FK in place and you can easily manager multiple entities.
Obviously if you have many files for each entity, you can go with a N-to-N relationship, like this.

Resources