EF virtual collection with custom get method - c#

Is it possible in EF to have a virtual collection that has a custom query to the data it pulls?
For instance, if I have a class person.cs and I have a property in that class: public ICollection<job> jobs, but I want that property to be defined by a custom query that I write, for instance, _context.jobs.where(j => j.backup_person_id == id).select(j); rather than defaulting to look for just person_id on the jobs table.
Is that possible? If so, how is it achieved?
[Edit]
More specifically: let's say the jobs table has the two columns manager and main_lead. I then want my person.cs class to have the properties: ICollection<job> managerJobs which contains all of the <job> entries where the manager field matches the person_code field of that person and ICollection<job> main_leadJobs which contains all of the <job> entries where the main_lead field matches the person_code field of that person,
In my head, it seems pretty simple: ICollection<job> managerJobs = select * from tbl_jobs where manager = person_code and ICollection<job> main_leadJobs = select * from tbl_jobs where main_lead = person_code
I hope that makes more sense than what I had above

No. It is not possible. However, what you want to achieve, more generally, is possible, just in a different way. See the MSDN article: https://msdn.microsoft.com/en-us/data/jj574232.aspx#explicitFilter
In a situation where you a dealing with a specific person, rather than including jobs generally, you can do:
var person = _context.People.Find(personId);
_context.Entry(person)
.Collection(b => b.jobs)
.Query()
.Where(j => j.backup_person_id == backupId)
.Load();
Then, when you accessed the person.jobs collection, it would only be those filtered items. This means you've got two queries, though, whereas if you just eargerly loaded all jobs via Include, it would be done in a single query.
If you have multiple people you're working with though, this explicit load would have to be done multiple times, meaning a query for each person (N+1). In that situation, you're better off querying each entity individually:
var people = db.People.Where(...).ToList();
var jobs = db.Jobs.Where(m => people.Select(m => m.Id).Contains(m.person_id) && m.backup_person_id == backupId).ToList();
In other words, you're querying just jobs belonging to the specific selected people and then further limiting that by your criteria, which in this case is the id of the backup person. Then, as you're iterating through the people, you can filter jobs in memory to just the jobs that belong to the current person in context.

Related

Eager loading includes endless self-join tables without include tables

