Entity Framework 4.0 - Including entities - Eager loading problem - c#

We have 3 tables in our db that each have an entity in our edmx. To illustrate my problem, imagine 3 tables:
Table: Make
Fields:
makeID
make
Table: Model
FIelds:
modelID
makeID foreign key
model
Table: Car
carID
modelID foreign key
car
Our Make, Model, and Car entity have all of the navigation properties in the entity model. Lazy loading is disabled. We want to be able to pull all cars that are Jeep Grand Cherokees to output to our page.
Right now we have something like this in one of our functions (C# 4.0)
IEnumerable<Make> makeList = (((ObjectSet<Lot>)_modelRepository.GetQuery())
.Include(mk => mk.Models.Where(md => md.model == "Grand Cherokee"))
.Where(mk => mk.make == "Jeep").ToList());
_makeRepository.GetQuery() returns an IQueryable ... we implement the repository pattern
This query should work fine (haven't tested it, created for ths example) but how can we .Include the car table so that our function returns Make entity objects such that the Model is populated and the Cars are populated (problem getting the Cars because they do not have a direct navigation property to Make)
We're using POCO objects.
The goal is to have 1 function returning a Make entity to be able to do this:
foreach(Make myMake in makeList)
{
Response.Write(myMake.make);
foreach(Model myModel in myMake.Models)
{
Response.Write(myModel.model);
foreach(Car mycar in myModel.Cars)
{
Response.Write(mycar.car);
}
}
}
Something like this doesn't seem possible but its what we're going for:
IEnumerable<Make> makeList = (((ObjectSet<Lot>)_modelRepository.GetQuery())
.Include(mk => mk.Models.Where(md => md.model == "Grand Cherokee"))
.Include(c => mk.Models.Cars)
.Where(mk => mk.make == "Jeep").ToList());
I've also tried creating a new entity in my edmx that contains all of this information so that I can just query that one object but I keep getting errors saying keys must be mapped... I did map them in the Mapping tab (Visual Studio 2010)... so I'm back to trying to get the query working.

I am not 100% sure but I believe you are going to need to create some kind of DTO like this:
public MakeModelCarDto
{
public IEnumerable<Make> Makes {get; set;}
public IEnumerable<Model> Models {get; set;}
public IEnumerable<Car> Cars {get; set;}
}
Then you are going to have to JOIN the tables like this:
_makeRepository.GetQuery()
.Join(_modelRepository.GetQuery(), mk => mk.makeid, mo => mo.makeid, (mk, mo) => new { mk, mo })
.Join(_carRepository.GetQuery(), #t => #t.mo.modelid, c => c.modelid, (#t, c) => new { #t, c })
.Where(#t => #t.#t.mk.make == "Jeep" && #t.#t.mo.model == "Grand Cherokee")
.Select(#t => new MakeModelCarDto
{
Makes = #t.#t.mk,
Model = #t.#t.mo,
Cars = #t.c
}).SingleOrDefault();

Related

What is the correct way to do many to many entity relation update in Entity framework core 6?

public class Book
{
public int Id { get; set; }
public string Name{ get; set; }
public string ISBN { get; set; }
public ICollection<Category> Categories { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name{ get; set; }
public ICollection<Book> Books { get; set; }
}
I a using Entity Framework core 6 with .NET 6.
I am trying to update the Categories of a specific Book.
For example, If one book has categories like .NET, C# then I want to update categories into .NET, EF Core, SqlServer, I think you get it now.
Do I need to add a Join entity for only the Update operation? As you can see I have not created any Join entity like BookCategories though I managed to Insert categories while creating Book for the first time.
But when trying to update the book with new categories I am getting two issues.
The old category is not deleted.
And getting Duplicate Error Key while trying to update with existing category, in this case, .NET.
Please kindly show the proper way of updating related entities in Entity Framework Core 6 in .NET6.
Many-to-Many relationships need a bit of configuration depending on what you want out of the relationship. If you just want the linking table to manage the link and nothing else:
[BookCategories]
BookId (PK, FK)
CategoryId (PK, FK)
Then you can set up the relationship to either use an entity definition or a shadow entity. In both cases this is typically preferable since your Book can have a collection of Categories, and the Category can have a collection of books. With Code-First and Migrations I believe EF can and will set up this linking table automatically. Otherwise you can use OnModelCreating or an EntityTypeConfiguration to configure what Table and Columns to use for the relationship.
This can be done either with an Entity declared for BookCategory, or without one:
With entity:
modelBuilder.Entity<Book>()
.HasMany(x => x.Categories)
.WithMany(x => Books);
.UsingEntity<BookCategory>(
l => l.HasOne<Book>().WithMany().HasForeignKey(x => x.BookId),
r => r.HasOne<Category>().WithMany().HasForeignKey(x => x.CategoryId),
j =>
{
j.HasKey("BookId", "CategoryId");
j.ToTable("BookCategories");
});
Without entity: (See Scaffolding many-to-many relationships - https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-6.0/whatsnew)
modelBuilder.Entity<Book>()
.HasMany(x => x.Categories)
.WithMany(x => Books);
.UsingEntity<Dictionary<string, object>>(
"BookCategories",
l => l.HasOne<Book>().WithMany().HasForeignKey("BookId"),
r => r.HasOne<Category>().WithMany().HasForeignKey("CategoryId"),
j =>
{
j.HasKey("BookId", "CategoryId");
j.ToTable("BookCategories");
});
Alternatively, if the joining table needs to contain additional relevant details, for example if you are using a soft-delete system and want to mark deleted relationships as inactive rather than deleting those rows, then you have to adopt an indirect relationship using a BookCategory entity where Book has a collection of BookCategories, as does Category. (See Join entity type configuration - https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key)
Once you have your relationships set up, it is important to treat these relationships as associations, not copies of data. This means you should ensure that your collections are initialized on construction, and never reset. You can add items to the collection or remove items from the collection, but you should never have code that resets the collection. (I.e. no code that does stuff like book.Categories = new List<Category>() or book.Categories = myUpdatedCategories etc.) While EF is tracking entities, it is relying on proxies to help with change tracking to know when data needs to be added, removed, or updated. This also means if you want to "change" a book's category, this is a remove and add, not an update.
For instance to change a book's category from "Java" to ".Net", you don't want to do something like:
var book = context.Books.Include(x => x.Categories).Single(x => x.BookId == bookId);
var category = book.Categories.SingleOrDefault(x => x.CategoryName == "Java");
if (category != null)
category.CategoryName = ".Net"; // or category.CategoryId = dotNetCategoryId;
This would attempt to modify the Category record to change it's Name (likely not intended) or attempt to change it's PK. (illegal)
Instead, you want to change the association:
var dotNetCategory = context.Categories.Single(x => x.CategoryId == dotNetCategoryId);
var book = context.Books.Include(x => x.Categories).Single(x => x.BookId == bookId);
var category = book.Categories.SingleOrDefault(x => x.CategoryName == "Java");
if (category != null)
{
book.Categories.Remove(category);
book.Categories.Add(dotNetCategory);
}
Behind the scenes, EF will delete the BookCategory linking the book to Java category, and insert a BookCategory with the new .Net association. If you have a joining entity then you will just need to remove, add, or update the BookCategory entity specifically based on the relationship changes you want to make.

Can Entity Framework Core return view model

I have a situation which can be explained by below analogy.
Example: let's say we have 3 tables Categories => SubCategories => Products.
1 category can have many subcategories and 1 subcategory can have many products.
I am showing simple card for products details with category and subcategory names but for it I am writing the EF like.
var products = await _context.Products
.Where(x => x.IsDeleted == false)
.Include(x => x.SubCategory)
.Include(x => x.SubCategory.Category).ToListAsync()
The SQL generated is too expensive.
When the products are reached to the controller then Automapper starts it's magic to map according to my required view model.
I am new to Entity Framework Core and I have three questions:
Is it there a better way to write above code?
Can I return a view model directly from Entity Framework Core? In the above case, can I return a model with properties showing just names of Products, SubCategory and Category?
If I can't return, then how can I convince myself to stop using Dapper?
Automapper can generate the sql for you, basically doing the mapping to your viewmodel/DTO in the database.
Use the ProjectTo extension to IQueryable, explained here.
Yes. You can use the ThenInclude operation to make the code easier to read.
var products = await _context.Products.Where(x => x.IsDeleted == false)
.Include(x => x.SubCategory)
.ThenInclude(x => x.Category).ToListAsync()
Yes or no. It depends on what your ViewModel is.
Entity framework is a framework for operating database entities. But ViewModel is a concept in MVVM. It was two different concepts and have no relationship.
Usually, the view is rendering what is needed to be rendered. So we return it a ViewModel instead of Entity. If the Entity itself is what you need to be rendered, just return it! It's ok.
return View(viewName: "myview", model: products);
#model IEnumerable<Product> // Product is your entity in EF. You can use it in a view.
It's fine.
But, consider what the view needs is not what you got from entit-framework. Now you need to convert the entity to the ViewModel. For example:
var entity = await dbContext.MyTable.SingleOrDefaultAsync(t => t.Id == id);
var viewModel = new MyViewModel
{
Color = entity.Color // Only need to return the color, for example.
}
return View(viewModel);
#model MyViewModel
<h2>The color of it is #Model.Color</h2>
#*You can't access other properties of the entity here in the view.*#
And the other properties will not be returned to the view.
And some tools like AutoMapper can just help you do the map job.
Another way is to use Select() to return the column on your choice. For example:
Entity definition and view model definition.
public class Product
{
public int Id { get; set; } // Don't want to return this.
public string Name { get; set; } // Only want to return this.
}
public class ProductDto
{
public string Name { get; set; }
}
var products = _context.Products; // While the products is declared, the query was not happened in the database. It only defines an IQueryable object.
List<ProductDto> viewModel = await products.Select(t => new ProductDto
{
Name = t.Name // Manually map here.
})
.ToListAsync();
return View(viewModel);
In your view:
#model List<ProductDto>
foreach (var dto in Model)
{
<h2>dto.Name</h2>
}

Can I setup a navigation property between two objects without a unique foreign key?

I'm using Entity Framework Core and have two tables in my database :
Table 1 (Contract)
Columns : ContractNumber, ContractCode, ProductType
Table 2 (ContractRole)
Columns: ContractNumber, ContractCode, ProductType, RoleType, RoleName
So, my database doesn't have a foreign key, instead I use two columns (contractnumber, contractcode) to reference tables.
My goal is to create my entities, so that I can fetch contracts and then for each Contract I can extract a relevant list of ContractRoles. That means using navigation properties.
My code will be something like:
[Table("XXXXX")]
public class Contract
{
public Contract()
{
ContractRoles = new HashSet<ContractRole>();
}
public ICollection<ContractRole> ContractRoles { get; set; }
}
If I had a direct contractId foreign key then I could do:
modelBuilder.Entity<ContractRoles>()
.HasOne(x => x.Contract)
.WithMany(x => x.ContractRoles)
.HasForeignKey(x => x.ContractId);
But I don't! Therefore, I need the reference to be made to two fields: contract number and contract code. Is it possible?
I did make it work by fetching the flat data with a query and then building my proper objects (Contract object with a list of ContractRoles) later:
var result = (from s in _dbContextReadOnly.Contracts
join sa in _dbContextReadOnly.ContractRoles
on new { s.ContractNumber, s.ContractCode } equals new { sa.ContractNumber, sa.ContractCode }
select new FlatContractWithContractRoles
{
ContractNumber = s.ContractNumber,
ContractCode = s.ContractCode,
RoleType = sa.RoleType,
RoleName = sa.RoleName
}).Distinct().ToList();
Please don't advise me to modify the database at the source, it is not a possibility. I just want to know if I can fetch a Contract with a list of ContractRoles using the navigation properties directly.
Thanks :) !
I got it! I could just do :
.HasForeignKey(x => new { x.ContractNumber, x.ContractCode });

How can I get value from .Include() using .ThenInclude()c

I have some users filter in my project and I want to show each user's friends here. UserFrom - who send friendship request, UserTo - who accept it. So I need To know the Id in the code below to choose the opposite, beacuse it will be his friend.
var users = await _context.User
.Where(u => userFilter.Gender != null ?
u.Gender == userFilter.Gender : true)
.Where(u => (userFilter.Languages != null &&
userFilter.Languages.Count() != 0) ?
userFilter.Languages.Any(fl => u.Languages.Any(
ul => ul.LanguageCode == fl &&
LevelInRange(ul, userFilter.MinLevel))) : true)
.Where(u => (userFilter.MaxDistance != null) ?
LocationHelper.GetDistanceBetween((double)u.Longitude, (double)u.Latitude,
longtitude, latitude) <= userFilter.MaxDistance : true)
.Where(u => (userFilter.MaxAge != null) ?
GetAge(u.Birthdate) <= userFilter.MaxAge : true)
.Where(u => (userFilter.MinAge != null) ?
GetAge(u.Birthdate) >= userFilter.MinAge : true)
.Include(u => u.Languages)
.ThenInclude(ul => ul.Language)
.Include(u => u.CreatedEvents)
.Include(u => u.Friends)
.ThenInclude(f => f.UserTo) //The problem is here. How can I get u.Id there
.Include(u => u.Credentials)
.Include(u => u.Hobbies)
.ThenInclude(h => h.Hobby)
.ToListAsync();
Database management systems are optimized for selecting data. One of the slower parts is the transport of the selected data to your process. Hence it is wise to transport only the data that you actually plan to use.
If you have a one-to-many relation, like Schools with their Students, and School 10 has 1000 Students, then every Student of this School will have a foreign key SchoolId with a value 10.
So if you fetch "School [10] with its Students", you already know that every Student of school [10] will have a property SchoolId with a value 10. This value (that you already know) will be transported 1000 times (1001 if you also count the school's primary key). What a waste of processing power!
If you query data using entity framework, always use Select. Only use Include if you want to update the fetched data (change, delete)
Using Select enables you to select only the properties that you want, in the format that you want.
Back to your problem
Alas you forgot to give us your classes. So we'll have to guess it. It seems that a User has zero or more Languages, CreatedEvents, Friends, Hobbies, etc. Some of them will be a one-to-many relation, probably most of them will be a many-to-many relation: a user knows zero or more languages. Every language is spoken by zero or more Users.
If you've followed the entity framework code first conventions, you probably have classes similar to:
class User
{
public int Id {get; set;}
public string Name {get; set;}
// every User has zero or more Hobbies (many-to-many)
public virtual ICollection<Hobby> Hobbies {get; set;}
// every Student has created zero or more events (one-to-many)
public virtual ICollection<CreatedEvent> CreatedEvents {get; set;}
...
}
class Hobby
{
public int Id {get; set;}
public string Name {get; set;}
...
// every Hobby is practised by zero or more Users (many-to-many)
public virtual ICollection<User> Users {get; set;}
}
class CreatedEvent
{
public int Id {get; set;}
public string Name {get; set;}
public DateTime Date {get; set;}
// every event is created by exactly one User (one-to-many, using foreign key)
public int UserId {get; set;}
public virtual User User {get; set;}
}
etc.
In entity framework, the columns of your tables are represented by non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many, ...)
Hence, a foreign key is non-virtual. The item that the foreign key points to is virtual. If two classes have a virtual ICollection<...> pointing towards each other, entity framework knows that there is a many-to-many relation; if one of the two classes has virtual ICollection<...> while the other has virtual ... then entity framework knows that you intended to design a one-to-many relation.
If you've created your classes properly, especially the virtual ICollections, a query using Select is fairly easy. You seldom have to do a (group-)join anymore. Because you use the virtual properties, entity framework knows that a (group-)join is needed.
var queryUsers = dbContext.User.Where(...).Where(...) ...
.Select(user => new
{
// select only the user properties you really plan to use
Id = user.Id,
BirthDay = user.BirthDay,
// Select the data in the format that you want, for example:
FullName = user.FirstName + user.MiddleName + user.LastName,
// SubCollections:
Languages = user.Hobbies
.Where(hobby => ...) // only if you don't want all this user's hobbies
.Select(hobby => new
{
// again, select only the hobby properties that you plan to use
Id = hobby.Id,
...
// not needed, you already know the value:
// I know, it is probably a many-to-many, but let's suppose it is one-to-many
// UserId = hobby.UserId,
})
.ToList(),
...
});
Now your problem is in property Friends, you can add it to your Select, just like you selected the Hobbies
Friends = user.Friends
.Where(friend => ...) // only if you don't want all Friends
.Select(friend => new
{
// select the Friend properties you actually plan to use:
Id = friend.Id,
Name = friend.Name,
...
})
.ToList(),
// continue the select
IIRC, You can Select() over children with Linq expressions like so for children using .Include().
return _context.User
.Include(a => a.Friends.Select(c => c.UserTo));

NHibernate Join Query with only one instance of QueryOver and no mapping relation defined

In my case, I only have a specific repository. Lets say 'StudentRepository'. This repository hides the ISession instance from me and only thing I have is the IQueryOver< Student,Student> instance.
Consider below entities have a simplistic mapping with NHibernate.
class Student
{
public int Id {get;set;}
public string Number {get;set;}
}
class Exam{
public int Id {get;set;}
public double Score {get;set;}
public string StudentNumber {get;set;}
}
You are right, the basic way just add the real relation to Exam class like:
public Student Student {get; set;}
Unfortunately, that is not an option either
The problem: I need to query with some criteria like "Score>70" on Exam entity from studentRepository. How can I produce such a query with Nhibernate without knowing session and no relation defined on mapping. ?
So the main problem here as I see it: to make joins to unrelated entities with QueryOver it's required to have alias defined as variable for root query (your QueryOver for students).
How to do such joins explained in NHibernate QueryOver to join unrelated entities
So if you can modify your repository class to allow to provide optional alias variable for this QueryOver it would be the best solution. Something like this (I assume you are using NHibernate 5.1 or higher) :
Student studentAlias = null;
var studentsQueryOver= yourRepository.GetQueryOver<Student>(studentAlias);
Exam examAlias = null;
var students = studentsQueryOver
.JoinEntityAlias(() => examAlias, () => examAlias.StudentNumber == studentAlias.Number)
.Where(s => examAlias.Score > 70)
.List();
If it's not an option you still can create joins to unrelated entities but you need to build them directly with underlying root Criteria. Something like this:
Exam examAlias = null;
studentsQueryOver.RootCriteria
.CreateEntityAlias(
nameof(examAlias),
Restrictions.EqProperty("examAlias.StudentNumber", studentsQueryOver.RootCriteria.Alias + ".Number"),
JoinType.LeftOuterJoin,
typeof(Exam).FullName);
var students = studentsQueryOver
.Where(s => examAlias.Score > 70)
.List();
And on NHibernate versions before 5.1 you can use subqueries:
var subQuery = QueryOver.Of<Exam>()
.Where(e => e.Score > 70)
.Select(e => e.StudentNumber);
subQuery.RootCriteria.Add(Restrictions.EqProperty("StudentNumber", studentsQueryOver.RootCriteria.Alias + ".Number"))
//Or if root query alias variable available simply
//subQuery.And(e => e.StudentNumber == studentAlias.Number)
var students = studentsQueryOver
.WithSubquery.WhereExists(subQuery)
.List();

Categories