I'm working on a three-tier application (presentation, business, persistence) and am having conceptual trouble on what the best approach for interacting with nHibernate via MVC. Specifically, I have a page that lets you create a user and select their roles. The roles drop-down list is populated via a List objects obtained from a business handler. The list saves the selected ids to an int[], a la:
#Html.ListBoxFor(
x => x.SelectedRoles,
new SelectList(Model.Roles, "Id", "Name"))
using these base classes:
public class Role
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<User> Users { get; set; }
}
public class User {
public virtual int Id { get; set; }
public virtual string Username { get; set; }
public virtual IList<Role> Roles { get; set; }
}
public RoleMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasManyToMany(x => x.Users)
.Table("UserRole")
.ParentKeyColumn("RoleId")
.ChildKeyColumn("UserId")
.Inverse();
}
public UserMap()
{
Id(x => x.Id);
Map(x => x.Username);
HasManyToMany(x => x.Roles)
.Table("UserRole")
.ParentKeyColumn("UserId")
.ChildKeyColumn("RoleId")
.Cascade.SaveUpdate();
}
I then use AutoMapper to bind my ViewModel to my Entity (i.e. UserCreateViewModel -> User), then pass the User object to its business handler via a Create(User) call. This workflow breaks down for the above example, since when I pass a User object, its List is empty (since AutoMapper doesn't bind an int[] to a List, logically).
This is where my question arises:
Is it better to just pass in the int[] to my Create method, i.e. Create(User, int[]), then do something like User.Roles = ids.Select(x => _roleRepository.GetById(x)).ToList();, or should I really be using some other approach...is this where DTOs come into play? I see how they would make refactoring easier in the future, since I would have something like Create(UserDto), which would never require method-renaming (since I could just add more fields to the DTO as I needed them).
Coming from mini-ORMs built on stored procedures, I feel like this approach is wrong. Regardless of using a DTO, won't I be generating a new query for every role I request via GetById? Or is this something that nHibernate will automatically cache?
Is this even the right approach? In the past I would just pass the int[] of role ids directly into SQL and create the records from there - one database call to create the user and all of their roles. Having a hard time wrapping my head around an ORM-way of doing that efficiently.
Since you are already using AutoMapper, why don't you create a map for Roles property?
.ForMember(x => x.Roles, o => o.MapFrom(
x => x.Roles.Select(id => _roleRepository.GetById(id)).ToList())
That could be encapsulated in an AutoMapper Profile that's using DI:
public class UserProfile : Profile
{
private readonly IRoleRepository _roleRepository;
public UserProfile(IRoleRepository roleRepository)
{
_roleRepository = roleRepository;
}
protected override void Configure()
{
CreateMap<UserCreateViewModel, User>()
.ForMember(x => x.Roles, o => o.MapFrom(
x => x.Roles.Select(id => _roleRepository.GetById(id)).ToList());
}
}
Related
We are building an application using .NET 6 and EF Core 6 with an existing SQL Server database. We are using the database first approach and running the Scaffold-DbContext tool we were able to generate the dbcontex class. Everything works fine, a part for a parent child relation between two tables:
The scaffold tool, for the above tables generated the following two classes:
public partial class TreeNode
{
public TreeNode()
{
TreeNodeHierarchyChildren = new HashSet<TreeNodeHierarchy>();
TreeNodeHierarchyParents = new HashSet<TreeNodeHierarchy>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public bool IsLeaf { get; set; }
public int? OrganisationId { get; set; }
public bool IsDeleted { get; set; }
public virtual ICollection<TreeNodeHierarchy> TreeNodeHierarchyChildren { get; set; }
public virtual ICollection<TreeNodeHierarchy> TreeNodeHierarchyParents { get; set; }
}
public partial class TreeNodeHierarchy
{
public int Id { get; set; }
public int ParentId { get; set; }
public int ChildId { get; set; }
public virtual TreeNode Child { get; set; }
public virtual TreeNode Parent { get; set; }
}
And in the dbcontext class the following mapping:
modelBuilder.Entity<TreeNode>(entity =>
{
entity.ToTable("TreeNode");
entity.Property(e => e.Code).HasMaxLength(100);
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(255);
});
modelBuilder.Entity<TreeNodeHierarchy>(entity =>
{
entity.ToTable("TreeNodeHierarchy");
entity.HasOne(d => d.Child)
.WithMany(p => p.TreeNodeHierarchyChildren)
.HasForeignKey(d => d.ChildId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_TreeNodeHierarchy_TreeNode_Child");
entity.HasOne(d => d.Parent)
.WithMany(p => p.TreeNodeHierarchyParents)
.HasForeignKey(d => d.ParentId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_TreeNodeHierarchy_TreeNode_Parent");
});
Here is the issue, when I write the following:
var nodes = _context.TreeNodes.Include(th => th.TreeNodeHierarchyChildren)
.Where(tn => tn.IsLeaf)
.....
it loads the child but not the parent.
This relation works properly in the current application (.net 4.7) using LINQ to SQL.
Am I missing something?
Updated
as suggested from #SpruceMoose, I included also the TreeNodeHierarchyParents property in the query but it didn't fix the issue.
var nodes = _context.TreeNodes
.Include(th => th.TreeNodeHierarchyChildren)
.Include(th => th.TreeNodeHierarchyParents)
.Where(tn => tn.IsLeaf)
Updated #2
I applied the mapping suggested from #Dave which in my opinion it makes sense (at the end the relation is like the Windows folders/files system).
Anyway there is still something that's not working properly. When I debug the following code:
var nodes = _context.TreeNodes
.Include(th => th.TreeNodeHierarchyChildren)
.Include(th => th.TreeNodeHierarchyParents)
.Where(tn => tn.IsLeaf)
.ToList();
I still see that the parent has not been loaded
Updated #3
I applied the change to the query as suggested from #Moho
var nodes = _context.TreeNodes
.Include(th => th.TreeNodeHierarchyChildren)
.ThenInclude(tnhc => tnhc.Child)
.Include(th => th.TreeNodeHierarchyParents)
.ThenInclude(tnhp => tnhp.Parent)
.Where(tn => tn.IsLeaf)
.ToList();
and finally we got the Parent value
Now we are missing the last step, the parents of a parent
You need to explicitly (eagerly) load the Parent elements by using an Include() on the TreeNodeHierarchyParents navigation property (as you are currently for the TreeNodeHierarchyChildren navigation property).
Change your linq query to the following:
var nodes = _context.TreeNodes
.Include(th => th.TreeNodeHierarchyChildren)
.Include(th => th.TreeNodeHierarchyParents)
.Where(tn => tn.IsLeaf)
.....
I think your relationship mapping is wrong. You say one child has many children and one parent has many parents. It should be one child has many parents, and one parent has many children.
I think it's also a good idea to define these kinds of relationships on both sides, so that if you get something wrong it shows up as an error faster. Note also that I think some of these statements would already be the default.
Also, important, note that I think you need to use Nullable Reference Types to indicate nullability. Anything that is supposed to be nullable should have a ? on its type name in the entity types. Though I think it's possible you should cascade delete, not set null. It depends how your model works.
Something like this, though I can't guarantee compilation:
modelBuilder.Entity<TreeNode>(tnb => {
tnb.ToTable("TreeNode");
tnb.Property(tn => tn.Code).HasMaxLength(100);
tnb.Property(tn => tn.Name).IsRequired().HasMaxLength(255);
tnb
.HasMany(tn => tn.TreeNodeHierarchyParents)
.WithOne(tnh => tnh.Child);
tnb
.HasMany(tn => tn.TreeNodeHierarchyChildren)
.WithOne(tnh => tnh.Parent);
});
modelBuilder.Entity<TreeNodeHierarchy>(tnhb => {
tnhb.ToTable("TreeNodeHierarchy");
tnhb
.HasOne(tnh => tnh.Child)
.WithMany(tn => tn.TreeNodeHierarchyParents)
.HasForeignKey(tnh => tnh.ChildId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_TreeNodeHierarchy_TreeNode_Child");
tnhb
.HasOne(tnh => tnh.Parent)
.WithMany(tn => tn.TreeNodeHierarchyChildren)
.HasForeignKey(tnh => tnh.ParentId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_TreeNodeHierarchy_TreeNode_Parent");
});
One thing you can do to try to make sure your model definition is correct is to create an empty second database with it and compare its model against the real one, and then keep fine tuning it until you get it right.
Regarding Update #2:
You’re eager loading the relationship entities of type TreeNodeHierarchy but you are not eager loading the TreeNode entities they reference. You need to add .ThenInclude calls to do so.
var nodes = _context.TreeNodes
.Include(th => th.TreeNodeHierarchyChildren)
.ThenInclude(tnhc => tnhc.Child)
.Include(th => th.TreeNodeHierarchyParents)
.ThenInclude(tnhp => tnhp.Parent)
.Where(tn => tn.IsLeaf)
The Child property in your example is populated in your current query because the TreeNode entity is loaded by your base query and EF Core will automatically hook it up to relevant navigation properties in other tracked entities. Thus, any “parent” that is not IsLeaf (nor any “child” that is not IsLeaf) is not loaded without the additional ThenInclude.
Another (not recommended) alternative is to enable lazy loading.
here is my order and orderItem classe:
public class Order : AggregateRootBase<OrderId>
{
public string CustomerName { get; private set; }
public IList<OrderItem> Items { get; set; }
public DateTime RegisterDatetime { get; private set; }
}
public class OrderItem : ValueObjectBase
{
public long Id { get; private set; }
public long OrderId { get; set; }
public long Number { get; private set; }
public long Goods { get; private set; }
public double UnitPrice { get; private set; }
}
Im using nhibernate as my orm. in mapping this code i want order to be in Orders table & orderItem to be stored in a diffrente table called OrderItems.
here is my mapping:
public class OrderMapping : ClassMapping<Order>
{
public OrderMapping()
{
Table("Orders");
Lazy(false);
ComponentAsId(a => a.EntityId, a => { a.Property(x => x.DbId, x => x.Column("Id")); });
Property(a=>a.CustomerName);
Property(a => a.RegisterDatetime);
Bag(a => a.Items,
mapper => {
mapper.Inverse(true);
mapper.Cascade(Cascade.None);
mapper.Table("OrderItems");
mapper.Key(k => k.Column(columnMapper => columnMapper.Name("OrderId")));
},
relation => { relation.OneToMany(); });
}
}
public class OrderItemMapping : ClassMapping<OrderItem>
{
public OrderItemMapping()
{
Lazy(false);
Id(a => a.Id);
Table("OrderItems");
Property(a => a.OrderId);
Property(a => a.Number);
Property(a => a.Goods);
Property(a => a.UnitPrice);
}
}
also i have created tables in database but when i insert order with 3 orderItems, it inserts order but not orderitems
thanks for your help
You have mapped the parent Order as mapper.Inverse(true); for the Items bag which tells NHibernate that you do not want the parent to map this relation.
Since the child OrderItem has no mapping to the parent, then nothing is saving the relation. When you write a collection mapping, at least one side must be inverse(false).
You have also set mapper.Cascade(Cascade.None);, which tells NHibernate that you do not want the parent Order to deal with any operations on the Items when they change in Session.
So unless you are explicitly calling Save() on each Item, then they will never be saved as it stands.
There is a trade off in NHibernate between free class layout, and optimum database performance (although very minor in this case).
If you really don't want the OrderItem to have an Order property linking back to it's parent, then you will get an additional UPDATE call to SQL whenever the parent Order of an OrderItem changes, this cost is in practice negligible if creation of OrderItems is less than ~10% of operations that you do with them.
In that case you can set inverse(false) on the OrderMapping to Items.
But my adivce would be to give OrderItem an Order field or property (you can map a private field using NHibernate!) and then give the OrderItemMapping a map back to the parent with inverse(false), so that when children are saved, they will deal with the relation. You will have to make sure that each OrderItem has it's Order field/property filled in before save though!
You might be able to wing this by using the OrderId property instead of a full reference to Order, but you'd have to look that up.
As for making them save to the DB, the easiest way is to change mapper.Cascade(Cascade.None); to mapper.Cascade(Cascade.AllDeleteOrphan); (may not be exact class name). This will make sure that whenever you modify that collection on the Order, then Save()/Update() the Order, all OrderItems in the Items collection will be updated in the DB accordingly.
You could also check out less strict Cascades or save them manually as your current setup would require.
Lastly check out bag vs set in the Nhibernate docs, I suspect you want a set here, I would only use bag with inverse(true) ever. If you use an inverse(false) bag there is a performance penalty, unless the items can be identified (your OrderItem has an Id, so it can!), but if the items can be identified, then why not just make it a set!
The only reason you'd have an inverse(false) bag with identifiable items, is if those identifiers didn't correlate to a Primary Key in the database, that's not a situation that comes up very often (or ever in my case!).
thank you #starlight54 but my problem solved like this.
i had to change my classes a little like this:
public class Order : AggregateRootBase<OrderId>
{
private readonly IList<OrderItem> _items;
public string CustomerName { get; private set; }
public DateTime RegisterDatetime { get; private set; }
public IReadOnlyCollection<OrderItem> Items => new ReadOnlyCollection<OrderItem>(_items);
}
public class OrderItem : ValueObjectBase
{
private readonly long _number;
private readonly long _goods;
private readonly double _unitPrice;
public long Number => _number;
public long Goods => _goods;
public double UnitPrice => _unitPrice;
}
notice that in order item you have to create fields like _number, _goods & ...
and this is the mapping of order:
public class OrderMapping : ClassMapping<Order>
{
public OrderMapping()
{
Table("Orders");
Lazy(false);
ComponentAsId(a => a.EntityId, a => { a.Property(x => x.DbId, x => x.Column("Id")); });
Property(a => a.CustomerName);
Property(a => a.RegisterDatetime);
IdBag(a => a.Items, map => {
map.Access(Accessor.Field);
map.Table("OrderItems");
map.Key(a => a.Column("OrderId"));
map.Id(a => {
a.Column("Id");
a.Generator(Generators.Identity);
});
}, relation => relation.Component(map => {
map.Access(Accessor.Field);
map.Property(a => a.Number, a => a.Access(Accessor.Field));
map.Property(a => a.Goods, a => a.Access(Accessor.Field));
map.Property(a => a.UnitPrice, a => a.Access(Accessor.Field));
}));
}
}
in nhibernate there is IdBag that helped me do exactly what i needed. notice than there is no need to create class mapping for orderItem class. nhibernate inserts it automatic.
but you have to create databases manually.
i hope this helps you.
I am writing a small bookmarking tool in ASP.NET MVC, and each bookmark is stored in a folder. I am currently representing this by simply storing a path as a string in the database, but when it comes to beginning to display items by folder and list folders this seems like an efficient way to do it.
Is there any built-in way to represent and then manipulate paths in a database? Or an accepted best practice?
If you are talking paths like in filesystem paths, then you might want to start thinking about about a trees and a hierachical system.
In essence, you will have an additional table in a form named like Category or something, which is self referencing with a ParentId. With this you can iterate along your caegories until, there is no parent or child item, depending on the direction of your query and build a path with the result of your query.
This of course can get ugly, if your bookmarkings have more then 1 category. You will want to get some additional validation into this.
The following code is acutally from an article on the web, which I used myself, but I can't find the article again, to quote it as the source.
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public virtual Category Parent { get; set; }
public virtual ICollection<Category> Children { get; set; }
}
public class CategoryMapping : EntityTypeConfiguration<Category>
{
public CategoryMapping()
{
HasKey(x => x.CategoryId);
Property(x => x.CategoryId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.Name)
.IsRequired()
.HasMaxLength(255);
//Parent is optional, because root node has no parent
HasOptional(x => x.Parent)
.WithMany(x => x.Children)
.HasForeignKey(x => x.ParentId)
.WillCascadeOnDelete(false);
}
}
References:
http://sqllessons.com/categories.html
http://www.sitepoint.com/hierarchical-data-database/
Entity Framework Code First Self Referencing Parent Child with Payload
I am not sure if fluent n hibernate can do this or not, but I cannot figure out how.
I have a table - cases and some properties
ownerId, brokerId, shipperId
I want to map this to my property:
int[] OrgsWithAccess
Is this possible?
This way when I am checking if an org has access to the case, I can check the property OrgsWithAccess rather than OwnerId == myorg.id or brokerId == myorg.id etc.
If I understand your question correctly, I wouldn't recommend trying to map in the way that you have asked.
cases table looks like it is some form of junction table between other tables. I'll assume that these other tables each contain data that are represented as entities in the application, and that there are three tables, Owner, Broker and Shipper.
OrgsWithAccess should be mapped using the references to the entities that is has in the application i.e. assume the class looks something like
public class OrgsWithAccess
{
public virtual Owner { get; set; }
public virtual Broker { get; set; }
public virtual Shipper { get; set; }
}
Then the mapping will look like
public class OrgsWithAccessMap : ClassMap<OrgsWithAccess>
{
public OrgsWithAccessMap()
{
References(x => x.Owner);
References(x => x.Broker);
References(x => x.Shipper);
}
}
Then when querying, you would simply look at the properties on OrgsWithAccess
session.QueryOver<OrgsWithAccess>().Where(x => x.Owner.Id == id);
Say I have the following class with an aggregation of an external class:
public class MyMovie
{
public virtual string id{get;set;}
public virtual Movie movie{get;set;}
}
//These classes are externally defined and cannot be changed.
public class Movie
{
public string title{get;set;}
public IList<Director> Directors{get;set;}
}
public class Director
{
public string name{get;set;}
public IList<Movie> DirectedMovies{get;set;}
}
The db schema for this would be three tables:
Movie(m_id, title)
Director(d_id, name)
Directs(m_id, d_id)
Is it possible to map this with fluent nhibernate? I just don't understand how this would be done with the many to many relation being in the external classes where I cannot map create a map class for Director as this does not define members as virtual.
Map your class MyMovie as usual, and use disable lazyloading of Movie and Director. Aftter all lazy-loading for many-to-many part should work as usualy, cause for collection laziness proxy is not need.
public class MyMovieMap : ClassMap<MyMovie>
{
public MyMovieMap()
{
Id(x => x.id);
References(x => x.movie);
}
}
public class MovieMap : ClassMap<Movie>
{
public MovieMap()
{
Not.LazyLoad();
Id<int>("m_id");
Map(x => x.title);
HasManyToMany(x => x.Directors)
.Table("Directs")
.LazyLoad();
}
}
public class DirectorMap : ClassMap<Director>
{
public DirectorMap()
{
Not.LazyLoad();
Id<int>("d_id");
Map(x => x.name);
HasManyToMany(x => x.DirectedMovies)
.Table("Directs")
.LazyLoad();
}
}
Basically, your issue here is that you are trying to tell nhibernate to load objects, but it doesn't know anything about the objects. For instance, you are telling it MyMovie contains a Movie, yet it doesn't know what field Movie.title belongs to, and it doesn't know how to join in Director's with the movies because it is unmapped. So basically in order to pull this off without a mapping file, you need to use Criteria and result transformers to accomplish this (basically issuing a sql query and converting the results to objects via an on the fly mapping), you could encapsulate this logic in a function so it can be called in your code without being too messy, but other than that I can't see any other way around it. Check out this post, the code is not exactly what you are trying to do (because you will have to join in directors), but it is using the same tools you will have to use... http://ayende.com/blog/2741/partial-object-queries-with-nhibernate