class Topic {
public int TopicId {get;set;}
public virtual ICollection<Post> Posts { get; set; }
public Post FirstPost {
get {
return this.Posts.OrderBy(p=> p.PostedDate).FirstOrDefault();
}
}
}
class Post {
public int PostId {get;set; }
public int TopicId {get;set;}
public DateTime PostedDate {get;set;}
public virtual Topic Topic {get;set;}
}
var query = Database.Forums.Where(p=> p.Id == id).Select(p=> new {
p.Title,
Topics = p.Topics.OrderByDescending(p=> p.LastPostedDate).Select(t=> new {
t.TopicId,
t.FirstPost.PostId
})
}).ToList();
When I run this query, t.FirstPost is null even though the topic does have posts in the database. Is there a way to do this using navigational properties instead of using query syntax and joins?
I think you need to update code from this.Post to this.Posts like this
public Post FirstPost {
get {
return this.Posts.OrderBy(p=> p.PostedDate).FirstOrDefault();
}
}
In general avoid using not mapped properties in LINQ to Entities query. They cannot be translated to SQL, and even that EF Core supports client side evaluation, accessing the navigation properties is problematic since they are not loaded yet at the time evaluation occurs.
You can use navigations properties inside LINQ to Entities query (which is actually preferable over explicit joins), but with explicit expression, i.e. not hidden behind unmapped property:
var query = Database.Forums.Where(f => f.Id == id).Select(f => new
{
f.Title,
Topics = f.Topics.OrderByDescending(t => t.LastPostedDate).Select(t => new
{
t.TopicId,
FirstPostId = Posts.OrderBy(p => p.PostedDate).Select(p => (int?)p.PostId).FirstOrDefault(),
})
}).ToList();
(not sure what LastPostedDate is - it's not shown in the posted model, and hope is not another unmapped property. But you get the idea).
Related
Say I have two models/tables.
public class ParentEntity
{
public Guid ID { get; set;}
public List<ChildEntity> ChildEntities { get; set; } // navigation property
}
public class ChildEntity
{
public Guid ID { get; set; }
public Guid ParentEntityID { get; set; } // foreign key
}
If I run a query like this:
var parentEntities = await _context.ParentEntities.Where(x => x.ChildEntities.Any()).ToListAsync();
From what I can tell (based mostly on experimentation), this query does not require explict hydration of ChildEntities (using .Include(x => x.ChildEntities)).
If I wanted to do something with the ChildEntities outside of the query/after the list is materialized, I would need to explicitly hydrate ChildEntities:
var parentEntities = await _context.ParentEntities.Include(x => x.ChildEntities)
.Where(x => x.ChildEntities.Any()).ToListAsync();
foreach (var parentEntity in parentEntities)
{
foreach (var childEntity in parentEntities)
{
// do something with childEntity
}
}
That's my understanding, anyway, and that's how it seems to work. However, I'm hoping to find some Microsoft documentation that mentions this explicitly. I haven't been able to find anything (all the search keywords I can think to use point me in the direction of Filtered Includes, which is NOT what I'm wondering about).
I want to be confident that my understanding is correct, and that I haven't just gotten "lucky" by the child entities already being hydrated from other queries in the same context.
Include needed ONLY for loading related entities and ONLY for such purpose. It has no affect on filter or projection. You can omit Include if you do not plan to load related entities.
In my database I have two entities: DbStatus and DbTask
public class DbStatus
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<DbTask> Tasks { get; set; }
}
public class DbTask
{
public int Id { get; set; }
public string Title { get; set; }
public bool Done { get; set; }
public int StatusId { get; set; }
public virtual DbStatus Status { get; set; }
}
In the OnModelCreating method, I establish the relation with the following code:
modelBuilder.Entity<DbStatus>()
.HasMany(s => s.Tasks)
.WithOne(t => t.Status)
.HasForeignKey(t => t.StatusId);
I also add some sample data in this method, setting the StatusId of newly created DbTasks.
Problem is, when I try to access the Status name of the DbTask using
task.Status.Name
I get a NullReferenceException.
Can anyone help me how to set up the relation properly?
IMPORTANT
For anyone reading this, the quickest solution (and the one fulfilling task-specific criterias) for this was provided Rob. However, you should read and implement the solution provided by Steve Py, for the reasons they also describe in their answer!
When getting your list of DbTasks from the database, you need to tell it to include the child Status objects.
Try something like this:
var tasks = dbContext.DbTasks
.Include(t => t.Status)
.ToList();
Setting a FK on an entity does not automatically cause that related entity to be loaded. When working with navigation properties I recommend avoiding declaring FK fields in entities and using shadow properties to avoid issues like this.
To update a status on a DbTask:
public ActionResult MarkTaskComplete(int taskId)
{
var completeStatus = _context.Statuses.Single(x => x.StatusId = Statuses.Complete);
// TODO: Validation that user can update task etc.
var task = _context.Tasks
.Include(x => x.Status)
.Single(x => x.TaskId == taskId);
task.Status = completeStatus;
_context.SaveChanges();
return Json(new { success = true; status = task.Status.Name } );
}
The issue with FK fields is that the behaviour can differ depending on whether you use the navigation property or the FK, and whether the navigation property is eager loaded or not. From the perspective of the Task, there are two sources of truth for the current Status, some code might check task.StatusId while others use task.Status.StatusId. These values could differ depending on one being updated without the other.
While this can mean a trip to the DB to fetch a status, fetching rows by ID is extremely fast, and also provides a validation that your methods are only using legal values.
How can I sort a query from a DbSet and include child items which should also be sorted.
Example:
I have a model for scheduling orders.
public class Order
{
public virtual int Id { get; set; }
public virtual int? SchedulingOrder { get; set; }
public virtual int? WeekId { get; set; }
public virtual Week Week { get; set; }
}
public class Week
{
public virtual int Id { get; set; }
public virtual DateTime StartDate { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
...
public DbSet<Week> Weeks { get; set; }
public DbSet<Order> Orders { get; set; }
Then an action method
public ActionResult ShopSchedule()
{
return View(db.Weeks.OrderBy(w => w.StartDate)
.Include(w => w.Orders.OrderBy(o => o.SchedulingOrder))
.ToList());
}
This doesn't work I think because of the nature of Include. Do I have to create a separate view model and map to it? Or is there some way to get around it right there in the query? There is some kind of syntax where people say new { left = right, etc } within the query?
related questions:
Ordering Entity Framework sub-items for EditorFor
C# Entity Framework 4.1 Lambda Include - only select specific included values
It's worth noting that the other 2 solutions here pull the data via SQL, then reorder things in memory, which is very wasteful in terms of performance during both the query and the post-processing. This solution gets things in one go via SQL alone, without the extra in-memory step.
It can be done as described in the second approach here:
How to order child collections of entities in EF
Like:
db.VendorProducts.Select(p =>
new { Product = p, S = p.Schedules.OrderBy(s => s.From) })
.FirstOrDefault(q => q.Product.Id == id).Product
So instead of an Include statement, you call up the related data in an anonymous object along with the original root data you were going to fetch, order it in that subquery and finally return the root data. The ordering remains intact. Twisted but it works.
To stick with your original code:
db.Weeks.Select(w => new { W = w, O = w.Orders.OrderBy(o => o.SchedulingOrder) })
.OrderBy(q => q.W.StartDate).Select(q => q.W);
You are right, you can't use orders in Include, it's not meant to work that way. But you could sort the results within the view using the OrderBy on the Orders collection. Also, you're returning a result directly, shouldn't it be return View(db.Weeks...);
Something like this should work :
public ActionResult ShopSchedule()
{
var vw = db.Weeks.OrderBy(w => w.StartDate)
.Include(w => w.Orders)
.ToList();
vw.Orders = vw.Orders.OrderBy(o => o.SchedulingOrder).ToList()
return view(vw);
}
I am having trouble setting up a Fluent NHibernate HasMany collection.
I have set the code up as below and I'm calling it via Linq IQueryable.
In SQL Profiler, I can see the correct SQL getting called, but the Store.Staff collection is always empty.
public class Store
{
public virtual IList<Employee> Staff { get; set; }
public virtual void AddEmployee(Employee Employee)
{
Employee.Store = this;
if(Staff == null)
Staff = new List<Employee>();
Staff.Add(Employee);
}
public class StoreMap : ClassMap<Store>
{
public StoreMap()
{
Id(x => x.StoreId)
.GeneratedBy.Identity();
HasMany(x => x.Staff)
.Inverse()
.Cascade.All();
...
}
}
public bool Create(Store entity)
{
var stores = _readRepository.Query<Store>()
.Where(x => x.StoreId == entity.StoreId)
.Fetch(x => x.Staff)
.ToList();
select store0_.StoreId,
staff2_.SurgeryId,
staff2_.StoreId
from dbo.[Store] store0_
left outer join dbo.[Employee] staff2_
on store0_.StoreId = staff2_.StoreId
where store0_.StoreId = 1 /* #p0 */
Thanks for any help.
Not sure this is the problem, but you should be using FetchMany to eagerly load collections, not Fetch.
I was confusing two parts of the framework. I was using Linq to retrieve the data and couldn't eagerly load.
Instead of using Linq, I now use NHibernate.Session.QueryOver.
I want to update a "Post" and change relationships with "Categories" that already created before. Post entity has ICollection of Categories. But categories are not changed. It seems, that EF does not track entity relations. By the way I have no problem with creating of new Posts with assigning of Categories.
There are two models:
public class Post
{
public virtual int PostId { get; set; }
...
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public virtual int CategoryId { get; set; }
...
public virtual ICollection<Post> Posts { get; set; }
}
The Add controller, that works as expected:
public ActionResult Create(Post model)
{
var c = Request.Form["CategoryID"].Split(',');
model.Categories = c.Select ... .ToList(); //here I assign relationships with attached objects
_repository.Add(model);
_repository.SaveChanges();
...
}
Repository Add method:
T IRepository.Add<T>(T entity)
{
return Set<T>().Add(entity);
}
The Edit controller does not save changed categories, only post props.
public ActionResult Edit(Post model)
{
var c = Request.Form["CategoryID"].Split(',');
model.Categories = c.Select ... .ToList(); //here I update relationships with attached objects
_repository.Attach(model);
_repository.SaveChanges();
...
}
Repository Edit method:
T IRepository.Attach<T>(T entity)
{
var entry = Entry(entity);
entry.State = System.Data.EntityState.Modified;
return entity;
}
Am I doing something wrong?
Thanks in advance
Solution:
public ActionResult Edit(Post model)
{
model = _repository.Attach(model);
var post = _repository.Posts.Include(p => p.Categories).Single(s => s.PostId == model.PostId);
post.Categories.Clear();
model.Categories = GetCategories();
_repository.SaveChanges();
}
First Attach the object (EntityState.Modified)
Query the object with Include or other method for Loading Related Entities
Clear the existent relations. I don like this, but I cannot find another way
Assign new relations from the view and SaveChanges.
Entity Framework won't track relationship changes this way. It only tracks the states of objects, so the proper way would be to load the "Post" that you want with all categories and then to modify the loaded collection - this way changes are going to be reflected in all objects' states.
Manipulate category collection i.e. (add, remove, edit) in post class. If you are using same DbContext then changes will be tracked. It should be worked.
add a category
_post.Categories.Add(category1);
delete category
_post.Categories.Remove(category1);
edit category
_post.Categories[0].Name = "TEST Name";
udapte a object
string ImgID = CompCert.CertID.ToString() + "ComID" + CompId + ext;
CompCert.CertImageFile = ImgID;
db.ObjectStateManager.ChangeObjectState(CompCert, EntityState.Modified);
db.SaveChanges();