when i try to select some items, items are coming with their includes despite i did not include their object to linq
public List<Institution> GetListWithCities(Expression<Func<Institution,bool>> filter = null)
{
using (var context = new DbContext())
{
return filter == null
? context.Set<Institution>()
.Include(x => x.City)
.ToList()
: context.Set<Institution>()
.Include(x => x.City)
.Where(filter)
.ToList();
}
}
[Table("Institution")]
public class Institution{
public int ID;
public string Name;
public int CITY_ID;
public int RESPONSIBLE_INSTUTION_ID;
public virtual City City{ get; set; }
public virtual Institution ResponsibleInstution{ get; set; }
}
I expect a result include with city of instution but my method returns city and responsible instution. And it continues recursively.
People tend to use Include instead of Select while they don't plan to use the functionality that Include gives, but still wasting the processing power that Include uses.
In entity framework always use Select to fetch some data. Only user Include if you plan to update the included items.
One of the slower parts of a database query is the transport from the fetched data from the database management system to your local process. Hence it is wise to Select only those properties that you really plan to use.
Apparently your Institution is in exactly one City, namely the City that the foreign key (CityId?) is referring to. If Institution [10] is located in City [15], then Institution.CityId will have a value 15, equal to City.Id. So you are transferring this value twice.
using (var dbContext = new MyDbContext())
{
IQueryable<Institution> filteredInstitutions = (filter == null) ?
dbContext.Institutions :
dbContext.Institutions.Where(filter);
return filteredInstitutions.Select(institution => new Institution
{
// Select only the Institution properties that you actually plan to use:
Id = institution.Id,
Name = institution.Name,
City = new City
{
Id = institution.City.Id,
Name = institution.City.Name,
...
}
// not needed: you already know the value:
// CityId = institution.City.Id,
});
Possible improvement
Apparently you chose to add a layer between entity framework and the users of your functions: although they use your functions, they don't really have to know that you use entity framework to access the database. This gives your the freedom to use SQL instead of entity framework. Hell, it even gives you the freedom to get rid of your database and use an XML file instead of a DBMS: your users won't know the difference: nice if you want to write unit tests.
Although you chose to separate the method you use to persist the data, you chose to expose your database layout, inclusive foreign keys to the outside world. This makes it more difficult to change your database in future: your users have to change as well.
Consider writing repository classes for Institution and City that only expose those properties that the users of your persistency really need. If people only query "some properties of institutions with some properties of the City in which they are located", or the other way round "Several properties of Cities with several properties of the Institutions located in these Cities", then they won't need the foreign keys.
The intermediate repository classes give you more freedom to change your database. Apart from that, it will give you the freedom to hide certain properties for certain users.
For instance: suppose you add the possibility to delete an institution, but you don't want to immediately delete all information about this institution, for instance because this allows you to restore if someone accidently deletes the institution, you might add a nullable property ObsoleteDate
Moest people that query institutions, don't want the obsolete institutions. If you had an intermediate repository institution class, where you omitted the ObsoleteDate, and all queries removed all Institutions that have a non-zero ObsoleteData, then for your users it would be as if an obsolete institution would have been deleted from the database.
Only one user will need access to the ObsoleteDate: a cleaner task, that every now and then deleted all Institutions that are obsolete for a considerable time.
A third improvement for an intermediate repository class would be that you can give different users access to the same data, with different interfaces: some users can only query information about institutions, some are also allowed to change some data, while others are allowed to change other data. If you give them an interface, they can break this by casting them back to the original Institution.
With separate repository classes, you will have the possibility to give each of these users their own data, and nothing more than this data.
The disadvantage of a repository pattern is that you have to think about different users, and create different query functions. The advantages is that a repository is easier to change and easier to test, and thus easier to keep everything bug free after future changes.

Does EF6 virtual navigation property result into SQL query?

Assuming that I have an entity with virtual nagivation property like this:
public class School
{
public long Id { get; set; }
public virtual ICollection<Students> Students { get; set; }
}
As I understand, EF6 uses proxy to enable lazy-loading of Students, but do the following LINQ queries:
var myStudent = this.Students.Single(x => x.Id == id);
var studentsCount = this.Students.Count();
var bestStudents = this.Students
.OrderByDescending(x => x.GPA)
.Take(5)
.ToArray();
result into a SQL query (just like IQueryable<T> does)? Or is it just a lazy-loading collection and will load all students into memory after the first request, and then perform simple IEnumerable<T> LINQ behaviour?
When you query for an entity in Entity Framework, the objects that get returned are not (always) the type of object you think they are. Behind the scenes, it creates a brand new class that inherits from the your class. Because OOP allows a subclass to be stored in a variable typed as the superclass, you never really notice. This is the "proxy" that you mention. That's why the virtual function allows lazy loading. The subclass overrides your virtual method, and contains the code to lazy load the extra data before returning it.
That overridden property call will then check the context to see if the navigation properties are already loaded. If they are, it just returns them. If they are not, it will make additional SQL calls to load them, storing them in the DbContext for later.
In your updated question, my understanding is that running those lines of code would result in 3 separate queries being executed.
Public virtual properties are lazy loading in EF6.
You can disable lazy loading for a DbContext, or use the .Include() method on the IQueryable to include the property in the first query.
http://www.entityframeworktutorial.net/EntityFramework4.3/lazy-loading-with-dbcontext.aspx
https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
Once you "iterate" through the list (e.g. by calling the .Single(), .Count() or .ToArray() method), the query is executed and you have a in-memory list of your students. See https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/query-execution for more details about query execution.
The first example would result in 3 queries, where the first returns the student with the given Id, the second returns the student count and the third returns the first 5 students ordered desc by their GPA property.
Regarding DDD => You can use some Layered architecture, with ApplicationServices and DomainServices, where DomainServices perform the domain logic and ApplicationServices load / transform the data.
The https://aspnetboilerplate.com/ template is for example a good starting point for domain driven design.
Assuming that the second code block is executed somewhere INSIDE the scope of an instance of 'School' (so 'this' is an instance of 'School' - in my example below school19) then the following scenarios are possible:
A) You have loaded your instance of 'School' like this (Lazy loading enabled):
var school19 = dbContext.Set<School>()
.FirstOrDefault(school => school.Id == 19)
Then your 3 lines of code for accessing the property 'Students' will trigger a single additional database hit when
var myStudent = this.Students.Single(x => x.Id == id);
is executed, but no more database hits will occur with the subsequent two statements.
B) In case you have loaded your instance of 'School' like this (Lazy loading enabled):
var school19 = dbContext.Set<School>()
.Include(school => school.Students)
.FirstOrDefault(school => school.Id == 19)
Then your 3 lines of code for accessing the property 'Students' will not trigger an additional database hit.
C) If lazy loading was disabled, then
A) would result in a Null Reference Exception
B) would behave the same
As a last remark, if 'this' was a reference to an instance of the DBContext class, which has a property
public Set<School> Schools { get; set; }
then it would trigger 3 different database calls. But the result is different as this would be executed in the context of ALL schools, while my assumptions above only apply to a single school.

