Fluent NHibernate HasMany collection not populating - asp.net

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.

Related

Does using a navigation collection property in a .Where predicate require explicit hydration?

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.

Entity Framework with ASP.NET one-to-many relation causes NullReference error

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.

EF Core: Filter query by complex property (which uses custom value converter)

guys.
I use EF Core 3.1 now as my database provider. So let's imagine a simple model of user with different working roles (for example, "worker", "builder", "supported", etc.)
public enum Roles
{
Worker = 1,
Builder = 2,
Supporter = 3
}
public class User
{
public Guid Id { get; set; }
public ICollection<Roles> EmploymentRoles { get; set; }
...
}
with DbContext of
...
DbSet<User> Users { get; set; }
...
modelBuilder.Entity<User>().Property(f => f.EmploymentRoles).HasConversion(new JsonEnumCollectionToStringConverter<Roles>());
public class JsonEnumCollectionToStringConverter<T> : ValueConverter<ICollection<T>, string> where T : Enum
{
public JsonEnumCollectionToStringConverter() :base(entity => JsonConvert.SerializeObject(entity), value => JsonConvert.DeserializeObject<ICollection<T>>(value))
{
}
}
So, It's not possible to write filter query directly like this:
var users = dbContext.Users.Where(e => e.EmploymentRoles.Contains(Roles.Worker)).ToList();
because EF Core can't translate Custom ValueConverter to SQL and execute this on the server-side. There are no warnings still now in 3.1.x, but this code will catch an error at the 5.x .NET.
So, what's the good way to write this filter query correct? I don't want to grab whole Users and filter it on the client-side, it can be worse in performance.
Thank you.

Navigation Property returns null?

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).

ApplicationUser has a list of ApplicationUser

I have built a new Web Application that uses the template Visual Studio provides and included MVC and Web API. The default authorization mechanism is Identity and the database interaction is done using Entity Framework with Code-first method of creating the database.
I have three requirements:
A user can have a list of Children objects
I do not want to use a "relationship" object
All users already exist on the AspNetUsers table, because they all need to be able to login, so I do not want another table to maintain user data
In theory, multiple parents could have reference to multiple children, but for this example, we will just consider it a one-to-many relationship.
In my application, I need to have an ApplicationUser have a list of ChildUsers as a collection of ApplicationUser such as shown below.
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
public string ShirtSize { get; set; }
public ICollection<ApplicationUser> Children { get; set; }
}
I want these users to be accessible as shown above (a collection of ApplicationUser), not a collection of Relationship object that ties them together such as:
public class Relationship
{
public String ParentId { get;set; }
public String ChildId { get;set; }
}
Can a new table be created and exist on the database without having a code-first model for it to know how to create a relationship table?
What are available solutions to this problem?
After some research, and experimentation, I have found bits and pieces of guidance to arrive at a solution that works.
In order for an intermediate table to be created to maintain the relationship, the ApplicationDbContext OnModelCreating function needs to know what it should look like. I have told it to create a new table that is not bound to an object by using the modelBuilder shown in the code below. Unfortunately, I do not have the links to the articles that guided me to this.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base( "DefaultConnection", throwIfV1Schema: false )
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
base.OnModelCreating( modelBuilder );
modelBuilder.Entity<ApplicationUser>()
.HasMany( p => p.ChildUsers )
.WithMany()
.Map( m =>
{
m.MapLeftKey( "Father_Id" );
m.MapRightKey( "Son_Id" );
m.ToTable( "father_son_relation" );
} );
}
}
Additionally, when you need to add Children to the parent ApplicationUser, you will need to do some tweaking as you are about to insert so that it updates the database correctly. I definitely want the UserManager to do the creation of the user for me, but that means that when I go to add the user to my list of Children with the code below, it tries to add it again and throws an exception because it already exists.
var result = await UserManager.CreateAsync( user, model.Password );
var myUserId = User.Identity.GetUserId();
var users = AppDbContext.Users.Where( u => u.Id == myUserId ).Include( u => u.ChildUsers );
var u2 = users.First();
u2.ChildUsers.Add( user );
await AppDbContext.SaveChangesAsync();
After finding this question, I researched the EntityStates and found that adding the following line before calling SaveChanges resolved the exception and it no longer attempts to add it again.
AppDbContext.Entry( user ).State = EntityState.Unchanged;
TADA!!! Now to select them from the database using EF, you can then use the following code:
AppDbContext.Users.Where( u => u.Id == myUserId ).Include( u => u.Children ).First();
Since I am only getting one level of Children this will work ok, after that you risk circular references.
Comments and ideas to improve the code are welcome.

Resources