How can I write a query with the build-in linq provider of NHibernate including eager loading and restrictions on the details? For example
public class Library
{
public Library()
{
Books = new List<Book>();
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Book> Books { get; protected internal set; }
}
public class Book
{
public Book()
{
Pages = new List<Page>();
}
public virtual int Id { get; set; }
public virtual Library Library { get; set; }
public virtual string Title { get; set; }
}
the following query shows what I need but does not load eagerly
var query = from master in session.Query<Library>()
from detail in master.Books
where detail.Title == detailValue
select master;
The following query does not work ...
var query = from master in session.Query<Library>()
// not allowed - causes Runtime error
.FetchMany(m => m.Details.Where(d => d.Value == detailValue))
select master;
Thanks a lot in advance.
Carsten
You may want to consider using queryOver here instead:-
Book book = null;
var query =
Session.QueryOver<Library>()
.Fetch(f => f.Books).Eager
.Left.JoinAlias(f => f.Books, () => book)
.Where(() => actor.book == detailValue);
I may be wrong but I don't think the NH LINQ provider can support this at the moment.
Also note the .left this is important, see this blog post for reasons why
Related
I am working on .NET Core application with Entity Framework Core. I have three tables Questions, Answer and AnswerType
so schema is as
question <-- 1:* --> Answers <-- 1:1--> AnswerTypes
I need to run query that return questions with ICollection of Answer , further Answer with AnswerType
Question
public class QuestionDataModel
{
public QuestionDataModel()
{
Answers = new HashSet<AnswerDataModel>();
}
public Guid Id { get; set; }
public virtual ICollection<AnswerDataModel> Answers { get; set; }
}
Answer
public class AnswerDataModel
{
public AnswerDataModel()
{
}
public Guid Id { get; set; }
public Guid QuestionId { get; set; }
public virtual QuestionDataModel Question { get; set; }
public string Value { get; set; }
public Guid AnswerStatusTypeId { get; set; }
public virtual AnswerStatusTypeDataModel AnswerStatusType { get; set; }
}
AnswerStatusType
public class AnswerStatusTypeDataModel
{
public AnswerStatusTypeDataModel()
{
Answers = new HashSet<AnswerDataModel>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<AnswerDataModel> Answers { get; set; }
}
I have tried nested join to get AnswerStatusType of each answer in collection but getting error "invalid anonymous type member declared, anonymous type member must be declared with a member assignment, simple name or member access". This error appears in 2nd nested join in following code,
linq
var query3 = Context.Questions.Join(Context.Answers,
question => question.Id,
answer => answer.QuestionId,
(question, answer) => new
{
question.Id,
question.Title,
question.Answers.Join(Context.AnswerStatusTypes,
answer => answer.AnswerStatusTypeId,
answerStatus => answerStatus.Id,
(answers, answerStatus) => new
{
answerStatus
})
}
);
Configuration Classes
and configuration classes as
Question Config
public void Configure(EntityTypeBuilder<QuestionDataModel> builder)
{
builder.ToTable("Questions");
builder.HasKey(question => question.Id);
builder.HasMany(question => question.Answers);
}
Answer Config
public void Configure(EntityTypeBuilder<AnswerDataModel> builder)
{
builder.ToTable("Answers");
builder.HasKey(answer => answer.Id);
builder
.HasOne(answer => answer.Question)
.WithMany(question => question.Answers)
.HasForeignKey(answer => answer.QuestionId);
builder
.HasOne(answer => answer.AnswerStatusType)
.WithMany(answerType => answerType.Answers)
.HasForeignKey(answer => answer.AnswerStatusTypeId);
}
AnswerStatus COnfig
public void Configure(EntityTypeBuilder<AnswerStatusTypeDataModel> builder)
{
builder.ToTable("AnswerStatusTypes");
builder.HasKey(answerStatusType => answerStatusType.Id);
builder.HasMany(answerStatusType => answerStatusType.Answers);
}
Your entity configurations look correct to me.
As #Ivan Stoev pointed out,
var questions = context.Questions
.Include(x => x.Answers)
.ThenInclude(x => x.AnswerStatusType)
// Now since we have the AnswerStatusType loaded we can do something like this as well.
//.Where(x => x.Answers.Conatins(a => a.AnswerStatusType.Name == "Some Status Name"))
.ToList();
This should do!
var result = Context.Questions
.Include(x=>x.Answers.Select(y=>y.AnswerType))
.Where(x=>x.ID == QuestionidYouAreLookingFor)
.ToList();
make sure include System.Data.Entity namespace
When you call ToList() its actually going to make SQL call and execute the query. If you want lazy loading then don't call ToList()
I have been trying to get a list of all Workflows that have Offices contained in a certain List by Office Id. I can easily get all of the Workflows that have SingleWorkflowSteps because they have only one Office, but have been unable to understand how I would successfully get those contained in a MultiWorkflowStep. All workflow steps have either a SingleWorkflowStep or a MultiWorkflowStep that contains two or more SingleWorkflowSteps. At the time I designed this, it seemed like a logical way to do this but atlas my LINQ-fu is not as good as I thought it was. Can someone please point me in the right directions. Code listed below:
var OfficesToFind = new List<int> (new int[] { 1,3,5,7,9,10,11,12} );
public class Workflow
{
public Workflow()
{
WorkflowSteps = new List<WorkflowStepBase>();
}
public int Id { get; set; }
public virtual ICollection<WorkflowStepBase> WorkflowSteps { get; set; }
}
public abstract class WorkflowStepBase
{
public int Id { get; set; }
public int StatusId { get; set; }
public virtual Workflow Workflow { get; set; }
public virtual Status Status { get; set; }
}
public class MultiWorkflowStep : WorkflowStepBase
{
public MultiWorkflowStep()
{
ChildSteps = new List<SingleWorkflowStep>();
}
public virtual ICollection<SingleWorkflowStep> ChildSteps { get; set; }
}
public class SingleWorkflowStep : WorkflowStepBase
{
public int? ParentStepId { get; set; }
public int OfficeId { get; set; }
public virtual MultiWorkflowStep ParentStep { get; set; }
public virtual Office Office { get; set; }
}
public class Office
{
public int Id { get; set; }
public string Name { get; set; }
}
public class WorkflowService : IWorkflowService<Workflow>
{
private readonly IRepository<Workflow> _workflowService;
private readonly IRepository<SingleWorkflowStep> _singleStepService;
private readonly IRepository<MultiWorkflowStep> _multiStepService;
public WorkflowService(IUnitOfWork uow)
{
_workflowService = uow.GetRepository<Workflow>();
_singleStepService = uow.GetRepository<SingleWorkflowStep>();
_multiStepSercice = uow.GetRepository<MultiWorkflowStep>();
}
// ~ ------- Other CRUD methods here -------- ~
public IEnumerable<Workflow> GetWorkflowFilter(List<int> statuses, List<int> offices...)
{
var query = _workflowService.GetIQueryable(); // returns an IQueryable of dbset
if(statuses.Any())
{
query = query.Where(q => statuses.Contains(q.StatusId));
}
if(offices.Any())
{
// Get all active single steps and the ones that contain the offices
singleSteps = _singleStepService
.Where(s => s.StatusId == (int)Enumerations.StepStatus.ACTIVE)
.Where(s => offices.Contains(s.OfficeId));
// Get all of the parent Workflows for the singleSteps
var workflows = singleSteps.Select(w => w.Workflow);
// Update the query with the limited scope
query = query.Where(q => q.Workflow.Contains(q));
}
return query.ToList();
}
}
OK, after a good night sleep, being all bright-eyed and bushy-tailed, I figured out my own problem. First the updated code was all wrong. Because each derived WorkflowStep has access to the Workflow and each MultiWorkflowStep contains a list of SingleWorkflowSteps - when I get the list of all SingleWorkflowSteps (which would include all from MultiWorkflowStep(s)), I simply needed to get a list of all of the parent Workflows of the SingleWorkflowSteps. Next I updated my query to limit the Workflows that were contained in the new Workflow list and here is the correct code for the GetWorkflowFilter method:
...
if(offices.Any())
{
// Get all active single steps and the ones that contain the offices
singleSteps = _singleStepService.Where(s => s.StatusId == (int)Enumerations.StepStatus.ACTIVE).Where(s => offices.Contains(s.OfficeId));
// Get all of the parent Workflows for the singleSteps
var workflows = singleSteps.Select(w => w.Workflow);
// Update the query with the limited scope
query = query.Where(q => q.Workflow.Contains(q));
}
return query.ToList();
}
I am using Entity Framework and this is my view model:
public class UserDetailsModel:CityModel
{
public int Id { get; set; }
public string Fullname { get; set; }
}
public class VendorInCategoryModel
{
public int CategoryId { get; set; }
public int VendorId { get; set; }
public virtual CategoryMasterModel CategoryMaster { get; set; }
public virtual UserDetailsModel UserDetails { get; set; }
}
public class CategoryMasterModel
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
}
This is my query to fetch vendor details along with category details of particular vendor say v001:
UserDetailsModel workerDetails = context.UserDetails.
Where(d => d.Id == _vendorId).
Select(d => new UserDetailsModel
{
Id = d.Id,
Fullname = d.Fullname,
CategoryId = d.VendorInCategory.Select(v => v.CategoryId).FirstOrDefault(),
}).SingleOrDefault();
Here I have used FirstOrDefault to fetch categoryId (that is single value)
But I don't want to use FirstOrDefault as I have used in so many queries and it is giving me wrong output in some cases. So that the reason why I don't want to use FirstOrDefault.
When I have written SingleOrDefualt in place of FirstOrDefault it is throwing me error
that use FirstOrDefault.
So how to overcome this? Can anybody please help me?
It looks like maybe your outer select is capable of returning multiple results (e.g. if there are more than one UserDetailsModel with the same Id). If it returns multiple results then your call to .SingleOrDefault() will throw an exception as it expects only a single result or no results. See LINQ: When to use SingleOrDefault vs. FirstOrDefault() with filtering criteria for more details.
I am working with the EF6 and I am a big fan of the dynamic proxies, which enables lazy loading and change tracking. Anyway I am not happy, that the lazy loading is triggered once the property is accessed instead of loading the data, when the enumerator or the count property is called first. Therefore I tried to diesable the proxys and replace them by custom proxies. It was an easy thing to use a custom object context and overload the CreateObject method. Unfortantly the ObjectMaterialized event cannot replace the entity and I am not able to replace an entity from a query. The creation of the object lies deep in internal classes of the framework.
Has anybody an idea how to use custom proxies? Or how I am able to replace the entities materialized in an object query?
You should .Include the properties you want to fetch so that you avoid an N+1 query problem.
public class User
{
public int Id { get; set; }
public string Name { get; set ;}
public virtual ICollection<Post> Posts { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set ; }
public int AuthorId { get; set; }
public virtual User Author { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int Id { get; set; }
public string Note { get; set ;}
public int PostId { get; set; }
public virtual Post Post { get; set; }
public int AuthorId { get; set; }
public virtual User Author { get; set; }
}
public class BlogContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> Comments { get; set; }
}
Then this is BAD in that it'll do tons of queries:
using (var db = new BlogContext())
{
var user = db.Users.Single(u => u.Id=5)); // +1 query
foreach (var post in user.Posts) // N queries
{
var message = String.Format("{0} wrote {1}", user.Name, post.Title);
Console.WriteLine(message);
foreach (var comment in post.Comments) // N * M queries!
{
// and that .Author make N * M MORE!
var message = String.Format("\t{0} commented {1}", comment.Author.Name, comment.Note);
Console.WriteLine(message);
}
}
}
And this is GOOD in that it'll do one query:
using (var db = new BlogContext())
{
var user = db.Users
.Single(u => u.Id=5))
.Include(u => u.Posts) // eliminates the N post queries
.Include(u => u.Posts.Comments) // eliminates the M comment queries
.Include(u => u.Posts.Comments.Author); // eliminates the M comment author queries
foreach (var post in user.Posts) // N queries
{
var message = String.Format("{0} wrote {1}", user.Name, post.Title);
Console.WriteLine(message);
foreach (var comment in post.Comments) // N * M queries!
{
// and that .Author make N * M MORE!
var message = String.Format("\t{0} commented {1}", comment.Author.Name, comment.Note);
Console.WriteLine(message);
}
}
}
I'm trying to create a single linq query which populates the following models in the CompanyViewModel constructor:
public class CompanyViewModel
{
public IList<CompanyUserViewModel> CompanyUsers { get; set; }
...
}
public class CompanyUserViewModel
{
public User User { get; set; }
public IList<UserOperationViewModel> UsersOperations { get; set; }
}
public class UserOperationViewModel
{
public Operation Operation { get; set; }
public int Permission { get; set; }
}
Currently I've got the following query:
return db.Users.Where(u => u.CompanyId == companyId)
.Select(u => new CompanyUserViewModel {
User = u,
UsersOperations = db.UsersInOperations
.Where(uo => uo.UserId == uo.UserId)
.Select(uo => new UserOperationViewModel{
Operation = uo.Operation,
Permission = uo.Permission
}).ToList()
}).ToList();
Which builds, but when the page runs I get
LINQ to Entities does not recognize the method 'System.Collections.Generic.List`1[WoodCo.Models.BusinessObject.UserOperationViewModel] ToList[UserOperationViewModel](System.Collections.Generic.IEnumerable`1[WoodCo.Models.BusinessObject.UserOperationViewModel])' method, and this method cannot be translated into a store expression.
What does one do?
Change your view model properties to use IEnumerable<T> instead of IList<T and remove the .ToList() calls.