How to display children and grandchildren when 'parentItemNo' is given.
Below is my model: Any record (itemNo) can become a parent, child, or grandchild.
public partial class item
{
public string ItemNo { get; set; }
public string ParentItemNo { get; set; }
}
Below query returns children: Would like to display grandchildren as well.
var result = Context.Items.Where(p => p.ParentItemNo == parentItemNo).Select(p => p.ItemNo).ToListAsync(); ;
Navigation properties.
Given an Item class which can have multiple levels where each item can have an optional Parent:
public class Item
{
[Key]
public string ItemNo { get; set; }
[ForeignKey("Parent")]
public string ParentItemNo { get; set; }
public virtual Item Parent { get; set; }
public virtual ICollection<Item> Children { get; set; } = new List<Item>();
}
From here I would recommend setting up the mapping explicitly, either in the DbContext OnModelCreating event or using an EntityTypeConfiguration implementation:
modelBuilder.Entity<Item>()
.HasOptional(x => x.Parent)
.WithMany(x => x.Children);
When you want to get the children and grand-children for an item:
var children = await context.Items
.Include(x => x.Children) // eager load children
.ThenInclude(x => x.Children) // eager load grandchildren
.Where(x => x.ItemNo == parentItemNo)
.SelectMany(x => x.Children)
.ToListAsync();
Normally though you'd load the parent with it's children and grandchildren eager loaded:
var parent = await context.Items
.Include(x => x.Children)
.ThenInclude(x => x.Children)
.SingleAsync(x => x.ItemNo == parentItemNo);
For eager loading you need to plan ahead with what data you expect to need, otherwise you should ensure lazy loading is enabled and available if it's possible that a grandchild could itself have children items. Iterating down to this level would either attempt a lazy load (possibly resulting in errors if the object is orphaned from it's DbContext) or return no data or a partial representation of data if the DbContext was already tracking some of the great grandchildren that it could fill in.
Edit: if you just want to get the Item Numbers of the children and grandchildren then you don't need to use the eager loading with Include as that would be to return entities and their related entities. Projection with Select / SelectMany does not need this.
To select the ItemNos of any Children and Grandchildren:
var childDetails = await context.Items
.Where(x => x.ItemNo == parentItemNo)
.SelectMany(x => x.Children.Select(c => new
{
c.itemNo,
GrandChildItemNos = c.Children.Select(gc => gc.itemNo).ToList()
}).ToListAsync();
What this will give you is a list of anonymous types containing each child's Item #, and the list of that child's grandchild item #s. From there you can combine all of the item #s:
List<string> itemNumbers = childDetails.Select(x => x.itemNo).ToList();
foreach(var child in childDetails)
itemNumbers.AddRange(child.GrandChildItemNos);
This would combine all of the item numbers for the children and their grandchildren into a single list. From there if relationships to a particular child could be doubled up (2 children share the same grandchild) you can add a Distinct() to the end to remove duplicates.
It may be possible to select both child and grand child in the EF Linq operation using something like a Union but if possible I'd expect it to be a fair bit more complex and might be difficult to modify and understand later on.
Related
I have a bunch of GUID constants in my code for certain tag categories that are important in my application. This is mapped in a simple two column many to many table. I often want to avoid fetching the nhibernate object because all I really need is the GUID and it's already hardcoded. I also noticed it's much quicker and easier to do certain queries with direct table access. So, the goal is to to map those nhibernate many to many tables as a class so they can be read and written to without disrupting nhibernates usage of them in the regular sense; while at the same time using GUIDs for identifiers.
Anyway, I have settled on using a composedID across the two columns nhibernate generates. But there is a problem. If I use composed ID and make a Category tag object and try to save my TagID and CategoryTag ID directly, TagID gets saved to the CategoryTagID column and CategoryTagID gets saved to the TagID column!
public class CategoryTagMapping : ClassMapping<CategoryTag>
{
public CategoryTagMapping ()
{
Table("CategoryTag");
/*Id(x => x.ID, map => map.Generator(Generators.Guid));*/
Property(x => x.CategoryTagID, map => { map.Column("CategoryTagID");});
Property(x => x.TagID, map => { map.Column("TagID"); });
ComposedId(p =>
{
p.Property(p1 => p1.CategoryTagID, a => a.Column("TagID"));
p.Property(p1 => p1.TagID, a => a.Column("CategoryTagID"));
});
}
}
public class CategoryTag
{
/*public virtual Guid ID {get;set;}*/
public virtual Guid CategoryTagID { get; set; }
public virtual Guid TagID { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
var t = obj as CategoryTag;
if (t == null)
return false;
if (this.CategoryTagID == t.CategoryTagID && this.TagID == t.TagID)
return true;
return false;
}
public override int GetHashCode()
{
return (this.CategoryTagID + "|" + this.TagID).GetHashCode();
}
}
Trying to do this:
CategoryTag A = new CategoryTag { CategoryTagID = Constants.GUID1, TagID = Constants.GUID2 };
If I add the ID column by uncommenting the two lines, the saving works properly. But then that breaks the regular usage of the table because mysql can't auto increment the guid field and nhibernate won't generate an ID to go in the ID column.
Anyhow, maybe it's a bug, but maybe there's a workaround. Is there something wrong with the mapping, or the equals/gethashcode?
Thanks!
I am stupid. The columns are mismatched in the composedID mapping part.
I need a way to query in Nhibernate for items that have a Dictionary Property containing value.
Assume:
public class Item
{
public virtual IDictionary<int, string> DictionaryProperty {get; set;}
}
and mapping:
public ItemMap()
{
HasMany(x => x.DictionaryProperty)
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore)
.AsMap<string>(
index => index.Column("IDNumber").Type<int>(),
element => element.Column("TextField").Type<string>().Length(666)
)
.Cascade.AllDeleteOrphan()
.Fetch.Join();
}
I want to query all Items that have a dictionary value of "SomeText". The following example in Linq fails:
session.Query<Item>().Where(r => r.DictionaryProperty.Any(g => g.Value == "SomeText"))
with error
cannot dereference scalar collection element: Value
So is there any way to achieve that in NHibernate? Linq is not an exclusive requirement but its preffered. Not that I'm not interested to query over dictionary keys that can be achieved using .ContainsKey . Φορ this is similar but not the same
Handling IDictionary<TValueType, TValueType> would usually bring more issues than advantages. One way, workaround, is to introduce a new object (I will call it MyWrapper) with properties Key and Value (just an example naming).
This way we have to 1) create new object (MyWrapper), 2) adjust the mapping and that's it. No other changes... so the original stuff (mapping, properties) will work, because we would use different (readonly) property for querying
public class MyWrapper
{
public virtual int Key { get; set; }
public virtual string Value { get; set; }
}
The Item now has
public class Item
{
// keep the existing for Insert/Updae
public virtual IDictionary<int, string> DictionaryProperty {get; set;}
// map it
private IList<MyWrapper> _properties = new List<MyWrapper>();
// publish it as readonly
public virtual IEnumerable<MyWrapper> Properties
{
get { return new ReadOnlyCollection<MyWrapper>(_properties); }
}
}
Now we will extend the mapping:
HasMany(x => x.Properties)
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore)
.Component(c =>
{
c.Map(x => x.Key).Column("IDNumber")
c.Map(x => x.Value).Column("TextField")
})
...
;
And the Query, which will work as expected:
session
.Query<Item>()
.Where(r =>
r.Properties.Any(g => g.Value == "SomeText")
)
NOTE: From my experience, this workaround should not be workaround. It is preferred way. NHibernate supports lot of features, but working with Objects brings more profit
I have a document structure like the following:
Employer => Positions => RequiredSkills
Employer has a collection of Position
Positions have a collection of RequiredSkill.
Required Skill consists of a skill (string) and a proficiency (enum).
If I use a dynamic index it seems to return company fine, however I want to use an index to populate MVC view models to return to the UI.
I'm really new to Raven so my apologies for doing anything stupid/unnecessary!
I've got the following mapping:
public class PositionSearch : AbstractIndexCreationTask<Employer>
{
public PositionSearch()
{
Map = employers =>
from employer in employers
from position in employer.Positions
select new
{
EmployerId = employer.Id,
EmployerName = employer.Name,
PositionId = position.Id,
PositionTitle = position.Title,
position.Location,
position.Description,
RequiredSkills = position.RequiredSkills
};
StoreAllFields(FieldStorage.Yes);
Index("RequiredSkills_Skill", FieldIndexing.Analyzed);
}
}
However when I try to execute the following query:
var results = session.Query<PositionSearchResultModel, PositionSearch>()
.Customize(x => x.WaitForNonStaleResults())
.Where(x=>x.RequiredSkills.Any(y=>y.Skill == "SkillName"))
.ProjectFromIndexFieldsInto<PositionSearchResultModel>()
.ToList();
I get the following error:
System.ArgumentException:
The field 'RequiredSkills_Skill' is not indexed,
cannot query on fields that are not indexed
Can anyone see what I'm doing wrong or suggest another approach for me please?
Thanks,
James
UPDATE my view model - Thanks :
public class PositionSearchResultModel
{
public PositionSearchResultModel()
{
RequiredSkills = new HashSet<SkillProficiency>();
}
public string EmployerId { get; set; }
public string EmployerName { get; set; }
public string PositionId { get; set; }
public string PositionTitle { get; set; }
public string Location { get; set; }
public string Description { get; set; }
public ICollection<SkillProficiency> RequiredSkills { get; set; }
}
Because you want to do an analyzed search against the skill name, you need to isolate it as a separate index entry.
public class PositionSearch
: AbstractIndexCreationTask<Employer, PositionSearchResultModel>
{
public PositionSearch()
{
Map = employers =>
from employer in employers
from position in employer.Positions
select new
{
EmployerId = employer.Id,
EmployerName = employer.Name,
PositionId = position.Id,
PositionTitle = position.Title,
position.Location,
position.Description,
position.RequiredSkills,
// Isolate the search property into it's own value
SkillsSearch = position.RequiredSkills.Select(x => x.Skill)
};
// you could store all fields if you wanted, but the search field
// doesn't need to be stored so that would be wasteful.
Store(x => x.PositionId, FieldStorage.Yes);
Store(x => x.PositionTitle, FieldStorage.Yes);
Store(x => x.Location, FieldStorage.Yes);
Store(x => x.Description, FieldStorage.Yes);
Store(x => x.RequiredSkills, FieldStorage.Yes);
// Any field you are going to use .Search() on should be analyzed.
Index(x => x.SkillsSearch, FieldIndexing.Analyzed);
}
}
Note that I specified the projection as the result of the index. This is syntactic sugar. It's not wrong to leave it off, but then you have to specify your search field using a string.
You'll also need to add the search field to your results class
public string[] SkillsSearch { get; set; }
It really doesn't matter what type it is. A string array or collection will do just fine. You could also use just a string or an object, because it's only the name that's relevant.
When you query against this index, use the .Search() method, like this:
var results = session.Query<PositionSearchResultModel, PositionSearch>()
.Customize(x => x.WaitForNonStaleResults()) // only use this in testing
.Search(x=> x.SkillsSearch, "SkillName")
.ProjectFromIndexFieldsInto<PositionSearchResultModel>() // AsProjection would also work
.ToList();
Note that the only reason you have to store so many fields is because you want to project them. If you separated the positions into their own documents, you would have much smaller indexes and much less to project. Keep in mind that when you project, all of the fields in the original document are already there and come directly from the document store, rather than having to be copied into the index. So if your original documents more closely match your desired results, then there's less work to do.
I'm new to Hibernate and I'm struggling with problem:
I have 3 tables and I need to present some data from these tables on DataGridView.
Normally - without hibernate, I have to make select and bind columns.
But I don't need all columns from these tables.
I have made sample project, where I select data from one table. And I store data as collection of hibernate poco object. Then I bind it as datasource to DataGridView and it is fine.
How to bind if I need columns from more than one table?
Should I have one class with columns. Or 3 poco classes? Or I totally wrong and I need to make this on mapping level?
If you just need to display data (and not change it), you could define a class for the objects you want to have listed in the DataGridView, e.g.
CombinedClassForGrid
{
public Id { get; set; }
public PropertyA1 { get; set; }
public PropertyB1 { get; set; }
public PropertyB2 { get; set; }
}
With NHibernate you can query the tables like this, assuming that ClassA has a property ClassBRef:
ClassB bAlias = null;
CombinedClassForGrid cForGrid = null;
IList<CombinedClassForGrid> result = session.QueryOver<ClassA>()
.JoinAlias(a => a.ClassBRef, () => bAlias)
.SelectList(list => list
.Select(a => a.Id).WithAlias(() => cForGrid.Id)
.Select(a => a.Property1).WithAlias(() => cForGrid.PropertyA1)
.Select(a => bAlias.Property1).WithAlias(() => cForGrid.PropertyB1)
.Select(a => bAlias.Property2).WithAlias(() => cForGrid.PropertyB2))
.TransformUsing(Transformers.AliasToBean<CombinedClassForGrid>())
.List<CombinedClassForGrid>();
Then you should be able to bind result to your DataGridView.
I have a parent object with a child collection containing one element, the child collection contains a "grandchild" collection containing 3 elements.
I am loading the parent object from the database using NHibernate as follows
Parent parentObject = session.Query<Parent>()
.FetchMany(x => x.Children)
.ThenFetchMany(x => x.GrandChildren)
.Where(x => x.Id = "someparentid")
.Single();
What I'm finding is that there are duplicate children objects (3 in total) attached to the parent object when there should be only one. (There are 3 grandchild objects correctly attached to each child.) Eager loading the children collection only works correctly.
Do you know how I can achieve loading of the full parent object without duplicate children?
If you map Children and GrandChildren as set, you can avoid the cartesian product. You need to define Children and Grandchildren as collections:
public class Parent
{
...
public virtual ICollection<Child> Children { get; set; }
...
}
public class Child
{
...
public virtual ICollection<GrandChild> GrandChildren { get; set; }
...
}
And in the mapping (using FluentNHibernate):
public class ParentMapping : ClassMap<Parent>
{
public ParentMapping()
{
...
HasMany(x => x.Children)
.KeyColumn("ParentID")
.Inverse
.AsSet()
...
}
}
public class ChildMapping : ClassMap<Child>
{
public ChildMapping()
{
...
HasMany(x => x.GrandChildren)
.KeyColumn("ChildID")
.Inverse
.AsSet()
...
}
}
I was able to use the answer here using QueryOver, it correctly loads the objects while generating efficient SQL (selects per table instead of one huge join).
If you are using Linq, you can simplify it with this:
int parentId = 1;
var p1 = session.Query<Parent>().Where(x => x.ParentId == parentId);
p1
.FetchMany(x => x.Children)
.ToFuture();
sess.Query<Child>()
.Where(x => x.Parent.ParentId == parentId);
.FetchMany(x => x.GrandChildren)
.ToFuture();
Parent p = p1.ToFuture().Single();
Detailed explanation here: http://www.ienablemuch.com/2012/08/solving-nhibernate-thenfetchmany.html
You can't do it with NHibernate (I don't think you can do it with EF4 either) since your result is a Cartesian product. You're getting all results from all tables.
NHibernate doesn't know how to map the results from both collections back to the root. So for each Children, you get the same number of GrandChildren, and for each GrandChildren you end up with the same number of Children.