I using neo4j 2.2.1, and I have code like this:
MATCH (n:person)-[r:BUY]->(p:product) WHERE p.name IN ['Bag','Book','Pencil'] RETURN SUM(r.total) AS st, n ORDER BY st DESC LIMIT 1
and I try to convert this code to C#,
class:
public class person
{
public string name { get; set; }
}
public class product
{
public string name { get; set; }
}
public class buy
{
public int total { get; set; }
}
Here is my query
public void search1()
{
var data1 = new[] { Bag, Book, Pencil };
var cypher = client.Cypher
.Match("(n:person)-[r:BUY]->(p:product)")
.Where(" p.name IN {data1}")
.WithParams(new { data1 })
.Return((n, r) => new
{
person1 = n.As<person>(),
buy1 = r.As<buy>(),
})
.OrderByDescending("sum(r.total)").Limit(1); //this is an error
foreach (var result in cypher.Results)
{
result1 = result.person1.name;
total1 = result.buy.total; // I want to get sum(r.total) but I can't
}
}
So, what's wrong in my query, and how do I fix it?
I think you should find this will work as you want:
Cypher.Match("(n:Person)-[r:BUY]->(p:product)")
.Where("p.name in {data1}")
.WithParams(new {data1})
.Return(n => new {
Person = n.As<Person>(),
Total = Return.As<int>("SUM(r.Total)")
})
.OrderByDescending("Total")
.Limit(1)
I don't know where the a parameter you're returning in your original query is coming from, as you don't math on a anywhere.
I think you just need to add a WITH - using an alias for the summed property - to your cypher statement. I've got a similar query where I've done that and it worked.
Like so:
var cypher = client.Cypher
.Match("(n:person)-[r:BUY]->(p:product)")
.Where(" p.name IN {data1}")
.WithParams(new { data1 })
.With("a,r,sum(r.total) as sumtotal")
.Return((a, r) => new
{
person1 = a.As<person>(),
buy1 = r.As<buy>(),
})
.OrderByDescending("sumtotal").Limit(1);
Related
I have two indexes set up in Elasticsearch storing different types of data and I'm attempting to get search results using nest from both indexes at the same time. I have set up models like the following...
public class Person
{
[Number(Name="person_id")]
public int Id { get; set; }
[Date(Name = "person_created")]
public DateTime Created { get; set; }
...
}
public class Work {
[Number(Name="work_id")]
public int Id { get; set; }
[Date(Name = "work_created")]
public DateTime Created { get; set; }
...
}
When querying on a single index I'm able to do the following and get my results back mapped to my model type...
var request = new SearchRequest("works")
{
From = searchQuery.Offset,
Size = searchQuery.PageSize,
Query = new QueryStringQuery { Query = searchQuery.SearchTerm },
};
var result = _elasticClient.Search<Work>(request);
However when doing a query like the following how do I tell nest what types to map results to per index?
var request = new SearchRequest("works,person")
{
...
}
var result = _elasticClient.Search<object> ...
Other answers I've seen suggest doing something like the following but I think the Types function has been removed in NEST 7.0...
client.Search<object>(s => s
.Size(100)
.SearchType(SearchType.Count)
.Type(Types.Type(typeof(Dog), typeof(Cat)))
If you are in control of document indexing you could take advantage of the possibility to customize JSON serialization process in NEST, by placing .NET type within a document in an elasticsearch index. This way NEST will deserialize documents into correct types.
class Program
{
public class Person
{
[Number(Name="person_id")]
public int Id { get; set; }
[Date(Name = "person_created")]
public DateTime Created { get; set; }
}
public class Work
{
[Number(Name="work_id")]
public int Id { get; set; }
[Date(Name = "work_created")]
public DateTime Created { get; set; }
}
static void Main(string[] args)
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var connectionSettings =
new ConnectionSettings(pool,
sourceSerializer: (builtin, settings) => new JsonNetSerializer(builtin, settings,
() => new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All}
));
var client = new ElasticClient(connectionSettings);
var deleteIndexResponse = client.Indices.Delete("person,work");
var createIndexResponse = client.Indices
.Create("person", i => i.Map<Person>(m => m.AutoMap()));
var createIndexResponse2 = client.Indices
.Create("work", i => i.Map<Work>(m => m.AutoMap()));
client.Index(new Person {Id = 1, Created = DateTime.UtcNow},
i => i.Index("person"));
client.Index(new Work {Id = 1, Created = DateTime.UtcNow},
i => i.Index("work"));
client.Indices.Refresh();
var searchResponse = client.Search<object>(s => s
.Index("person,work")
.Query(q => q.MatchAll()));
foreach (var document in searchResponse.Documents)
{
Console.WriteLine($"Person? {document is Person} Work? {document is Work}");
}
}
}
Prints:
Person? True Work? False
Person? False Work? True
NEST.JsonNetSerializer package needs to be installed in your project.
Hope that helps.
class Entity {
public MyEnum Type { get; set; }
public bool IsEnabled { get; set; }
}
class FilterItem {
public string Name { get; set; }
public int Total { get; set; }
}
IQueryable<Entity> entityQuery = _db.Entities;
IQueryable<IGrouping<string , Entity>> groupingQuery;
groupingQuery = entityQuery.GroupBy(f => f.Type);
var query = groupingQuery.Select(f => new FilterItem
{
Name = f.Key, // Here the type error occurs
Total = f.Count()
});
return query.ToListAsync();
Right now it throws an error that it cannot convert MyEnum to string. So I tries to do so:
groupingQuery = entityQuery.GroupBy(f => f.Type.ToString()); // Cannot call a function like that in a query builder.
but I found out that it is not possible when actually trying to run this part. My question is:
Is there a way to convert the enum so it would match my declared 'groupingQuery' variable type?
Or, maybe if the type of 'groupingQuery' would be
IQueryable<IGrouping<MyEnum , BucketEntity>>
then maybe it is somehow possible to do the conversion on Select?
var query = groupingQuery.Select(f => new FilterItem
{
Name = f.Key.toString(), // This is what I would imagine in an ideal world
Total = f.Count()
});
You actually can call ToString() on the key. However just in the Select() and not in the GroupBy(). Watch the writing of ToString (not toString) and it should work.
I tested it with the following code:
class Program
{
static void Main(string[] args)
{
var q = new Test[] { Test.One, Test.Two, Test.Two };
var e = q.AsQueryable().GroupBy(x => x).Select(x => new
{
Name = x.Key.ToString(),
Amount = x.Count()
});
foreach (var x in e)
{
Console.WriteLine(x.Name);
}
Console.ReadLine();
}
}
enum Test
{
One,
Two
}
The output of the program is:
One
Two
I'm using jqueryui autocomplete to assist user in an item selection. I'm having trouble selecting the correct items from the objects' subcollections.
Object structure (simplified) is
public class TargetType
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<SubCategory> SubCategories { get; set; }
public TargetType()
{
SubCategories = new HashSet<SubCategory>();
}
}
public class SubCategory
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<SubTargetType> SubTargetTypes { get; set; }
public SubCategory()
{
SubTargetTypes = new HashSet<SubTargetType>();
}
}
Currently I'm doing this with nested foreach loops, but is there a better way?
Current code:
List<SubTargetResponse> result = new List<SubTargetResponse>();
foreach (SubCategory sc in myTargetType.SubCategories)
{
foreach (SubTargetType stt in sc.SubTargetTypes)
{
if (stt.Name.ToLower().Contains(type.ToLower()))
{
result.Add(new SubTargetResponse {
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name });
}
}
}
You can do using Linq like this
var result = myTargetType.SubCategories
.SelectMany(sc => sc.SubTargetTypes)
.Where(stt => stt.Name.ToLower().Contains(type.ToLower()))
.Select(stt => new SubTargetResponse {
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name });
The above query doesn't work. The following should work, but I'd not recommend that as that'd not be faster or more readable.
var result = myTargetType.SubCategories
.Select(sc => new Tuple<int, IEnumerable<SubTargetType>>
(sc.Id,
sc.SubTargetTypes.Where(stt => stt.Name.ToLower().Contains(type.ToLower()))))
.SelectMany(tpl => tpl.Item2.Select(stt => new SubTargetResponse {
Id = stt.Id,
CategoryId = tpl.Item1,
Name = stt.Name }));
Actually there are 2 different questions.
LINQ select all items of all subcollections that contain a string
Solutions:
(A) LINQ syntax:
var result =
(from sc in myTargetType.SubCategories
from stt in sc.SubTargetTypes.Where(t => t.Name.ToLower().Contains(type.ToLower()))
select new SubTargetResponse
{
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name
})
.ToList();
(B) Method syntax:
var result =
myTargetType.SubCategories.SelectMany(
sc => sc.SubTargetTypes.Where(stt => stt.Name.ToLower().Contains(type.ToLower())),
(sc, stt) => new SubTargetResponse
{
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name
})
.ToList();
Currently I'm doing this with nested foreach loops, but is there a better way?
Well, it depends of what do you mean by "better". Compare your code with LINQ solutions and answer the question. I personally do not see LINQ being better in this case (except no curly braces and different indentation, but a lot of a hidden garbage), and what to say about the second LINQ version in this answer - if that's "better" than your code, I don't know where are we going.
I have a Save Method that saves with a Linq query a manually re-orderd list (in a web form) that is passed as the parameter to my method, and I try to update the Order Property of the IEnumerable<VM_CategoryLabel> I retrieve from the database (EF) with the corresponding value in the list (maybe would that be clearer with my code below):
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
//Order = x.Sequence_Cat,
Order = (int)listTemplate.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
I used the "test" var to see if my "sub-query" gets the correct value, and it does, but when I use my Linq expression inside the Select (the commented Order line), I get the following error:
Unable to create a constant value of type 'Namespace.Models.VM_CategoryLabelExtra. "Only primitive types and enumeration types are supported in this context.
Here are my classes:
public class VM_CategoryLabel
{
public int Id { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
public class VM_CategoryLabelExtra
{
public int Id { get; set; }
public int IdCat { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
So I suppose that I should not query the list inside my query ? So how do I "match" the 2 lists of values ?
I also tried the following (after having replace in the Linq query: Order = x.Sequence_Cat)that is not working neither because the iteration variable is
read-only:
foreach (var item in requete)
{
item.Order = listTemplate.Where(x => x.Id == item.Id).Select(x => x.Order).FirstOrDefault();
}
try
{
context.SaveChanges();
I suggest using this.
It is the let clause.
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
let list = listTemplate
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
Order = list.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
edit: instead offrom you can just do let list = listTemplate
Should work now :)
example for let:
// The let keyword in query expressions comes in useful with subqueries: it lets
// you re-use the subquery in the projection:
from c in Customers
let highValuePurchases = c.Purchases.Where (p => p.Price > 1000)
where highValuePurchases.Any()
select new
{
c.Name,
highValuePurchases
}
If you do not know how Let working than please download LinqPad and see an example
I have the following array of TrackerReport Object:
public class TrackerReport : TrackerMilestone
{
public long Views { get; set; }
public override string ToString()
{
return "Id=" + MilestoneId + " | Name=" + Name + " | Views = " + Views;
}
I post also the parent class to better explain:
public class TrackerMilestone
{
public int MilestoneId { get; set; }
public int CampaignId { get; set; }
public string Name { get; set; }
public int? SortIndex { get; set; }
public string Url { get; set; }
public bool IsGoal { get; set; }
public bool IsPartialGoal { get; set; }
public int? ParentMilestoneId { get; set; }
public int? ViewPercent { get; set; }
public override string ToString()
{
return "Id=" + MilestoneId + " | Name=" + Name + " | Url = " + Url;
}
}
So that it is displayed like this more or less:
ID Name Url Counter
1 A ab.com 5
2 N ac.com 2
And I have a List of this object that I fill in this way:
var trackerReportList = new List<TrackerReport[]>();
foreach (var trackerItem in trackerChildren)
{
//currentReport is the array of TrackerReport TrackerReport[] above mentioned
var currentReport = GetReportForItem(GetFromDate(), GetToDate(), trackerItem.ID,
FindCampaignId(trackerItem));
trackerReportList.Add(currentReport);
}
All the entries have the same values, but the counter, so, for ex:
list1:
ID Name Url Counter
1 A ab.com 5
2 N ac.com 2
list2:
ID Name Url Counter
1 A ab.com 17
2 N ac.com 28
My goal is to generate a single TrackerReport[], with the sum of the counter values as counter.
TrackerReport[]:
ID Name Url Counter
1 A ab.com 22
2 N ac.com 30
Is there any Linq query to do this? Or If you come up with a better solution, just post it.
Thanks in advance!
Here's how to do it using Linq.
Firstly, you want to get the data in a single list rather than a List of Arrays. We can do this with a SelectMany.
trackerReportList.SelectMany(c => c) flattens List<TrackerReport[]>() into IEnumerable<TrackerReport>
Then you group the objects by the relevant column/columns
group p by new {
ID = p.MilestoneId,
Name = p.Name,
Url = p.Url} into g
Finally, you project the grouping into the format we want. Counter is obtained by Adding the Views for the collection of objects in each grouping.
select new {
...
};
Putting it all together:
var report = from p in trackerReportList.SelectMany(c => c)
group p by new {
ID = p.MilestoneId,
Name = p.Name,
Url = p.Url} into g
select new {
ID = g.Key.ID,
Name = g.Key.Name,
Url = g.Key.Url,
Counter = g.Sum(c => c.Views)
};
This is the lambda expression for your query.
TrackerReport[] trackerInfoList = trackerReportList
.SelectMany(s => s)
.GroupBy(g => g.MilestoneId)
.Select(s =>
{
var trackerReportCloned = Clone<TrackerReport[]>(s.First());
trackerReportCloned.Views = s.Sum(su => su.Views);
return trackerReportCloned;
})
.ToArray();
If you have noted, I have used Clone<T>(T) method to deep cloning one object by the grouped value. I could have done by copying each property, but I found it the easiest.
This is the Clone() method:
public static T Clone<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
Before you continue, you need to set [Serializable] attribute on your DTO objects.
[Serializable]
public class TrackerReport : TrackerMilestone
{
.
.
.
}
[Serializable]
public class TrackerMilestone
{
.
.
.
}
Hope, this is what you are looking for.
I'd do it in the following way:
1) Let the TrackerReports[] resultList is the array of deep copies of the trackerReportList[0] items
2) As I understand, the Views property corresponds to the Counter column. If so, then
foreach(report in resultList)
{
report.Views = trackerReportList.Sum(reports => reports.Single(r => r.MilestoneId == report.MilestoneId).Views);
}