DocumentExists() fails when a wildcard is used in its Type - nest

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.

Related

Swagger listing IFormFile parameter as type "object"

I have a controller that requests a model containing an IFormFile as one of it's properties. For the request description, the Swagger UI (I'm using Swashbuckle and OpenApi 3.0 for .NET Core) lists the type of the file property as type object. Is there some way to make the Swagger UI denote the exact type and it's JSON representation to help the client?
The controller requesting the model looks as follows.
[HttpPost]
[Consumes("multipart/form-data")
public async Task<IActionResult> CreateSomethingAndUploadFile ([FromForm]RequestModel model)
{
// do something
}
And the model is defined as below:
public class AssetCreationModel
{
[Required}
public string Filename { get; set; }
[Required]
public IFormFile File { get; set; }
}
We've been exploring this issue today. If you add the following to your startup it will convert IFormFile to the correct type
services.AddSwaggerGen(c => {
c.SchemaRegistryOptions.CustomTypeMappings.Add(typeof(IFormFile), () => new Schema() { Type = "file", Format = "binary"});
});
Also see the following article on file upload in .net core
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-2.1
This problem was already tackled in the following github issue/thread.
This improvement was already merged into Swashbuckle.AspNetCore master (as per 10/30/2018), but i don't expect that to be available as a package soon.
There are simple solutions if you only have a IFormFile as a parameter.
public async Task UploadFile(IFormFile filePayload){}
For simple case you can take a look at the following answer.
For complicated cases like container cases, you can take a look at the following answer.
internal class FormFileOperationFilter : IOperationFilter
{
private struct ContainerParameterData
{
public readonly ParameterDescriptor Parameter;
public readonly PropertyInfo Property;
public string FullName => $"{Parameter.Name}.{Property.Name}";
public string Name => Property.Name;
public ContainerParameterData(ParameterDescriptor parameter, PropertyInfo property)
{
Parameter = parameter;
Property = property;
}
}
private static readonly ImmutableArray<string> iFormFilePropertyNames =
typeof(IFormFile).GetTypeInfo().DeclaredProperties.Select(p => p.Name).ToImmutableArray();
public void Apply(Operation operation, OperationFilterContext context)
{
var parameters = operation.Parameters;
if (parameters == null)
return;
var #params = context.ApiDescription.ActionDescriptor.Parameters;
if (parameters.Count == #params.Count)
return;
var formFileParams =
(from parameter in #params
where parameter.ParameterType.IsAssignableFrom(typeof(IFormFile))
select parameter).ToArray();
var iFormFileType = typeof(IFormFile).GetTypeInfo();
var containerParams =
#params.Select(p => new KeyValuePair<ParameterDescriptor, PropertyInfo[]>(
p, p.ParameterType.GetProperties()))
.Where(pp => pp.Value.Any(p => iFormFileType.IsAssignableFrom(p.PropertyType)))
.SelectMany(p => p.Value.Select(pp => new ContainerParameterData(p.Key, pp)))
.ToImmutableArray();
if (!(formFileParams.Any() || containerParams.Any()))
return;
var consumes = operation.Consumes;
consumes.Clear();
consumes.Add("application/form-data");
if (!containerParams.Any())
{
var nonIFormFileProperties =
parameters.Where(p =>
!(iFormFilePropertyNames.Contains(p.Name)
&& string.Compare(p.In, "formData", StringComparison.OrdinalIgnoreCase) == 0))
.ToImmutableArray();
parameters.Clear();
foreach (var parameter in nonIFormFileProperties) parameters.Add(parameter);
foreach (var parameter in formFileParams)
{
parameters.Add(new NonBodyParameter
{
Name = parameter.Name,
//Required = , // TODO: find a way to determine
Type = "file"
});
}
}
else
{
var paramsToRemove = new List<IParameter>();
foreach (var parameter in containerParams)
{
var parameterFilter = parameter.Property.Name + ".";
paramsToRemove.AddRange(from p in parameters
where p.Name.StartsWith(parameterFilter)
select p);
}
paramsToRemove.ForEach(x => parameters.Remove(x));
foreach (var parameter in containerParams)
{
if (iFormFileType.IsAssignableFrom(parameter.Property.PropertyType))
{
var originalParameter = parameters.FirstOrDefault(param => param.Name == parameter.Name);
parameters.Remove(originalParameter);
parameters.Add(new NonBodyParameter
{
Name = parameter.Name,
Required = originalParameter.Required,
Type = "file",
In = "formData"
});
}
}
}
}
}
You need to look into how you can add some/an OperationFilter that is suitable for your case.

Programmatic PUT mapping for a type isn't being used?

