I have a project model which has multiple "open tasks":
[NotMapped]
public IEnumerable<Task> OpenTasks
{
get
{
//All tasks where status id == 1 (= open tasks)
return Tasks.Where(t => t.TaskStatusId == (int)EnumTaskStatus.taskStatus.openTask);
}
}
A task model has multiple users:
public virtual ICollection<Useraccount> Useraccounts { get; set; }
Now I would like to get all open tasks of a specific user of a project.
It would look like this:
project.OpenTasks.Where(my user is in t => T.useraccounts);
But I'm not sure how my where condition would look like.
Try this:
project.OpenTasks.Where(x => x.Useraccounts.Contains(myUser));
If Useraccount doesn't override the equality members, you need to use something like this:
project.OpenTasks.Where(x => x.Useraccounts.Any(x => x.Id == myUser.Id));
Related
Is there a way to easily include entities from a relationship on a custom property in EF Core in a single query? For example, I have the following structure:
public class Report {
public int TenantId { get; set; }
public string Name { get; set; }
public List<Document> Documents { get; set; }
//...
}
public class Document {
public int TenantId { get; set; }
public int Id { get; set; }
//...
}
I can't change this structure, i.e. I can't add a property to the Report class to create another relation because Reports are not exclusively related to Documents and I don't want a reference to a Report in a Document.
In my DbContext I declared the relation like this:
modelBuilder.Entity<Document>(b => {
b.HasIndex(e => new { e.TenantId });
});
modelBuilder.Entity<Report>(b => {
b.HasIndex(e => new { e.TenantId });
b.HasMany(r => r.Documents).WithOne().HasPrincipalKey("TenantId");
});
What I want is to include all Documents in the Report for the specified TenantId. Think of the following database excerpt:
Documents
Id
TenantId
1
1
2
1
3
1
4
2
Reports
Name
TenantId
abc
1
def
2
When fetching all reports I want to have the possibility to include all of the tenant's Documents. For example, fetching the Report abc (Tenant 1) should also populate the List in the Report object with Documents 1-3 (because they're all Tenant 1). When fetching the Report def it should only populate the List with Document 4.
Unfortunately it doesn't work to just call...
reportRepository.GetAll().Where(r => r.Name == 'abc').Include(r => r.Documents)
... the list is always empty.
In fact, I could achieve exactly what I'm trying to accomplish with something like this:
var report = reportRepository.GetAll().Where(r => r.Name == 'abc').First();
report.Documents = documentRepository.GetAll().Where(d => d.TenantId == report.TenantId).ToList();
... but I would really like to have a cleaner solution that doesn't require two queries. This would also make it easier for later reusability because there are more of these structures in the code where I need similar functionality.
Is it possible in EF Core to convert the above two queries into one single query that just includes the Documents that relate on a given property (here TenantId)?
try set HasForeignKey and HasPrincipalKey
modelBuilder.Entity<Report>(b => {
b.HasIndex(e => new { e.TenantId });
b.HasMany(r => r.Documents)
.WithOne()
.HasPrincipalKey(d => d.TenantId)
.HasForeignKey(r => r.TenantId);
});
I want to write a simple query, but there are some problems.
I have 2 tables M to N:
Users -> Events.
I want to get all users of a specific event (get this event by eventId).
public IQueryable<User> GetUsersByEventId(int eventId)
{
IQueryable<User> query = this.Context.Users.AsQueryable();
return query.Where(x => x.Events.SingleOrDefault(e => e.EventId == eventId)); ??
}
Something is missing and I dont know what, can someone help me? Thanks a lot !
If I understand you correctly (adding your models would help), I think you want Any
public IQueryable<User> GetUsersByEventId(int eventId)
{
return Context.Users
.Where(u => u.Events.Any(e => e.EventId == eventId));
}
This should return all users who have any event matching the given id.
Note: If you set up your relationships correctly, you should be able to get this directly from the Event.
public class Event
{
...
public virtual ICollection<User> Users { get; set; }
}
So then, you'd get the Event by id and access it's user collection.
var evt = repo.GetEventById(id);
var users = evt.Users;
I suggest you do that in your Event model itself. AFAIK you are using Event, User and EventUsers tables which is standard stuff for many2many.
public class Event
{
public int Id { get; set; }
// ...
public virtual ICollection<EventUsers> EventUsers { get; set; } // This is table that holds EventId, UserId (many2many)
public IQueryable<User> Users { get { return this.EventUsers.Select(x => x.User); } } // Get all users that are in this event
}
I store User objects in RavenDB. Each User has a User.Id property.
I also have a Relationship class that links two User.Ids together to create a Mentor/Mentee relationship, like this:
public class User
{
public string Id { get; set; }
public string UserName { get; set; }
... more properties
}
public class Relationship
{
public string Id { get; set; }
public string MentorId { get; set; }
public string MenteeId { get; set; }
public RelationshipStatus Status { get; set; }
}
Now I want to retrieve a list of Mentees for a given Mentor. I have done this in the following way:
public static List<User> GetMentees(IDocumentSession db, string mentorId)
{
var mentees = new List<User>();
db.Query<Relationship>()
.Where(r => r.MentorId == mentorId)
.Select(r => r.MenteeId)
.ForEach(id => mentees.Add(db.Load<User>(id)));
return mentees;
}
This seems to work just fine but the coding-angel on my shoulder is wrinkling her nose at the smells emanating from the nested use of the IDocumentSession (db) and the need for multiple Load calls to fill the Mentees List.
How can I optimise this method using best practice RavenDB syntax?
Edit
Thanks to #Jonah Himango (see accepted answer below) who solved the problem of multiple calls to the database for me. In addition I have also created a new extension method called 'Memoize' to eliminate the need for the external 'mentees' result List (see code above).
Here is the optimised code. Please feel free to comment and refine further.
The Linq
public static List<User> GetMentees(IDocumentSession db, string mentorId)
{
return db.Query<Relationship>()
.Customize(x => x.Include<Relationship>(o => o.MenteeId))
.Where(r => r.MentorId == mentorId)
.Memoize()
.Select(r => db.Load<User>(r.MenteeId))
.ToList();
}
The extension method
public static List<T> Memoize<T>(this IQueryable<T> target)
{
return target.ToList();
}
Note : This extension method may seem completely superfluous (it is really) but it irritates my geek-gland that I have to call a function called ToList(), not to create a list, but to force the execution of the Linq statement. So my extension method just renames ToList() to the far more accurate Memoize().
You'll want to use the .Include query customization to tell Raven to include the related user object off of each Relationship:
db.Query<Relationship>()
.Customize(x => x.Include<Relationship>(o => o.MenteeId))
.Where(r => r.MentorId == mentorId)
.Select(r => r.MenteeId)
.ForEach(id => mentees.Add(db.Load<User>(id))); // .Load no longer makes a call to the DB; it's already loaded into the session!
Relevant documentation here:
The call to Load() is resolved completely client side (i.e. without
additional requests to the RavenDB server) because the [related]
object has already been retrieved via the .Include call.
I've got some data in a table that looks like so:
Recipe | Category | Email
What I'd like to do is pull this data back from the source and put it into something that looks like so:
public class RecipeItem
{
public long Recipe { get; set; }
public long Category { get; set; }
public List<string> Names {get; set; }
}
Grouping by the Recipe and Category ids and putting all the emails that into the list.
So, what I've tried is to do something like this:
var recipeItems =
from entry in list
group entry by new { entry.Recipe, entry.Category}
into aRecipe
select new RecipeItem()
{
Recipe = aRecipe.Key.Recipe,
Category = aRecipe.Key.Category,
// ? Not sure how to stick the list of names in here
};
list is the data pulled back via entity framework.
But this isn't quite right - I think I'm close here (maybe). What am I missing here on this?
Follow-up:
Thanks to Aducci for clearing this up. The answer is that you can do this:
Names = aRecipe.Select(x => x.Name)
and this will add all those Names which are in each group into the Names collection for that group. Pretty nifty.
I would modify your class to look like this
public class RecipeItem
{
public long Recipe { get; set; }
public long Category { get; set; }
public IEnumerable<string> Names {get; set; }
}
And your link to entities query to:
var recipeItems =
from entry in list
group entry by new { entry.Recipe, entry.Category}
into aRecipe
select new RecipeItem()
{
Recipe = aRecipe.Key.Recipe,
Category = aRecipe.Key.Category,
Names = aRecipe.Select(x => x.Name)
};
I am using NHibernate 3.x, along with Fluent NHibernate, and have not had any issues constructing database queries until now.
To simplify my objects for the purposes of this post, I've included a subset of my object and mapping structures below:
IssueItem POCO entity class:
public class IssueItem : DomainEntity, IKeyed<Guid> {
public virtual Guid ID { get; set; }
public virtual string Subject { get; set; }
public virtual string Description { get; set; }
public virtual IList<IssueLocation> Locations { get; set; }
}
Location POCO entity class:
public class Location : DomainEntity, IKeyed<Guid> {
public virtual Guid ID { get; set; }
public virtual string City { get; set; }
public virtual string State { get; set; }
public virtual string Zip { get; set; }
public virtual string Organization { get; set; }
public virtual IssueItem Issue { get; set; }
}
IssueItem Fluent NHibernate map:
public class IssueItemMap : DomainEntityMapping<IssueItem> {
public IssueItemMap()
{
Table("IssueItem");
LazyLoad();
Map(x => x.ID).Column("ID");
Map(x => x.Subject).Column("Subject");
Map(x => x.Description).Column("Description");
HasMany(x => x.Locations).KeyColumn("IssueItemID").LazyLoad().ReadOnly().Inverse();
}
}
Location Fluent NHibernate map:
public class LocationMap : DomainEntityMapping<Location> {
public LocationMap()
{
Table("Location");
LazyLoad();
Map(x => x.ID).Column("ID");
Map(x => x.City).Column("City");
Map(x => x.State).Column("State");
Map(x => x.Zip).Column("Zip");
Map(x => x.Organization).Column("Organization");
References(x => x.IssueItem).ForeignKey("IssueItemID").LazyLoad().ReadOnly();
}
}
Now, I'm using a Unit of Work and Service/Repository pattern in my MVC app. Therefore, I have a domain layer of my project that contains my basic POCO entities, as well as validators and services. In my data layer, I've got my NHibernate-related stuff, such as my repositories that my domain layer access from my services. This is where my NHibernate maps live as well.
In order to ensure that no NHibernate-specific logic creeps into my domain layer (in case I want to use a different ORM in the future), I perform my LINQ statements in my services within my domain layer against IQueryable objects returned from the repositories in my data layer. Therefore, when I write my queries, I am using System.Linq and System.Linq.Expressions instead of the NHibernate.Linq class.
That said, here's my LINQ query I'm having issues with from within one of my service classes in my domain layer:
var issues = _issueRepo.All();
if (!string.IsNullOrWhiteSpace(searchWords)) {
issues = issues.Where(i => i.Subject.Contains(searchWords)
|| i.Description.Contains(searchWords)
|| i.Locations.Where(l => l.Organization.Contains(searchWords)
|| l.City.Contains(searchWords))
.Select(x => x.IssueItemID).Contains(i.ID)
);
}
Now, the IssueItems are queried just fine. However, the one-to-many table (Locations) is not properly queried. This is what I mean...
The generated T-SQL statement is perfect except for the very end of it. Example:
select TOP(100) issueitem0_.ID as ID2_, issueitem0_.Subject as Subject2_, issueitem0_.Description as Description2_
from IssueItem issueitem0_
where issueitem0_.Subject like ('%test%') or issueitem0_.Description like ('%test%')
or exists (select location1_.IssueItemID from Location location1_ where
issueitem0_.ID=location1_.IssueItemID and (location1_.Organization like ('%test%')
or location1_.City like ('%test%')) and location1_.ID=issueitem0_.ID)
See that last bit? It throws in that last "and" statement (and location1_.ID=issueitem0_.ID) that throws a wrench in the whole system. I have tweaked every configuration parameter I could think of with my mapping and have tried many different LINQ statements and I cannot get rid of that last part. I don't know why it adds it.
If I construct the same LINQ statement in LINQPad, it properly generates the T-SQL statement without the last part (and location1_.ID=issueitem0_.ID).
Any ideas?
Thanks!
Joel
Add Any() when you query locations. It will come true if any location property contains what you are looking for. You are trying to select in where clause, then trying to get IssueID from there. I think you will see this query is clearer.
var issues = _issueRepo.All();
if (!string.IsNullOrWhiteSpace(searchWords))
{
issues = issues.Where(i => i.Subject.Contains(searchWords)
|| i.Description.Contains(searchWords)
|| i.Locations.Any(l => l.Organization.Contains(searchWords))
|| i.Locations.Any(l => l.City.Contains(searchWords)) )
}