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
Related
When I save a document that has a generic type DataView<Customer>, I'm manually setting the collection name to "customers". However, I'm having some trouble making an index using AbstractIndexCreationTask with a non-default collection name. Here's my index:
public class customers_Search
: AbstractIndexCreationTask<DataView<Customer>, customers_Search.Result>
{
public class Result
{
public string Query { get; set; }
}
public customers_Search()
{
Map = customers =>
from customer in customers
where customer.Data != null
select new
{
Query = AsDocument(customer.Data).Select(x => x.Value)
};
Index(x => x.Query, FieldIndexing.Analyzed);
}
}
When this gets deployed, it looks like this:
from customer in docs.DataViewOfCustomer
where customer.Data != null
select new {
Query = customer.Data.Select(x => x.Value)
}
This doesn't work obviously, and if I change DataViewOfCustomer to "customers" it works just fine.
I'd rather not have to use non-type-checked (string) indexes to deploy. Is there a way to set the collection name that from the AbstractIndexCreationTask class?
Update
Since my data class is generic, I made a generic index which fixes up the names.
public class DataViewQuery<TEntity>
: AbstractIndexCreationTask<DataView<TEntity>, DataViewQueryResult>
{
private readonly string _entityName;
private readonly string _indexName;
// this is to fix the collection name for the index name
public override string IndexName { get { return _indexName; } }
// this is to fix the collection name for the index query
public override void Execute(IDatabaseCommands databaseCommands, DocumentConvention documentConvention)
{
var conventions = documentConvention.Clone();
conventions.FindTypeTagName =
type =>
typeof(DataView<TEntity>) == type
? _entityName
: documentConvention.FindTypeTagName(type);
base.Execute(databaseCommands, conventions);
}
public DataViewQuery(string entityName)
{
_entityName = entityName;
_indexName = String.Format("{0}/{1}", entityName, "Query");
Map = items =>
from item in items
where item.Data != null
select new
{
Query = AsDocument(item.Data).Select(x => x.Value)
};
Index(x => x.Query, FieldIndexing.Analyzed);
}
}
public class DataViewQueryResult
{
public string Query { get; set; }
}
Then I can create a specific index which has all the configuration in it.
// sets the collection type (DataView<Customer>) for the index
public class CustomerQuery : DataViewQuery<Customer>
{
// sets the collection name for the index
public CustomerQuery() : base(EntityName.Customers) { }
}
You need to configure this in the conventions.
The property to configure is FindTypeTagName
The problem also exists for Update() using a Type wildcard, but I found that DocumentExists() does the same thing, so I've distilled the issue down here:
This works...
var docExists = client.DocumentExists<object>(d => d
.Index(indexname)
.Id(myId)
.Type("Abcdef"));
but this fails...
var docExists = client.DocumentExists<object>(d => d
.Index(indexname)
.Id(myId)
.Type("Abc*"));
It also fails if I omit the Type altogether.
Anyone know how to make this work? (Even if it worked regardless of the type of the document would be fine for my purpose.)
As far as I know it's not possible to specify wildcard in type name, but you can do a trick.
You can query your index for documents with specific id and use prefix filter to narrow down search on certain types.
var searchResponse = client.Search<dynamic>(s => s
.Type(string.Empty)
.Query(q => q.Term("id", 1))
.Filter(f => f.Prefix("_type", "type")));
Here is the full example:
class Program
{
static void Main(string[] args)
{
var indexName = "sampleindex";
var uri = new Uri("http://localhost:9200");
var settings = new ConnectionSettings(uri).SetDefaultIndex(indexName).EnableTrace();
var client = new ElasticClient(settings);
client.DeleteIndex(descriptor => descriptor.Index(indexName));
client.CreateIndex(descriptor => descriptor.Index(indexName));
client.Index(new Type1 {Id = 1, Name = "Name1"}, descriptor => descriptor.Index(indexName));
client.Index(new Type1 {Id = 11, Name = "Name2"}, descriptor => descriptor.Index(indexName));
client.Index(new Type2 {Id = 1, City = "City1"}, descriptor => descriptor.Index(indexName));
client.Index(new Type2 {Id = 11, City = "City2"}, descriptor => descriptor.Index(indexName));
client.Index(new OtherType2 {Id = 1}, descriptor => descriptor.Index(indexName));
client.Refresh();
var searchResponse = client.Search<dynamic>(s => s
.Type(string.Empty)
.Query(q => q.Term("id", 1))
.Filter(f => f.Prefix("_type", "type")));
var objects = searchResponse.Documents.ToList();
}
}
class Type1
{
public int Id { get; set; }
public string Name { get; set; }
}
class Type2
{
public int Id { get; set; }
public string City { get; set; }
}
class OtherType2
{
public int Id { get; set; }
}
Don't know much about your big picture, but maybe you can change your solution to use indexes instead of types? You can put wildcard in the index names. Take a look.
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 trying to specify a unique column for an entity, using the Fluent NHibernate Automapper Override. For my test class of CodeType, I'd like to make the Type property unique. The goal would be for a "new CodeType()" being created with the same type field as a currently saved CodeType to be overlaid on top of the current entity.
I have the following CodeType class:
public class CodeType : SecurableEntity
{
public virtual string Type { get; set; }
public virtual string Description { get; set; }
/// <summary>
/// This is a placeholder constructor for NHibernate.
/// A no-argument constructor must be available for NHibernate to create the object.
/// </summary>
public CodeType() { }
}
I have the following CodeTypeMap Class:
public class CodeTypeMap : IAutoMappingOverride<CodeType>
{
public void Override(AutoMapping<CodeType> mapping)
{
//Doesn't work. Need a way to specify a column as unique.
mapping.Map(m => m.Type).Unique();
}
}
The override is applied to the AutoMap, through the following:
public AutoPersistenceModel Generate()
{
var mappings = AutoMap.AssemblyOf<User>(new AutomappingConfiguration());
mappings.IgnoreBase<Entity>();
mappings.IgnoreBase<SecurableEntity>();
mappings.IgnoreBase(typeof(EntityWithTypedId<>));
mappings.Conventions.Setup(GetConventions());
mappings.UseOverridesFromAssemblyOf<AutoPersistenceModelGenerator>();
mappings.UseOverridesFromAssemblyOf<UserMap>();
mappings.UseOverridesFromAssemblyOf<CodeMap>();
mappings.UseOverridesFromAssemblyOf<CodeTypeMap>();
return mappings;
}
I'd like the following code to update any existing record with "type" equal to "existingType".
SecurableEntityRepository<CodeType> ctr = new SecurableEntityRepository<CodeType>();
CodeType ct = new CodeType();
ct.type = "existingType";
ct = ctr.SaveOrUpdate(ct);
How can I make NHibernate key off of the type field as unique?
Is this possible?
short answer, what you want is something you have to handle in code because there are so many possibilities. Everytime you create a new CodeType you have to check the db if there is already one
SecurableEntityRepository<CodeType> ctr = new SecurableEntityRepository<CodeType>();
CodeType ct = ctr.GetByType("existingType");
if (ct == null)
{
ct = new CodeType { type = "existingType" };
}
ctr.SaveOrUpdate(ct);
or
SecurableEntityRepository<CodeType> ctr = new SecurableEntityRepository<CodeType>();
CodeType ct = ctr.GetByType("existingType");
if (ct != null)
{
ctr.Detach(ct);
ctr.Merge(new CodeType{ type = "existingType" });
}
or
SecurableEntityRepository<CodeType> ctr = new SecurableEntityRepository<CodeType>();
int ctId = ctr.GetIdByType("existingType");
if (ct != 0)
{
ctr.Merge(new CodeType{ Id = ctId, type = "existingType" });
}
and there are some things which can be written differently
public CodeType() { } can be removed or made protected CodeType() { } if not needed for your domain
public AutoPersistenceModel Generate()
{
return AutoMap.AssemblyOf<User>(new AutomappingConfiguration())
.IgnoreBase<Entity>()
.IgnoreBase<SecurableEntity>()
.IgnoreBase(typeof(EntityWithTypedId<>))
.Conventions.Setup(GetConventions())
.UseOverridesFromAssemblyOf<AutoPersistenceModelGenerator>();
}
Does anyone know how can we automatically map dynamic components using Fluent Automapping in NHibernate?
I know that we can map normal classes as components, but couldn't figure out how to map dictionaries as dynamic-components using fluent automapping.
Thanks
We've used the following approach successfully (with FluentNH 1.2.0.712):
public class SomeClass
{
public int Id { get; set; }
public IDictionary Properties { get; set; }
}
public class SomeClassMapping : ClassMap<SomeClass>
{
public SomeClassMapping()
{
Id(x => x.Id);
// Maps the MyEnum members to separate int columns.
DynamicComponent(x => x.Properties,
c =>
{
foreach (var name in Enum.GetNames(typeof(MyEnum)))
c.Map<int>(name);
});
}
}
Here we've mapped all members of some Enum to separate columns where all of them are of type int. Right now I'm working on a scenario where we use different types for the dynamic columns which looks like this instead:
// ExtendedProperties contains custom objects with Name and Type members
foreach (var property in ExtendedProperties)
{
var prop = property;
part.Map(prop.Name).CustomType(prop.Type);
}
This also works very well.
What I'm still about to figure out is how to use References instead of Map for referencing other types that have their own mapping...
UPDATE:
The case with References is unfortunately more complicated, please refer to this Google Groups thread. In short:
// This won't work
foreach (var property in ExtendedProperties)
{
var prop = property;
part.Reference(dict => dict[part.Name]);
}
// This works but is not very dynamic
foreach (var property in ExtendedProperties)
{
var prop = property;
part.Reference<PropertyType>(dict => dict["MyProperty"]);
}
That's all for now.
I got struggle with exactly the same problem. With fluent nHibernate we cannot map this but on my own I somehow was able to solve this. My solution is to build lambda expression on the fly and the assign this into object. For instance, lets say that:
Let my copy part of the site that Oliver refer:
DynamicComponent(
x => x.Properties,
part =>
{
// Works
part.Map("Size").CustomType(typeof(string));
// Works
var keySize = "Size";
part.Map(keySize).CustomType(typeof(string));
// Does not work
part.Map(d => d[keySize]).CustomType(typeof(string));
// Works
part.References<Picture>(d => d["Picture"]);
// Does not work
var key = "Picture";
part.References<Picture>(d => d[key]);
});
And we have this problem that we need to hardcode "Picture" in mapping. But somehow after some research I created following solution:
var someExternalColumnNames = GetFromSomewhereDynamicColumns();
'x' is a DynamicComponent callback in fluent Nhibernate e.g. (DynamicColumns): DynamicComponent(a => a.DynamicColumns, x => (...content of method below...))
foreach(var x in someExternalColumnNames)
{
if (x.IsReferenceToPerson == true)
{
var param = Expression.Parameter(typeof(IDictionary), "paramFirst");
var key = Expression.Constant(x.Name);
var me = MemberExpression.Call(param, typeof(IDictionary).GetMethod("get_Item"), new[] { key });
var r = Expression.Lambda<Func<IDictionary, object>>(me, param);
m.References<Person>(r, x.Name);
}
else
{
m.Map(x.Name)
}
}
//
// Some class that we want to reference, just an example of Fluent Nhibernate mapping
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Table("Person");
Id(x => x.PersonId, "PersonId");
Map(x => x.Name);
}
}
public class Person
{
public virtual Guid PersonId { get; set; }
public virtual string Name { get; set; }
public Person()
{ }
}
Maybe it would be helpful