RavenDb static Index: query on child collection objects - indexing

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.

Related

Asp.Net core ODATA compute property after filter

Is there any way to create a computed property on an ODATA HTTP request, after the ODATA filter has been processed?
I have the following model
class Person
{
public string Name { get; set; }
public string Email { get; set; }
public string ImageLink { get; set; } //This is computed
}
And the following IEdmModel
private static IEdmModel GetEdmModel(IApplicationBuilder app)
{
var builder = new ODataConventionModelBuilder(app.ApplicationServices).EnableLowerCamelCase();
var person = builder.EntitySet<Person>("People").EntityType;
person.Property(x => x.Name);
person.Property(x => x.Email);
person.Property(x => x.ImageLink).IsOptional();
return builder.GetEdmModel();
}
The image link is generated dynamically and is a fairly costly process, as the link is signed. Is there a way to add only add this property when it is explicitly included by a $select and to only calculate after the filter is applied?
Setting the Select(SelectExpandType.Allowed) property does not seem to exclude it from the model.
I've managed to manually exclude the property by checking the query string in the controller. But the property is still included in the model, the value is null, and obviously this runs before any filter has applied.
var people = await _personService.FetchAllPeopleAsync(cancellationToken);
if (Request.Query.TryGetValue("$select", out var values) &&
values.Any(v => v.Contains(nameof(Person.ImageLink))))
{
return _photoService.AttachImageLinks(Url, people);
}
return people;

How can I successfully map a many to many table in nhibernate with a composedID and still save to it directly?

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.

Nhibernate query for items that have a Dictionary Property containing value

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

How to write this linq query with Criteria or QueryOver API

Is it possible to convert this code at below, written by using Query(linq) api to Criteria or QueryOver API in NHibernate? I'm using this to format data into DTO's also it works with just one round-trip to db.
Note: I tried transformers.aliastobean but I can only use one transformer at a time. Is it possible to use multiple transformer in one query?
from entityType in Provider.GetSession().Query<crmEntityType>()
.Fetch(x => x.Association)
.Fetch(x => x.Fields)
.AsEnumerable()
where instanceIDs.Contains(entityType.Instance.instanceID)
select new EntityTypeDTO()
{
ID = entityType.ID,
Title = entityType.Title,
Association = entityType.Association.Distinct().Select(asc => asc.ID).ToArray<int>(),
Fields = entityType.Fields.Distinct().Select(fi => new CustomFieldDTO {
ID = fi.ID,
Name = fi.Name,
Value = fi.Value,
EntityType = fi.EntityType.ID,
Type = fi.Type
}).ToList()
}).ToList();
Let's start with the QueryOver syntax:
// external filter data
instanceIDs = new int[] { 1, 2, 3 };
// aliasing
EntityTypeDTO entityDTO = null;
CustomFieldDTO fieldDTO = null;
Field field = null;
IQueryOver<EntityType, Field> query = Session.QueryOver<EntityType>()
// filter Entity by ID's list
.Where(Restrictions.On<EntityType>(c => c.ID).IsIn(instanceIDs))
// Join Fields
.JoinQueryOver<Field>(c => c.Fields, () => field)
.SelectList(list => list
// entity
.Select(c => c.ID)
.Select(c => c.Title)
// ... more Entity properties
// field collection
.Select(() => field.ID)
.Select(() => field.Name)
// ... more Field properties
)
.TransformUsing(new MyTransformer()); // see below
var dtos = query.List<EntityTypeDTO>();
This QueryOver will generate the SQL statement which will contain all EntityTypes with their Fields. Now we have to extract the unique EntityType instances and fill their Fields lists
There is an overview of DTO classes (as well as QueryOver above, these contain only ver few properties as an example):
public class EntityTypeDTO
{
public virtual int ID { get; set; }
public virtual string Title { get; set; }
public virtual IList<CustomFieldDTO> Fields { get; set; }
...
}
public class CustomFieldDTO
{
public virtual int ID { get; set; }
public virtual string Name { get; set; }
...
}
And finally the trick MyTransformer():
public class MyTransformer : IResultTransformer
{
// rows iterator
public object TransformTuple(object[] tuple, string[] aliases)
{
var entity = new EntityTypeDTO
{
ID = (int)tuple[0], // aliases should be used
Title = tuple[1] as string // first two are belong to Entity
};
var field = new CustomFieldDTO
{
ID = (int)tuple[2], // last 2 columns are for a Field
Name = tuple[3] as string // see SelectList in QueryOver
};
entity.Fields = new List<CustomFieldDTO> { field };
return entity;
}
// convert to DISTINCT list with populated Fields
public System.Collections.IList TransformList(System.Collections.IList collection)
{
var results = new List<EntityTypeDTO>();
foreach(var item in collection)
{
var entity = item as EntityTypeDTO;
// was already the same ID appended
var existing = results.SingleOrDefault(c => c.ID.Equals(entity.ID));
if(existing != null)
{
// extend fields
existing.Fields.Add(entity.Fields.First());
continue;
}
// new ID found
results.Add(entity);
}
// DISTINCT list of Entities, with populated FIELDS
return results;
}
...
MyTransformer is ad hoc one, only for this purpose... but this approach could be extended

more than one column from select

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.