Working with two non-directly related tables in one NHibernate query

I am new to NHibernate and am not sure if what I am asking makes sense.
I am trying to rewrite some code I currently have:
public IEnumerable<Order> GetByQueue(OrderStatus orderStatus, Queue queue)
{
var criteria = NHibernateSession.CreateCriteria(typeof (TaskDevice), "TaskDevice");
//Pull up all Tasks where a Task's TaskDevice's SourceSiteID or DestinationSiteID are represented in a Queue's QueueLocations.
foreach(QueueLocation queueLocation in queue.QueueLocations)
{
criteria.Add(
Expression.Disjunction()
.Add(Restrictions.Eq("OriginalLocationID", queueLocation.ComponentID))
.Add(Restrictions.Eq("LocationID", queueLocation.ComponentID))
);
}
//Get a hold on all the Tasks returned from TaskDevices.
List<Task> tasks = criteria.List<TaskDevice>().Select(taskDevice => taskDevice.Task).ToList();
//Return all Orders of the given Tasks whose OrderStatus matched the provided orderStatus.
return tasks.Where(task => task.Order.OrderStatus == orderStatus).Select(task => task.Order);
}
This code currently depends on a Queue object. I would like to change this code such that a queueID is provided instead of a Queue object. The table QueueLocation contains 'QueueID' for one of its columns.
This means that I now need to interact with another table in my database, QueueLocation, load the QueueLocation who has a QueueID matching the provided QueueID, and then emulate the adding of restrictions without iterating over a Queue object.
Task does not know of Queue and Queue does not know of Task. They are related by the fact that a Queue may contain a QueueLocation whose ComponentID matches a Task's OriginalLocationID or LocationID.
If I change my initial criteria declaration to:
var criteria = NHibernateSession
.CreateCriteria(typeof (TaskDevice), "TaskDevice")
.CreateCriteria("QueueLocation", "QueueLocation");
then an exception is generated indication that NHibernate could not find property QueueLocation on TaskDevice. This is a valid exception -- TaskDevice does not know of QueueLocation.
I am wondering how to load two non-related tables using NHibernate such that I may filter my restrictions fully through NHibernate in one query. Is this possible?
Criteria is not a good API for queries with entities that are not related in the model.
Use HQL instead.

EF Query with multiple contexts

