I am using odata and attempting to filter queries on the properties of a child class.
I am using MongoDb to store a series of object and am wanting to query these objects using OData. The objects are auto generated from xml so there is some annoying inheritance, the basic object scenario is below.
public class Container {
public Parent Property {get; set;}
}
public class Parent {}
public class Child : Parent {
public StringWrapper Value {get; set;}
}
public class StringWrapper {
public string Value {get; set;}
}
So I have made the Container into an entity and made a controller which has the code:
public ContainerController : ODataController {
public PageResult<Container> Get(ODataQueryOptions<Container> queryOptions) {
IQueryable<Container> containers = mongoRepo.All();
var filteredContainers = queryOptions.ApplyTo(containers)
as IQueryable<Container>;
return new PageResult<Container>(filteredContainers,
Request.GetNextPageLink(),
Request.GetInlineCount());
}
}
I am then querying this with the uri:
http://...Container?$filter=Property/NS.Child/Value/Value eq 'example'
If I throw a break point after applying the queryoptions and look at the IQueryable.Expression it gives the result:
value(MongoDB.Driver.Linq.MongoQueryable`1[NS.Container])
.Where($it => (IIF((IIF((($it.Property As Child) == null), null, ($it.Property As Child).Value) == null), null, ($it.Property As Child).Value.Value) == value(System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.String]).TypedProperty))
when this is then resolved I get the following error
Unable to determine the serialization information for the expression: .
Edit
I tried implementing this basic case without using the mongo db and the same query works fine. I then tried a test with the mongo C# driver for using .where with the As method this resulted in the same error.
I found the query
queryable.Where(
it => (it.Property is Child && ((Child)it.Properties).Value.Value == "example"));
works correctly and was wondering if there is a way to get the query into this form from the same uri or alternatively get the C# driver for mongodb to convert as queries into this form?
Can you try this instead,
var filteredContainers = queryOptions.ApplyTo(containers,
new ODataQuerySettings
{
EnableConstantParameterization = false,
HandleNullPropagation = HandleNullPropagationOption.False
});
?
Related
so reading this:
https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.x/field-inference.html
given a class of
public class MyData {
public Guid UserId { get; set; }
public decimal Value { get; set; }
}
I would expect to be able to do something like:
var fieldExpression = Field<MyData >(p => p.Value);
but I get an error of The non-generic type Field cannot be used with type arguments.
I am using Nest 7.1.0.
My goal was to be able to create a method that can have aggregations and queries passed in and then combined into running on a instance of a nest ElasticClient.
something like (I think)
var sr = new SearchRequest<MyData>
{
Aggregations = new ChildrenAggregation("name_of_child_agg", typeof(decimal?))
{
Aggregations =
new AverageAggregation("average_per_child", Field<MyData>(p => p.value))
&& new MaxAggregation("max_per_child", Field<MyData>(p => p.value))
&& new MinAggregation("min_per_child", Field<MyData>(p => p.value))
}
};
_client.Search<MyData>(sr);
As stated in the documentation you need to add a static import to be able to write the code using the same style
using static Nest.Infer;
Otherwise you need to use simple new Field(..) instantiation
I have this very weird problem that I cannot get my head around. Perhaps someone could point out what I am doing wrong.
Basically, I am just trying to search items using Linq to Sitecore.
So, my classes look like ( I am using glass too)
[SitecoreType(TemplateId = "{TEMPLATE_GIUD}")]
public class MyMappedClass : SharedFieldClass
{
[SitecoreField(FieldName = "mylist")]
public virtual IEnumerable<SharedFieldClass> MyMultilistField { get; set; }
[SitecoreField(FieldName = "field1")]
[IndexField("field1")]
public virtual MyKeyValue field1 { get; set; }
}
[SitecoreType]
public class MyKeyValue
{
public virtual Sitecore.Data.ID Id {get;set;}
public virtual string MyValue{get;set;}
}
So when I do the below query it works as it's supposed to.
var results = context.GetQueryable<SearchResultItem>()
.Where(c => ((string)c["field1"]) == "{GUID}").GetResults();
But, when I do the below it returns 0 result.
List<MyMappedClass> results = context.GetQueryable<MyMappedClass>()
.Where(c => c.field1.MyValue == "{GUID}").ToList();
I have read this link . And I have followed the 2nd process described here for Glass to work with Sitecore7 Search (the "SharedFieldClass" contains all the basic index fields).
This is a pretty obvious scenario and I'm sure lots of people have done it already and I am doing something silly here.
Thanks in advance.
/## EDIT ##/
Okay so I've done a bit more digging on this. Not sure if it's a bug in ContentSearch/Luncene.NET API or I am missing something BUT seems like what was posted here is probably not TRUE and if you have a complex field type ( which you will ) you can not query with a mapped class against Lucene. ( not sure if this is also the case for Solr). If you are doing search against simple type like string and int then it works like a charm.
SO here're my findings:
After enabling DEBUG and LOG on for contentsearch I found that if I query like this context.GetQueryable<MyMappedClass>().Where(c => c.field1.MyValue == "{GUID}") what it gets translated into is DEBUG Executing lucene query: field1.value:7e9ed2ae07194d83872f9836715eca8e and as there's no such thing in the index named "field1.value" the query doesn't return anything. The name of the index is actually just "field1". Is this a bug ??
However, query like this context.GetQueryable<SearchResultItem>() .Where(c => ((string)c["field1"]) == "{GUID}").GetResults(); works because it gets translated into "DEBUG Executing lucene query: +field1:7e9ed2ae07194d83872f9836715eca8e".
I also did write a method in my mapped class like below:
public string this[string key]
{
get
{
return key.ToLowerInvariant();
}
set { }
}
Which allowed me write the below query with my MyMappedClass.
results2 = context.GetQueryable<MyMappedClass>().Where(c => c["filed1"]== "{GUID}").ToList();
This returned expected result. BUT the values of the fields in MyMappedClass are not filled ( in fact they're all null except the core/shared values like templateid, url etc which are populated in the filled document). So the result list are pretty much useless. I could do a loop over all of them and manually get the values populated as I have the itemid. But imagine the cost for a large result set.
Lastly I did this:
<fieldType fieldTypeName="droplink" storageType="YES" indexType="TOKENIZED" vectorType="NO" boost="1f" type="System.String" settingType="Sitecore.ContentSearch.LuceneProvider.LuceneSearchFieldConfiguration, Sitecore.ContentSearch.LuceneProvider" />
So this returned populated "field1" with the itemid in lucene query using "IndexViewer2.0". BUT this fails for MyMappedClass too as the value of "field1" in the document is a System.string .. but it is mapped as "MyKeyValue" in MyMappedClass SO it throws the below exception
Exception: System.InvalidCastException
Message: Invalid cast from 'System.String' to 'MyLib.MyKeyValue'.
SO, the big question is:
How does one query using his/her mapped class using the cool ContentSearch API ?
I bit more further digging got me to a working solution. Posting it here just in case anyone runs into this issue.
This is how my "SharedFieldClass" looked like ( which was somewhat wrong )
public abstract class SharedFieldClass
{
[SitecoreId]
[IndexField("_id")]
[TypeConverter(typeof(IndexFieldIDValueConverter))]
public virtual ID Id { get; set; }
[SitecoreInfo(SitecoreInfoType.Language)]
[IndexField("_language")]
public virtual string Language { get; set; }
[SitecoreInfo(SitecoreInfoType.Version)]
public virtual int Version
{
get
{
return Uri == null ? 0 : Uri.Version.Number;
}
}
[TypeConverter(typeof(IndexFieldItemUriValueConverter))]
[XmlIgnore]
[IndexField("_uniqueid")]
public virtual ItemUri Uri { get; set; }
}
And there's a class in Glass that does the mapping. Which looks like below:
var sitecoreService = new SitecoreService("web");
foreach (var r in results)
{
sitecoreService.Map(r);
}
for me this class was failing to map because of this line:
[SitecoreId]
[IndexField("_id")]
[TypeConverter(typeof(IndexFieldIDValueConverter))]
public virtual ID Id { get; set; }
It was throwing a NULL exception at sitecoreService.Map(r); line
So I changed it to below:
[SitecoreId]
[IndexField("_group")]
[TypeConverter(typeof(IndexFieldIDValueConverter))]
public virtual ID Id { get; set; }
And it worked. I'm not sure when the index field for ItemId in sitecore changed from "_id" to "_group" or whether it was always like that. But it is "_group" in the SearchResultItem class. So I used it and mapping was successful.
So to Sum it all the solution looks like this:
The Mapped Class:
[SitecoreType(TemplateId = "{TEMPLATE_GIUD}")]
public class MyMappedClass : SharedFieldClass
{
[SitecoreField(FieldName = "mylist")]
public virtual IEnumerable<SharedFieldClass> MyMultilistField { get; set; }
[SitecoreField(FieldName = "field1")]
[IndexField("field1")]
public virtual MyKeyValue field1 { get; set; }
// Will be set with key and value for each field in the index document
public string this[string key]
{
get
{
return key.ToLowerInvariant();
}
set { }
}
}
[SitecoreType]
public class MyKeyValue
{
public virtual Sitecore.Data.ID Id {get;set;}
public virtual string MyValue{get;set;}
}
And the query is:
List<MyMappedClass> results = context.GetQueryable<MyMappedClass>()
.Where(c => c["field1"] == "{GUID}").ToList();
That's it.
/* edited */
OH!! Wait that's not it. This will still fail to cast the complex type "MyKeyValue" when the "field1" is populated with guid in the index document.
So to avoid this I had to write my custom converter as #Michael Edwards suggested.
I had to modify the class slightly to fit my needs ..
public class IndexFieldKeyValueModelConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
var config = Glass.Mapper.Context.Default.GetTypeConfiguration<SitecoreTypeConfiguration>(sourceType, true);
if (config != null && sourceType == typeof(MyLib.IKeyValue))
{
return true;
}
else
return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
return true;
else
return base.CanConvertTo(context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var scContext = new SitecoreContext();
Guid x = Guid.Empty;
if(value is string)
{
x = new Guid((string)value);
}
var item = scContext.Database.GetItem(x.ToString());
if (item == null)
return null;
return scContext.CreateType(typeof(MyLib.IKeyValue), item, true, false, new Dictionary<string, object>());
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
var config = Glass.Mapper.Context.Default.GetTypeConfiguration<SitecoreTypeConfiguration>(value.GetType(), true);
ID id = config.GetId(value);
return id.ToShortID().ToString().ToLowerInvariant();
}
}
Not sure if it were the expected behaviour or not .. but for some reason, in the convertfrom method the value of the "object value" parameter was short string id format. So for scContext.Database.GetItem to work I had to convert it to a proper GUID and then it started returning proper item rather than null.
AND then I wrote my query like this:
results = context.GetQueryable<MyMappedGlassClass>().Where(c => c["field1"] == field1value && c["field2"] == field2value && c["_template"] == templateId).Filter(selector => selector["_group"] != currentId).ToList();
Looks like a fair bit of work to get it to work. I guess using the LinqHelper.CreateQuery method is the easy way out .. but as Mr. Pope suggested here that this method is not be used as this is an internal method.
Not sure what's the balance here.
/* end edited */
Please see my question description section for explanation on why I had to do things this way.
Also, (I bias opinion ) is the trick described here may not be valid anymore (please see my question description's edit section for the reason behind).
Also, index field for itemid in the Glass Mapper tutorial here is I think wrong (unless otherwise proven).
Hope it helps someone saving/wasting time.
You could create a custom field mapper for lucene that would convert from the Guid in the index to a glass model. I hacked this out but I haven't tested it:
public class IndexFieldDateTimeValueConverter : TypeConverter
{
public Type RequestedType { get; set; }
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
var config = Glass.Mapper.Context.Default.GetTypeConfiguration<SitecoreTypeConfiguration>(sourceType, true);
if (config != null)
{
RequestedType = sourceType;
return true;
}
else
return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
return true;
else
return base.CanConvertTo(context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var scContext = new SitecoreContext();
return scContext.CreateType(RequestedType, scContext.Database.GetItem(value.ToString()),true, false, new Dictionary<string, object>());
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
var config = Glass.Mapper.Context.Default.GetTypeConfiguration<SitecoreTypeConfiguration>(value.GetType(), true);
ID id =config.GetId(value);
return id.ToShortID().ToString().ToLowerInvariant();
}
My concern is that the ConvertFrom method does not get passed the type requested so we have to store this as property on the class to pass it from the CanConvertFrom method to the ConvertFrom method. This makes this class not thread safe.
Add this to the indexFieldStorageValueFormatter section of the sitecore config.
The problem here is that SearchResultItem is not actually an Item, but does have the GetItem() method to get the Sitecore item. What you need to do is the following:
List<MyMappedClass> results = context.GetQueryable<SearchResultItem>()
.Select(sri => sri.GetItem())
.Where(i => i != null)
.Select(i => i.GlassCast<MyMappedClass>())
.Where(c => c.field1.MyValue == "{GUID}").ToList();
I haven't worked with Glass specifically, but if you change your parent class to SearchResultItem, does it begin working? If so, that would indicate an issue with the SharedFieldClass parent class.
I have a web api 2 application, and in my controller , I have this code :
[Queryable]
public IQueryable<Title> GetTitles()
{
return db.Titles;
}
and here is the Title entity :
public partial class Title
{
public Title()
{
this.People = new HashSet<Person>();
}
public short Id { get; set; }
public string NameT { get; set; }
public virtual ICollection<Person> People { get; set; }
}
When people query the Titles, they must get only "NameT" property. but now they get all of the properties. and yes, I know about $select, but I want another way. means even they use $select, they should not able to get "Id" property for example. if I have to bring more information, please tell me. thanks.
There are two ways to solve your problem when you use ODataController. However, they won't affect ApiController non-query part.
In that condition, you can try what Zoe suggested.
1.Ignore those properties while building your model with model builder.
builder.EntityType<Title>().Ignore(title => title.Id);
2.Add ignore member attributes on those properties.
[IgnoreDataMember]
public short Id { get; set; }
More than these, we provide support for limiting the set of allowed queries in Web API 2.2 for OData v4.0.
http://blogs.msdn.com/b/webdev/archive/2014/03/13/getting-started-with-asp-net-web-api-2-2-for-odata-v4-0.aspx
We can use attributes like Unsortable, NonFilterable, NotExpandable or NotNavigable on the properties of the types in our model, or we can configure this explicitly in the model builder.
Maybe you can have filter in your action GetTitles(), like:
[Queryable]
public IQueryable<Title> GetTitles()
{
return db.Titles.Select(t=>t.Name);
}
Use the ODataModelBuilder class as opposed to the ODataConventionModelBuilder class.
var modelBuilder = new ODataModelBuilder();
var titles = modelBuilder.EntitySet<Title>("titles");
var title = titles.EntityType;
title.HasKey(x => x.Id);
title.Ignore(x => x.Id);
title.Property(x => x.TName);
titles.HasIdLink(x => { return x.GenerateSelfLink(true); }, true);
config.Routes.MapODataRoute("odata", "odata", modelBuilder.GetEdmModel());
I'm designing an application using ASP.NET Web API and Entity Framework 5 and LINQ to Entities. The Web API doesn't serve up the entities directly, it converts them to a set of data transfer objects that are similar but not identical to my entities. The API will be used by a Silverlight application initially but I will have to support non-.NET clients (e.g. iOS apps) down the road. I'd also like to give the client the ability to run a robust set of queries against the API.
These requirements have lead me to consider the query object pattern. Essentially, I want to create a homegrown query object client-side, post it to the Web API, and convert the query object to a lambda expression that I can use in LINQ to Entities. This last part is what's tripping me up.
Starting with a simple comparison query, I want to be able to convert an object that looks like the following into a lambda expression at runtime.
public enum QueryOperator
{
None = 0,
GreaterThan,
GreaterThanOrEqualTo,
EqualTo,
NotEqualTo,
LessThanOrEqualTo,
LessThan
}
public class SimpleQuery<T>
{
public SimpleQuery()
{
this.Field = null;
this.Operator = QueryOperator.None;
this.Value = null;
}
public string Field { get; set; }
public QueryOperator Operator { get; set; }
public object Value { get; set; }
public IEnumerable<T> Execute(IQueryable<T> queryTarget)
{
// ????
}
}
How can I do this?
I've had to do things like this in the past. Here's what I came up with:
public IEnumerable<T> Execute(IQueryable<T> queryTarget)
{
return queryTarget.Where(this.GetWhereExpression<T>());
}
private Expression<Func<T, bool>> GetWhereExpression<T>()
{
var param = Expression.Parameter(typeof(T), "x");
var prop = Expression.Property(param, this.Field);
var value = Expression.Constant(this.Value, prop.Type);
Expression compare = null;
switch(this.Operator)
{
case QueryOperator.EqualTo:
compare = Expression.Equal(prop, value);
break;
...
}
return Expression.Lambda(compare, param);
}
How can I load collection with include, I have tried these:
Type.Where(t => t.Entity.Where(e => e.Parent == false).Count() > 0).Include(e => e.Entity)
But the filter is not supported here (Entities recovered not fulfill a condition).
With LinQ To Entities :
var v = from type in ObjectContext.Type
from entity in Type.Entities
where entity.Parent == false
select type;
But type do not contains it's associated Entities.
I can't write select new Type { type.Code, type.Entities } and I can't use anonymous type as it's not adapted for my repository layer.
Have you any ideas on how I can get a list of Type objects with its property Entities satisfying a condition?
Update
Thank you for your response
I think it's not a good idea to use the CTP , no ?
I am sorry, I did not explain my object model, I have :
class Type {
public string Code { get; set; }
public IList<Entity> Entities { get; set; }
...
}
class Entity {
public string Code { get; set; }
...
}
And I want to use your second filtering proposal : Filter both entity sets
Do you have any suggestions without using the CTP ?
Thanks
Rad
as you stated you want to apply filters to the related entities, best to look at the new CTP5 of the EF4 http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx
Look under "Applying filters when explicitly loading related entities"