I've defined a programmatic mapping for a type following the example in the fluent API unit test (FluentMappingFullExampleTests) like so:
_client.Map<SomeType>(m => m
.Type("mytype")
...
I then add an instance of SomeType to the index via a call like
_client.Index<SomeType>(instance)
However, when I go searching for an instance, I don't find any instances of 'mytype'; instead, there's an instance of 'sometype', and a new type mapping has been created for that 'sometype'. I would have expected that the PUT mapping would be honored when I performed the insertion.
Am I not using the PUT mappings the way they should be used? Unfortunately, the unit test doesn't demonstrate round-tripping, so I'm unsure if there's something else I should be doing.
Edit: It bears mentioning that I'm trying to achieve 100% programmatic mapping, here; no NEXT attributes on the type.
I was able to handle your use case in my example:
//request url: http://localhost:9200/indexName
var indicesOperationResponse = client.CreateIndex(indexName);
//request url: http://localhost:9200/indexName/document2/_mapping
var response = client.Map<Document>(m => m
.Type("document2")
.Properties(p => p.String(s => s.Name(n => n.Name).Index(FieldIndexOption.NotAnalyzed))));
//request url: http://localhost:9200/indexName/document2/1
client.Index(new Document { Id = 1, Name = "test"});
client.Refresh();
//request url: http://localhost:9200/indexName/document2/_search
var searchResponse = client.Search<Document>(s => s.Query(q => q.MatchAll()));
Key thing was to mark the Document class with ElasticType attribute:
[ElasticType(Name = "document2")]
public class Document
{
public int Id { get; set; }
public string Name { get; set; }
}
Hope this helps you.
UPDATE
Your comment makes sense. Instead of using ElasticType you can change type name inference for ElasticClient.
var uri = new Uri("http://localhost:9200");
var settings = new ConnectionSettings(uri)
.SetDefaultIndex(indexName)
.MapDefaultTypeNames(d => d.Add(typeof(Document), "document2"))
var client = new ElasticClient(settings);
So we can remove attribute from Document class
public class Document
{
public int Id { get; set; }
public string Name { get; set; }
}
We don't need also to specify type alias in mapping:
var response = client.Map<Document>(m => m
.Properties(p => p.String(s => s.Name(n => n.Name).Index(FieldIndexOption.NotAnalyzed))));

How to change the collection name on an index

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

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

FluentNHibernate: Getting Column & Table Names After Mapping Conventions Are Applied

I am wondering if it is possible to find the run-time column name for a class/component that has been mapped with FluentNHibernate after all conventions have been applied.
For example, given the simple model:
public class Address{
public string Street {get; set;}
public string Zip {get; set;}
}
public class AddressMap : ComponentMap<Address>{
Map( x => x.Street );
Map( x => x.Zip );
}
public class PersonMap : ClassMap<Person>
{
public PersonMap(){
Id( x => x.Id );
Map( x=> x.Ssn );
Map( x=> x.Name );
Component( x => x.Address )
.ColumnPrefix("ADDRESS_");
}
}
public class ClassConvention : IClassConvention
{
public void Apply( IClassInstance instance )
{
instance.Table( "tbl" + instance.EntityType.Name );
}
}
Table Name: tblPerson
Id Name Ssn ADDRESS_Street ADDRESS_Zip
-----------------------------------------------------------
1 Brian 11223344 123 Example St. 12345
What I'm looking for and what I am not sure how to do is the following:
var mappings = FluentNHibaernate.CompileMergeAndBuildAllMappings();
var zipCodeColumnName = mappings.FindMappedType<Address>().ColumnName(a => a.Zip)
zipCodeColumnName.ShouldBe("ADDRESS_Zip");
// Here I'm interested in the run-time & final column name with the
// prefix applied from the PersonMap class.
var personTableName = mappings.FindMappedType<Person>().TableName;
personTableName.ShouldBe("tblPerson");
// I'm interested in the final table name after the ClassConvention
// modified the table name.
Additional Clarification
I'm only interested in the result of FluentNHiberante's application of the conventions and mappings. Not in the actual SQL that is generated by NHibernate.
Thanks for the help,
Brian
[Test]
public void test4()
{
var ssnColumn = RuntimeNames
.ColumnName<Person>( x => x.Ssn );
ssnColumn.ShouldEqual( "Ssn" );
var addressColumn = RuntimeNames
.ColumnName<Person>( x => x.Address.Street );
addressColumn.ShouldEqual( "ADDRESS_Street" );
var personTableName = RuntimeNames
.TableName<Person>();
personTableName.ShouldEqual( "tblPerson" );
}
public static class RuntimeNames
{
private static Configuration cfg = Fluently.Configure()
.Database( MsSqlConfiguration.MsSql2005 )
.Mappings( m => m.FluentMappings
.AddFromAssemblyOf<PersonMap>()
.Conventions
.AddFromAssemblyOf<PersonMap>()
).BuildConfiguration();
public static string ColumnName<T>( Expression<Func<T, object>> property )
where T : class, new()
{
var accessor = FluentNHibernate.Utils.Reflection
.ReflectionHelper.GetAccessor( property );
var names = accessor.Name.Split('.');
var classMapping = cfg.GetClassMapping( typeof( T ) );
return WalkPropertyChain( classMapping.GetProperty(names.First()), 0, names );
}
private static string WalkPropertyChain(Property property, int index, string[] names)
{
if( property.IsComposite )
return WalkPropertyChain( ((Component)property.Value).GetProperty( names[++index] ), index, names );
return property.ColumnIterator.First().Text;
}
public static string TableName<T>() where T : class, new()
{
return cfg.GetClassMapping( typeof(T) )
.Table.Name;
}
}
since component column names can be different for each application of the Component you have to know which property you want.
var config = Fluently.Configure()
.DataBase(...)
.Mappings(...)
.BuildConfiguration();
var map = config.GetClassMapping(typeof(Person));
map.Table.Name.ShouldBe("tblPerson");
map.GetProperty("Address").IsComposite.ShouldBe(true);
((Component)map.GetProperty("Address").Value)
.GetProperty("Zip").ColumnIterator.First()
.Text.ShouldBe("ADDRESS_Zip");