I have an entity that consists only of foreign keys of other Entities.
My simplified class looks like this:
DeliverNote
Adress add1 {get; set;}
Adress add2 {get; set;}
I can load adresses by themselves just fine, but I can't load a DeliveryNote, because EF doesn't load the related data by default, I think.
So I saw solutions, mainly with context.notes.Include(dn => dn.Adresses), but I just can't figure out how I tell the note or the adress class how they're related to each other. Basically when I type "dn." nothing shows up.
The simplest, probably working, solution I saw was from microsoft. In the github from this page https://learn.microsoft.com/de-de/ef/core/querying/related-data you can see the Blog and the Post classes. To me the Post class looks flawed though, why would a Post have to know about the Blog it is in? This will mess up the database too in code first solutions. What if the same post is gonna be posted in several blogs?
Most solutions also seem to be lists of some kind, I don't have a list, just simple single objects. 1-1 relationship, I think.
So you have a database with a table of Addresses and a table of DeliveryNotes. Every DeliveryNote has two foreign keys to the Addresses: one From and one To (you call it addr1 and addr2)
If you follow the entity framework code first conventions, you'll have something like this:
class Address
{
public int Id {get; set;}
... // other properties
// every Address has sent zero or more delivery Notes (one-to-many)
public virtual ICollection<DeliveryNote> SentNotes {get; set};
// every Address has received zero or more delivery Notes (one-to-many)
public virtual ICollection<DeliveryNote> ReceivedNotes {get; set};
}
class DeliveryNote
{
public int Id {get; set;}
... // other properties
// every DeliveryNote comes from an Address, using foreign key
public int FromId {get; set;}
public virtual Address FromAddress {get; set;}
// every DeliverNote is sent to an Address, using foreign key:
public int ToId {get; set;}
public virtual Address ToAddress {get; set;}
}
In entity framework the columns of the tables are represented by non-virtual properties. The virtual properties represent the relations between the tables.
Note that the ICollection and FromAddress / ToAddress are virtual and thus not columns into your columns. If desired you can leave them out of your classes. However, if you have these virtual properties, you don't have to do the (Group)Joins yourself.
I can load adresses by themselves just fine, but I can't load a DeliveryNote, because EF doesn't load the related data by default ... I
From this it is not easy to detect what kind of queries you want.
One of the slower parts of database queries is the transport of the selected data from your DBMS to your local process. Hence it is wise to minimize the data being transported.
If you use Include, then the complete object is transported, inclusive the foreign keys and all properties you don't need. If you have a database with Schools and Students, then every Student will have a foreign key to the School he attends. If you ask for a 'School with his 1000 Students' of school with Id 4, using Include, you don't want to transport the foreign key SchoolId a 1000 times, because you already know it will have value 4
In entity framework only use Include if you want to change / update the fetched item, otherwise use Select
Given a bunch of DeliveryNotes, give me some AddressDetails of it:
IQueryable<DeliveryNote> deliveryNotes = dbContext.DeliveryNotes
.Where (deliveryNote => ...) // probably something with Id, or Date, or subject
.Select(deliveryNote => new
{
// select only the delivery note properties you actually plan to use
Subject = deliveryNote.Subject,
DeliveryDate = deliveryNote.DeliveryDate,
...
From = new
{
// select only the From properties you plan to use
Id = deliveryNote.FromAddress.Id,
Name = deliveryNote.FromAddress.Name,
Address = deliveryNote.FromAddress.Address,
...
}
To = new
{
// again: only properties you'll use
Name = deliveryNote.ToAddress.Name,
...
},
});
Entity framework knows the one-to-many relationship and will perform the proper join for you.
Given a bunch of Addresses give me some of the DeliveryNotes they received
var query = dbContext.Addresses
.Where(address => address.City == "New York" && ...)
.Select(address => new
{
// only properties you plan to use
Id = address.Id,
Name = address.Name,
ReceivedNotes = address.ReceivedNotes
.Where(note => note.DeliveryDate.Year == 2018)
.Select(note => new
{
// only properties you plan to use:
Title = note.Title,
...
// no need for this, you know it equals Id
// AddressId = note.FromId,
}),
});
Entity framework knows the one-to-many relationship and will do the proper groupjoin for you.
If you have a one-to-many relationship and you want the "item with its many sub-items", start on the one-side and use the virtual ICollection. If you want the sub-item with the item that it belongs to, start with the many-side and use the virtual property to the one-side
If you define your model as:
public class DeliverNote {
public int Id { get; set; }
public Adress addr1 { get; set; }
public Adress addr2 { get; set; }
}
public class Adress {
public int Id { get; set; }
}
You can then call:
context.notes.Include(dn => dn.addr1).Include(dn => dn.addr2);
Which will include the related data.
Your model doesn't define foreign keys for addr1 or addr2 so EF Core will create shadow properties for you, i.e. columns that exist in the table but not as properties in the c# model.
Related
I have two classes like so.
public class Client { public Guid Id { get; set; } ... }
public class Meeting
{
public Guid Id { get; set; }
public Client[] Invitees { get; set; } = new Client[0];
public Client[] Attendees { get; set; } = new Client[0];
}
The config in the contex is as follows.
private static void OnModelCreating(EntityTypeBuilder<Client> entity) { }
private static void OnModelCreating(EntityTypeBuilder<Meeting> entity)
{
entity.HasMany(a => a.Invitees);
entity.HasMany(a => a.Attendees);
}
I only need the reference to the clients from my meetings. The clients need not to know anything. The meetings need to reference the clients twice or less (volountary presence, optional invitation).
The migration on the above creates two tables, which I'm perfectly fine with. But it creates two indices as well, like this.
migrationBuilder.CreateIndex(
name: "IX_Clients_MeetingId",
table: "Clients",
column: "MeetingId");
migrationBuilder.CreateIndex(
name: "IX_Clients_MeetingId1",
table: "Clients",
column: "MeetingId1");
I'm not fine with that. First of all, I expected only one index to be created, as we're indexing the sme table's primary keys. Secondly, if I can't dogde that, I dislike the digit in IX_Clients_MeetingId1.
What can I do (if anything) to only have a single index created?
How can I specify the name of the index if I'm not using WithMany()?
I'm not providing any links as a proof of effort. Checking MSDN, SO and blogs resulted in a lot of hits on the full M2M relation, i.e. .HasMany(...).WithMany(...) and that's not what I'm heading for. I saw a suggestion to manually make the change in the migration file but tempering with those is begging for issues later. I'm not sure how to google-off the irrelevant results and I'm starting to fear that the "half" M2M I'm attempting is a bad idea (there's no in-between table created, for instance).
Well, it seems that EF is assuming you have 2 one2many relations. So one Client could only be invited to at most one meeting.
As a quick resolution you can either
add 2 join entities explicitly and configure the appropriate
one2many relations. Then you have one table for Invitations and one
for Attendance.
add one many2many join entity that also tracks a
link type (Client, Meeting, LinkType) so that "invited" and
"attended" are link types
Add 2 properties to Client to show EF that
you mean this as a many2many relation:
Like so:
public class Client {
public Guid Id { get; set; }
public ICollection<Meeting> InvitedTo { get; set; }
public ICollection<Meeting> Attended { get; set; }
}
These should not show up in the clients table but as 2 separate tables. (Essentially solution 1 with implicit join entity)
Stepping back, I think you can simply improve the model by introducing an MeetingMember entity. In the current model there's no way a client can be invited to two meetings, nor are clients restricted to attending meetings to which they are invited. So you need a M2M relation, and you can get away with one if you use an explicit linking entity, like
MeetingMember(MeetingId, ClientId, InvitedAt, Attended)
I'm trying learn to write efficient Entity Framework queries when data has to be fetched based on multiple joins, including a many-to-many via a junction table. In the following example, I'd like to fetch all States that contain a particular Book.
Let's use a model with the following tables/entities, all linked by navigation properties:
State, City, Library, Book, LibraryBook (junction table for many-to-many relationship between library and book.)
Each State has 1 or more Cities
Each City has 1 or more Libraries
Each Library has many Books & Each Book may exist at more than 1 library.
How can I best return all of the States that contain a particular Book? I'm inclined to think separate queries may work better than 1 large one, but I'm not certain what the best implementation is. I think that getting the LibraryId from the many-to-many relation first in a separate query is probably a good way to start.
So for that:
var bookId = 12;
var libraryIds = _context.LibraryBook.Where(l => l.BookId == bookId).Select(s => s.LibraryId);
If that comes first, I'm uncertain how to best query the next data in order to get the cities which contain each of those LibraryIds. I could use a foreach:
var cities = new List<City>;
foreach(var libraryId in libraryIds)
{
var city = _context.City.Where(c => c.Library = libraryId)
cities.Add(city);
}
But then I'd have to do yet another foreach for the states that contain the city, and this all adds up to a lot of separate SQL queries!
Is this really the only way to go about this? If not, what is a better alternative?
Thanks in advance!
Database management systems are extremely optimized in combining tables and selecting columns from the result. The transport of the selected data is the slower part.
Hence it is usually better to limit the data that needs to be transported: let the DBMS do all the joining and selecting.
For this, you don't need to put everything in one big LINQ statement that is hard to understand (and thus hard to test, reuse, maintain). As long as your LINQ statements remain IQuerayble<...>, the query is not executed. Concatenating several of these LINQ statements is not costly.
Back to your question
If you followed the entity framework conventions, your one-to-many relations and your many-to-many will have resulted in classes similar to the following:
class State
{
public int Id {get; set;}
public string Name {get; set;}
...
// every State has zero or more Cities (one-to-many)
public virtual ICollection<City> Cities {get; set;}
}
class City
{
public int Id {get; set;}
public string Name {get; set;}
...
// Every City is a City in exactly one State, using foreign key:
public int StateId {get; set;}
public virtual State State {get; set;}
// every City has zero or more Libraries (one-to-many)
public virtual ICollection<Library> Libraries {get; set;}
}
Library and Books: many-to-many:
class Library
{
public int Id {get; set;}
public string Name {get; set;}
...
// Every Library is a Library in exactly one City, using foreign key:
public int CityId {get; set;}
public virtual City City {get; set;}
// every Library has zero or more Books (many-to-many)
public virtual ICollection<Book> Books {get; set;}
}
class Book
{
public int Id {get; set;}
public string Title {get; set;}
...
// Every Book is a Book in zero or more Libraries (many-to-many)
public virtual ICollection<Book> Books {get; set;}
}
This is all that entity framework needs to know to recognize your tables, the columns in the tables and the relations between the tables.
You will only need attributes or fluent API if you want to deviate from the conventions: different identifiers for columns or tables, non-default types for decimals, non default behaviour for cascade on delete, etc.
In entity framework, the columns in the tables are represented by the non-virtual properties; the virtual properties represent the relations between the tables.
The foreign key is an actual column in the table, hence it is non-virtual. The one-to-many has virtual ICollection<Type> on the "one" side and virtual Type on the "many" side. The many-to-many has virtual ICollection<...> on both sides.
There is no need to specify the junction table. Entity framework recognizes the many-to-many and creates the junction table for you. If you use database first, you might need to use fluent API to specify the junction table.
But how am I supposed to do the joins without a junction table?
Answer: don't do the (group-)joins yourself, use the virtual ICollections!
How can I best return all of the States that contain a particular Book?
int bookId = ...
var statesWithThis = dbContext.States
.Where(state => state.Cities.SelectMany(city => city.Libraries)
.SelectMany(library => library.Books)
.Select(book => book.Id)
.Contains(bookId);
In words: you have a lot of States. From every State, get all Books that are in all Libraries that are in all Cities in this State. Use SelectMany to make this one big sequence of Books. From every Book Select the Id. The result is one big sequence of Ids (of Books that are in Libraries that are in Cities that are in the State). Keep only those States that have at least one Book.
Room for Optimization
If you regularly need to do similar questions, like: "Give me all States that have a Book from a certain Author", or "Give me all Libraries that have a Book with a certain title", consider to create extension methods for this. This way you can concatenate them as any LINQ method. The extension method creates the query, it will not execute them, so this won't be a performance penalty.
Advantages of the extension method: simpler to understand, reusable, easier to test and easier to change.
If you are not familiar with extension methods, read Extension Methods Demystified
// you need to convert them to IQueryable with the AsQueryable() method, if not
// you get an error since the receiver asks for an IQueryable
// and a ICollection was given
public static IQueryable<Book> GetBooks(this IQueryable<Library> libraries)
{
return libraries.SelectMany(library => library.AsQueryable().Books);
}
public static IQueryable<Book> GetBooks(this IQueryable<City> cities)
{
return cities.SelectMany(city => city.Libraries.AsQueryable().GetBooks());
}
Usage:
Get all states that have a book by Karl Marx:
string author = "Karl Marx";
var statesWithCommunistBooks = dbContext.States.
.Where(state => state.GetBooks()
.Select(book => book.Author)
.Contains(author));
Get all Cities without a bible:
string title = "Bible";
var citiesWithoutBibles = dbContext.Cities
.Where(city => !city.GetBooks()
.Select(book => book.Title)
.Contains(title));
Because you extended your classes with method GetBooks(), it is as if States and Cities have Books. You've seen the reusability above. Changes can be easy, if for instance you extend your database such, that Cities have BookStores. GetBooks can check the libraries and the BookStores. Your change will be in one place. Users of GetBooks(), won't have to change.
I have three tables:
Materials:
ID
Title
Content
Likes:
ID
MaterialID
UserID
IsLiked
Visitors:
ID
UserID
MaterialID
Date
ReadNow
I would like to get an object like:
Title
Content
CountLikes
CountVisitors
I tried to do the following:
from mat in ctx.materials
let visitors = mat.VisitorsCollection.Where(x=>x.ReadNow).Count()
let likes = mat.LikesCollection.Where(x=>x.IsLiked).Count()
let iliked = mat.LikesCollection.Where(x=>x.UserID == myID && x.IsLiked).Any()
select new {
Material = mat,
Visitors = visitors,
Likes = likes,
Liked = iliked
}
I get a selection of materials and separately the Entity Framework receives data on the number of visitors and so on.
I also tried the following:
from mat in ctx.materials
join lik in ctx.Likes.Where(x=>x.UserID == myID && x.IsLiked) on map.ID equals lik.MaterialID
select new {
Material = mat,
Liked = lik.Any()
}
but now an error occurs:
Microsoft.EntityFrameworkCore.Query:Warning: The LINQ expression 'Any()' could not be translated and will be evaluated locally.
If you are using entity framework, consider to use the ICollections, instead of performing the joins yourself.
You have a sequence of Materials where every Material has zero or more Likes and zero or more Visitors, both one-to-many relations, using a foreign key to Material.
If you've followed the entity framework code first conventions, you'll have classes similar to the following
class Material
{
public int Id {get; set;}
public string Title {get; set;}
public string Content {get; set;}
// every Material has zero or more Likes (one-to-many)
public virtual ICollection<Like> Likes {get; set;}
// every Material has zero or more Visitors (one-to-many)
public virtual ICollection<Visitor> Visitors {get; set;}
}
Likes and Visitors:
class Like
{
public int Id {get; set;}
public bool IsLiked {get; set;}
...
// every Like belongs to exactly one Material, using foreign key
public int MaterialId {get; set;}
public virtual Material Material {get; set;}
}
class Visitor
{
public int Id {get; set;}
...
// every Visitor belongs to exactly one Material, using foreign key
public int MaterialId {get; set;}
public virtual Material Material {get; set;}
}
This is all that entity framework needs to detect the one-to-many relationships. It might be that you want different table names, or different identifiers for your columns. In that case attributes or fluent API is needed
In entity framework the columns of the tables are represented by non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many, etc)
Once you've got your class definitions correctly, your query is simple and very intuitive:
Requirement:
From my collection of Materials, give me from every Material, the Title, the Content, the number of Likes it has and the number of Visitors it has:
var result = myDbContext.Materials
.Where(material => ...) // only if you don't want all Materials
.Select(material => new // from every Material make one new object
{ // containing the following properties
Title = material.Title,
Content = material.Content,
// if you want any information of the likes of this material, use property Likes
LikeCount = material.Likes
.Where(like => like.IsLiked) // optional, only if you don't want all likes
.Count(),
NrOfVisitors = material.Visitors
.Where(visitor => ...) // only if you don't want all visitors
.Count(),
});
In words: from my complete collection of Materials, keep only those Materials that ... From every remaining Material, make one new object:
Title is the title of the Material
Content is the content of the Material
LikeCount is the number of Likes of this material (that have a true IsLiked)
NrOfVisitors is the number of Visitors of this material (that are ...)
Entity framework knows your relations, and knows that a GroupJoin is needed.
Well if you have foreign keys in the database then the EF would generate links between the objects so all you need to do is:
var result = ctx.materials.Select(x =>
new SomeClass{
Material = x,
Visitors = x.Visitors.Where(v => v.ReadNow).Count(),
Likes = x.Likes.Where(y => y.IsLiked).Count(),
Liked = x.Likes.Where(z => z.IsLiked && z.UserID == myID).Count()
}).ToList();
The syntax maybe is not totally correct, but you get the point ...
I have an existing entity User. Now I am trying to create a new entity Contact with 0-1 relation with User.
class Contact
{
public int Id {get; set;}
public string Name {get; set;}
public int? UserId {get; set;}
public virtual User TheUser{get; set;}
}
All suggestion involve about something like this:
modelBuilder.Entity<User>()
.HasOptional(t => t.TheUser)
.WithOptionalDependent(u => u.TheConatct);
But this means we have to add TheConatct property to the existed User entity. Actually I do not want to make any modification to the existed entity. All what I need to define a foreign key form Contact to User entity and can access the User entity from Contact via TheUser property.
Update:
If I use ForeignKey attributes to annotate the property:
class Contact
{
public int Id {get; set;}
public string Name {get; set;}
public int? UserId {get; set;}
[ForeignKey("UserId")]
public virtual User TheUser{get; set;}
}
Then, the result of ObjectContext.CreateDatabase() will also include create statements for already existed tables (depending on the entities that have relations with User).
Of course we are talking about "Entity Framework 6 Code First", Also, I have the same problem with 1-1 relation.
The idea, I cannot alter the existing entity User to add additional property for the new entity Contact
I wonder if there is a way to overcome this issue
Just use another overload:
modelBuilder.Entity<User>()
.HasOptional(t => t.TheUser)
.WithOptionalDependent();
I don't know which version of Entity Framework you are using and I assume you are using Code First, but you may have to consider using a one to many relationship instead of a 0-1.
I don't believe there is support for 0-1 in the way you want it, but you can simulate by having a one to many (even though your "many" will only ever by 1)
I am in the process of building up a data model in Entity Framework using the Code First approach, but one part has me a bit stumped. The title on this question may be a bit confusing, so I will explain my problem in detail. The length of this post may be daunting, but I think it's a fairly straightforward problem.
I have one model defined like this:
public class KeyValuePair
{
[Key]
[MaxLength(128)]
[Column(Order = 0)]
public virtual string OwnerId { get; set; }
[Key]
[MaxLength(128)]
[Column(Order = 1)]
public virtual string Key { get; set; }
public virtual string Value { get; set; }
}
My intent is for this to just define a generic table for storing key-value properties on other entities in the system. I am using GUIDs for all of my Ids, so OwnerId should uniquely refer to one entity in the system, and the pair (OwnerId, Key) should uniquely identify one property on one entity.
In other words, I want to allow multiple tables in my system to have a One->Many relationship to this KeyValuePair table.
So for example, if I wanted to store the height of a Person who has the ID b4fc3e9a-2081-4989-b016-08ddd9f73db0, I would store a row in this table as:
OwnerId = "b4fc3e9a-2081-4989-b016-08ddd9f73db0"
Key = "Height"
Value = "70 in."
So now I want to define navigation properties from the parent entities to this table, like (to take the Person example):
public class Person
{
[Key]
public virtual string Id { get; set; }
public virtual string Name { get; set; }
// I want this to be a navigation property
public ICollection<KeyValuePair> Properties { get; set; }
}
But I'm not sure how do define the relationship between Person and KeyValuePair so that Entity Framework knows that it should look up the Person's properties by matching the Person's Id against the KeyValuePairs' OwnerId. I can't define a foreign key in the KeyValuePair model, because the OwnerId is going to refer to Ids in several different tables.
It looks like I can do the following to define a relationship from Person to KeyValuePair in OnModelCreating:
modelBuilder.Entity<Person>()
.HasMany(p => p.Properties).WithMany().Map(mp =>
{
mp.MapLeftKey("Id");
mp.MapRightKey("OwnerId", "Key");
mp.ToTable("PersonDetail");
});
Or I could even give the KeyValuePairs their own unique IDs, get rid of OwnerId, and do this:
modelBuilder.Entity<Person>()
.HasMany(p => p.Properties).WithMany().Map(mp =>
{
mp.MapLeftKey("Id");
mp.MapRightKey("Id");
mp.ToTable("PersonDetail");
});
But both of these approaches involve the creation of an intermediary table to link the Person and KeyValuePair tables, and that seems like excessive overhead in terms of bloating my database schema and requiring more expensive JOINs to query the data.
So is there a way to define the relationship such that I don't need to involve intermediary tables? Am I going about this database design the wrong way?
Side note: For anyone wondering why I am using this approach to define properties on my entities rather than simply adding fixed properties to the data model, I am using fixed properties in the data model where applicable, but the application I am building requires the ability to define custom properties at runtime. I also think this question is applicable to other potential scenarios where multiple tables have a One->Many relationship to a shared table.
The only way I can think of doing it (and I'll admit, this is not the best of ideas, but it will do what you're asking) would be to have any classes that need to have this relationship with KeyValuePair implement an abstract class that contains the fully implemented navigational property, as well as the ID field. By "fully implemented" I don't mean an actual, mapped relationship; I mean that it should use a DbContext to go out to the KeyValuePair table and actually grab the relevant properties given the ID.
Something like this:
public abstract class HasKeyValuePairs
{
[Key]
public virtual string Id { get; set; }
[NotMapped]
public ICollection<KeyValuePair> Properties
{
get
{
using(var db = new DbContext())
{
return db.KeyValuePairs.Where(kvp => kvp.OwnerID == this.ID);
}
}
}
}
Assuming you're using Lazy Loading (given that you're using the virtual keyword), there shouldn't be much extra overhead to doing it like this, since EF would have to go back to the database anyway to pick up the properties if you ever called for them. You might need to have that return a List just to avoid any potential ContextDisposedException later on in your code, but that at least will get you up and running.