How do get sum() of a property using the .Net neo4j client? - c#

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

Search multiple indexes and return correct types

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.

Changing IGrouping key type inside query builder

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

LINQ select all items of all subcollections that contain a string

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.

Linq "join" with a IList<T> getting "Error Unable to create a constant value.."

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

asp.net c# Linq query to retrieve the sum of each value of a column

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);
}

Categories