I have two EF contexts _inventoryContext and _auctionContext.
_inventoryContext has a property called Items and _auctionContext has one called Auctions. Items is a collection of Item objects which each contain a Guid to identify them uniquely. The Auctions property is a collection of Auction objects which each contain a Guid InventoryReference that refers to one of the elements of Items.
What I want to do is get a list of all inventory items that are not part of an auction. How do I do this?
This may be of help to you.
Alternatively, you can do this in 2 steps: First get a collection of GuidReferences from your Auction, and then fetch the Items whose Guid's are included in the collection. There will be a performance hit because of the extra query, and because the framework will need to allocate the Guid collection. But depending on the Item collection size, that may not be a big deal for you.
Another possibility would be to create a view in one database/context that pulls the data from the other. This would be read-only, however.
There is a better solution in EF Core
You can create view as named Auctions one of your context and map the DbSet model in your code. So you can use other context model and table in another context. But you must ensure your db user can access those two contexts.
For example in _inventoryContext you can define like that.
public virtual DbSet<Auction> Auctions { get; set; }
modelBuilder.Entity<Auction>(entity =>
{
entity.ToView("vwAuctions");
}
It's provides you something like that
var result= from x in _inventoryContext.InventoryReference
join y in _inventoryContext.Auctions on x.Id equals y.InvRef
select x;

NHibernate IQueryable collection as property of root

I have a root object that has a property that is a collection.
For example:
I have a Shelf object that has Books.
// Now
public class Shelf
{
public ICollection<Book> Books {get; set;}
}
// Want
public class Shelf
{
public IQueryable<Book> Books {get;set;}
}
What I want to accomplish is to return a collection that is IQueryable so that I can run paging and filtering off of the collection directly from the the parent.
var shelf = shelfRepository.Get(1);
var filtered = from book in shelf.Books
where book.Name == "The Great Gatsby"
select book;
I want to have that query executed specifically by NHibernate and not a get all to load a whole collection and then parse it in memory (which is what currently happens when I use ICollection).
The reasoning behind this is that my collection could be huge, tens of thousands of records, and a get all query could bash my database.
I would like to do this implicitly so that when NHibernate sees an IQueryable on my class it knows what to do.
I have looked at NHibernate's LINQ provider and currently I am making the decision to take large collections and split them into their own repository so that I can make explicit calls for filtering and paging.
LINQ To SQL offers something similar to what I'm talking about.
I've been trying to come up with a solution for a similar problem.
You can filter collections off an entity using ISession.FilterCollection. This creates an additional IQuery where you can count, page, add criteria, etc.
So, for example (my query in FilterCollection may be a little off, but you should get the idea):
ISession session = GetSession();
var shelf = session.Get<Shelf>(id);
var books = session.FilterCollection(shelf.Books, "where Name = :title").SetString("title", "The Great Gatsby").List<Book>();
There are a problem with that, however:
The consumer executing the code
needs to access
ISession.CreateFilter, or you need
to create a method on your
repository that takes in a property,
a query, and your query arguments
(as well as any paging or other
information). Not really the sexiest
thing on the planet.
It's not the LINQ you wanted.
Unfortunately, I don't think there's any way to get what you want out of the box with NHibernate. You could fake it, if you wanted to try, but they seem to fall flat to me:
Add a method or property that under the covers returns a LINQ to NHibernate IQueryable for this shelf:
public IQueryable<Book> FindBooks() {
return Resolver.Get<ISession>().Linq<Book>().Where(b => b.Shelf == this);
}
where someone might consume that like this:
var shelf = ShelfRepo.Get(id);
var books = (from book shelf.FindBooks()
where book.Title == "The Great Gatsby"
select book);
Yuck! You are bleeding your persistence needs through your domain model! Maybe you could make it a little less worse by having a repository emit IQueryable, which at runtime is actually LINQ to NHibernate:
public IQueryable<Book> FindBooks() {
return Resolver.Get<IRepository<Book>>().CreateQuery().Where(b => b.Shelf == this);
}
Still pretty blah.
Create your own custom collection type (and potentially an IQueryable implementation) that wraps a private field of the actual books, and map NHibernate to that field. However, it may be a difficult undertaking getting that working with ISession.CreateFilter. You have to consider "discovering" the current session, converting the LINQ expression into something you can use in CreateFilter, etc. Plus, your business logic is still dependent on NHibernate.
Nothing really satisfies at this point. Until NHibernate can do LINQ over a collection for you, it appears that you're better off just querying your Book repository normally as has already been suggested, even if it doesn't seem as sexy or optimal.
I tend to think like this:
Aggregate roots are boundaries of consistency, so if shelf needs to enforce some sort of consistency policies on the books it contains, then it should be an aggregate root.
And in such case it should hold a set/collection of books.
If you don't need to enforce consistency in any way from shelf to books, then I'd consider to remove the set/collection property and move those queries into a repository instead.
Also, since pagination and filtering most likely don't have anything to do with your domain logic, it is most likely for presentation.
Then I'd consider to make some special view for it instead of adding presentation facillities to my repositories.
e.g.
var result = Queries.FindBooksByShelf(shelfId,pageSize);
Such query could return projections and/or be optimized as plain SQL etc.
They are most likely specific for a certain view or report in your GUI.
This way your domain will focus on domain concepts only.
Perhaps you should give Nhibernate Linq a try. It allows you use the IQueryable and do things like:
Session.Linq<Book>().Where(b => b.Name == "The Great Gatsby");

Categories