Creating a custom aggregate function in C# using LINQ - c#

Consider the following class:
public class TaxType
{
public int Id {get;set;}
public decimal TotalTaxCollected {get;set;}
public string DetailXml {get;set;}
}
I got the following LINQ query somewhere in the code:
GetTaxTypesFromTheDataSource()
.Where(/*blah blah blah*/)
.GroupBy(t => new { t.Id })
.Select(g => new TaxType
{
TotalTaxCollected = g.Sum(n => n.TotalTaxCollected),
DetailXml = g.Aggregate(SumOfInnerElementsOfXml).DetailXml
})
.ToList();
Where SumOfInnerElementsOfXml is declared as follows:
private TaxType SumOfInnerElementsOfXml(TaxType t1, TaxType t2)
{
//(a) Deserialize the t1.DetailXml & t2.DetailXml into 2 different arrays.
//(b) Aggregate both arrays based on their IDs.
//(c) Serialize the result into an xml string.
//(d) Instantiate a new TaxType and set its DetailXml to the generated xml string.
//return (d)
}
The above solution works fine, however I'd like to create my own aggregate function so that I can use it as follows:
GetTaxTypesFromTheDataSource()
.Where(/*blah blah blah*/)
.GroupBy(t => new { t.Id })
.Select(g => new TaxType
{
TotalTaxCollected = g.Sum(n => n.TotalTaxCollected),
DetailXml = g.MyCustomAggregateFunction(n => n.DetailXml)
})
.ToList();
How's that possible? I've tried several extension methods but unfortunately none of them worked.

Your initial solution seems pretty fine to me. However, if you don't like it...
To create a predefined function (not lambda), I'd recommend first to get rid of anonymous type in your query - change:
.GroupBy(t => new { t.Id })
to
.GroupBy(t => t.Id)
This is also generally better, as you get rid of one boxing/unboxing per TaxType and transform IGrouping<{anonymous},TaxType> into IGrouping<int,TaxType> (easier to understand the semantics if someone inspects the code later)
Then, you can declare the method as follows:
public static class Extensions {
public static string MyCustomAggregateFunction(this IGrouping<int,TaxType> source, Func<TaxType,string> selector) {
// blah-blah-blah
//return something
}
}
You can later make it generic if the need arises.
I'd also recommend getting rid of the dependency on IGrouping, as you later may need to apply that aggregation elsewhere:
TotalTaxCollected = g.Sum(n => n.TotalTaxCollected),
DetailXml = g.Select(n => n.DetailXml).AggregateTaxXML()
and
public static string AggregateTaxXML(this IEnumerable<string> source) {
// blah-blah-blah
//return something
}
Sources/further reading: MSDN

Related

"The entity or complex type cannot be constructed in a LINQ to Entities query" in Controler

i have this code in my controller
IQueryable<SellingItem> sellingitems = db.SellingItems
.GroupBy(s => new { s.ItemId ,s.Condition,s.ExpInDays})
.Select(si => new SellingItem
{
Item = si.First().Item,
Condition = si.First().Condition,
ExpInDays = si.First().ExpInDays,
Qty = si.Sum(i => i.Qty),
});
and when i try to run it i get
an error
The entity or complex type cannot be constructed in a LINQ to Entities query
now it look to me that my linq query is too complex for entity framework to handle,
so i have 2 workarounds, but i don't like both of them.
1.
load the whole table in memory and make the query like this
2.
use a SQL statement, which will be faster but will not follow the entity framework rules
is there a better way?
-----UPDATE---------------------------------
as it turn out (thanks a lot guys)
i was wrong by stating
now it look to me that my linq query is too complex for entity framework to handle,
and it didn't work because of the fact that i used the same class for the result. i created a new class and now it works amazing!!
You don't need to resort to any of your workarounds to fix that exception per se. The problem is that SellingItem is a class that is part of your Entity Framework model. The reason for this is explained in the comments on this answer.
Either select an anonymous object like so:
IQueryable<SellingItem> sellingitems = db.SellingItems
.GroupBy(s => new { s.ItemId ,s.Condition,s.ExpInDays})
.Select(si => new
{
Item = si.First().Item,
Condition = si.First().Condition,
ExpInDays = si.First().ExpInDays,
Qty = si.Sum(i => i.Qty),
});
Or create an object specifically for the select that you are trying to do:
public class NewClass
{
public ItemClass Item { get;set; }
public ConditionClass Condition { get;set; }
public in ExpInDays { get;set; }
public int Qty { get;set; }
}
Naturally you will need to make sure that the types in this specific class match up to their respective types.
You can then use the new class to do the select:
// Use new class
IQueryable<SellingItem> sellingitems = db.SellingItems
.GroupBy(s => new { s.ItemId ,s.Condition,s.ExpInDays})
.Select(si => new NewClass
{
Item = si.First().Item,
Condition = si.First().Condition,
ExpInDays = si.First().ExpInDays,
Qty = si.Sum(i => i.Qty),
});

Code a SQL projection and mapping at the same time?

This works to carve out a DDL object from an Address object from our database:
public class DDL {
public int? id { get; set; }
public string name { get; set; }
}
List<DDL> mylist = Addresses
.Select( q => new DDL { id = q.id, name = q.name })
.ToList();
However, we'd like to keep our POCO to ViewModel mappings in a single place outside of our MVC controller code. We'd like to do something like this:
List<DDL> mylist = Addresses
.Select( q => new DDL(q)) // <-- constructor maps POCO to VM
.ToList();
But SQL cannot use the constructor function. The object initializer above doesn't use functions to map fields. Of course you could do .AsEnumerable().Select( q => new DDL(q)), but this selects all the fields in SQL (including the data), sends it to C#, then C# carves out the fields we need (terribly inefficient to transfer data we don't need.)
Any suggestions? We happen to be using Entity Framework 6 to pull data.
All you need is to define the expression somewhere and use it. For example, in your ViewModel as a static read-only field.
public class SomeViewModel
{
public static readonly Expression<Func<SomeEntity, SomeViewModel>> Map = (o) => new SomeViewModel
{
id = o.id,
name = o.name
}
public int id { get; set; }
public string name { get; set; }
}
// Somewhere in your controller
var mappedAddresses = Addresses.Select(SomeViewModel.Map);
I personally made myself a little static Mapper that keeps all the maps and use them for me. The maps are declared in a static initializer in all my ViewModels. The result gives me something that feels like AutoMapper, yet doesn't require the lib or the complicated mapping code (but also won't do any magic for you).
I can write something like this:
MyCustomMapper.Map<Entity, ViewModel>(entity);
and it's overloaded to accept IEnumerables, IQueryables and a single ViewModel. I also added overloads with only 1 generic type (the entity) that accept a type parameter. This was a requirement for me.
You can use anonymous types to restrict what to select from the DB and then use those fields to construct your object :
List<DDL> mylist = Addresses
.Select( q => new { id, name })
.AsEnumerable()
.Select(i => new DDL(i.id, i.name) // <- On the Enumerable and not on the Queryable
.ToList();
Are you against using 3rd party libraries? Automapper's QueryableExtensions does exactly what you want.
List<DDL> mylist = Addresses
.Project().To<DDL>()
.ToList();
It even has nice features like being able to filter on the transformed object and that filter being performed server side.
List<DDL> mylist = Addresses
.Project().To<DDL>()
.Where(d => d.name = "Smith") //This gets translated to SQL even though it was performed on DDL.
.ToList();

c# pass class as a parameter

I want to pass a class as parameter.
So what I want to do is the pass a class, for example: "Customer" to a method.
I want to do this because then I can also pass, for example: "Contract" as class to the same method. This way I don't need to make a method for every class.
Side info: I am using EntityFramework 6, MVC 5, Nest 1.0 and ElasticSearch 1.4
The concept is that we place stuff in ElasticSearch and that we then can do a search.
The search query is:
SearchElasticClient.Search<Customer>(body => body
.AllIndices()
.Size(500)
.Query(query => query
.Bool(#bool => #bool
.Must(must => must
.QueryString(qs => qs
.Query("XXXX"))))));
And for contract:
SearchElasticClient.Search<Contract>(body => body
.AllIndices()
.Size(500)
.Query(query => query
.Bool(#bool => #bool
.Must(must => must
.QueryString(qs => qs
.Query("XXXX"))))));
As you can see, if I want to do a search for every type we have, then I need to copy paste this query like 20 times at least.
I don't like copy pasting because the code is not proper and when I need to change it, it will be a lot of work.
So I want to create a method that takes my class as argument or something like that so that I can make a generic method that reuses this block of code.
So for our example:
My (Enitity Framework) classes:
public class Customer{
public int CustomerID {get;set;}
public String CustomerName {get;set;}
}
public class Contract{
public int ContractID {get;set;}
public String ContractName {get;set;}
}
relation(s) between the classes is for me irrelavent so I left them out.
Then in my HomeController I would like something like
public class HomeController : Controller
{
...
public ActionResult Search(String textToSearch)
{
//So here you see that I want to use the same method for both classes.
Customer customer = Helpers.SearchHelper.Search(textToSearch);
Contract contract = Helpers.SearchHelper.Search(textToSearch);
}
}
Then my SearchHelper would be something like:
public static class SearchHelper
{
public static ElasticClient SearchElasticClient
{
get
{
Uri uri = new Uri("http://localhost:9200");
var setting = new ConnectionSettings(uri, "default_INDEX");
return new ElasticClient(setting);
}
}
public static void SearchTest(String textToSearch, MyClass)
{
var test = SearchElasticClient
.Search<Customer>(body => body
.AllIndices()
.Size(500)
.Query(query => query
.Bool(#bool => #bool
.Must(must => must
.QueryString(qs => qs
.Query("XXXX"))))));
}
}
As you can see, now I set my class "Customer" fixed in my code.
I want to replace that with a variable or something.
Now what I have tried:
public static void SearchTest<T>(String textToSearch)
{
var test = SearchElasticClient
.Search<T>(body => body
.AllIndices()
.Size(500)
.Query(query => query
.Bool(#bool => #bool
.Must(must => must
.QueryString(qs => qs
.Query("XXXX"))))));
}
Here I get the compile error: "Cannot convert lambda expression to type 'Nest.ISearchRequest' because it is not a delegate type."
I am not familiar with delegation and how it works and if I can use it, so if delegation is something I need, please provide me enough details.
I also tried:
public static void SearchTest(String textToSearch, Type myClass)
{
var test = SearchElasticClient
.Search<myClass>(body => body
.AllIndices()
.Size(500)
.Query(query => query
.Bool(#bool => #bool
.Must(must => must
.QueryString(qs => qs
.Query("XXXX"))))));
}
Then it gives me the compile error: "The Type or namespace 'myClass' could not be found." I understand why I get this error, so I know that it will be more something like public static void Search(..){..} but I have no idea how to implement it.
I hope this is a better explanation about my problem.
So it is an implemantion of the "Nest" search and I want to avoid copy pasting the search query.
Thanks in advance
I believe what you want to do is make Search generic
public static classToPass Search<classToPass>()
Then use it like this
Test x = Helper.Search<Test>(); //Test = class as definied above
TestTwo y = Helper.Search<TestTwo>();
Make the Search method generic. A generic argument is, more or less, a parameter that is a type, rather than an object.
public static class Helper
{
public static object Search<T>(T classToPass)
{
SearchElasticClient
.Search<T>(body => body
.AllIndices()
.Size(500)
.Query(query => query
.Bool(#bool => #bool
.Must(must => must
.QueryString(qs => qs
.Query("XXX"))))));
}
}

GROUP BY and HAVING clauses in nHibernate QueryOver

I'm trying to write this specific sql query in nHibernate QueryOver language, which I am not very familiar with:
SELECT MessageThreadId FROM MessageThreadAccesses
WHERE ProfileId IN (arr)
GROUP BY MessageThreadId
HAVING COUNT(MessageThreadId) = arr.Count
where arr is a array of integers(user Ids) I'm passing as argument and MessageThreadAccess entity looks like this:
public virtual MessageThread MessageThread { get; set; }
public virtual Profile Profile { get; set; }
....
After reading multiple stack overflow threads and experimenting I got this far with my query (trying to get MessageThread object - it should always be just one or none), but it still doesn't work and I'm not really sure what else to try. The query always seems to be returning the MessageThreadAccess object, but when reading it's MessageThread property it's always NULL.
var access = Session.QueryOver<MessageThreadAccess>()
.WhereRestrictionOn(x => x.Profile).IsIn(participants.ToArray())
.Select(Projections.ProjectionList()
.Add(Projections.Group<MessageThreadAccess>(x => x.MessageThread))
)
.Where(
Restrictions.Eq(Projections.Count<MessageThreadAccess>(x => x.MessageThread.Id), participants.Count)
)
.TransformUsing(Transformers.AliasToBean<MessageThreadAccess>())
.SingleOrDefault();
return Session.QueryOver<MessageThread>()
.Where(x => x.Id == access.MessageThread.Id)
.SingleOrDefault();
Can someone point me in the right direction, or explain what am I doing wrong?
Thanks in advance.
I guess you may try using a DTO for storing the result, instead of trying to fit the result in a MessageThreadAccess, when it is not one (no Profile).
Maybe you can try :
public class MessageThreadCountDTO
{
public MessageThread Thread { get; set; }
public int Nb { get; set; }
}
then
var profiles = new int[] { 1,2,3,4 };
MessageThreadCountDTO mtcDto = null;
var myResult =
_laSession.QueryOver<MessageThreadAccess>()
.WhereRestrictionOn(x => x.Profile.Id).IsIn(profiles)
.SelectList(list =>
list.SelectGroup(x => x.MessageThread).WithAlias(() => mtcDto.Thread).
SelectCount(x => x.MessageThread).WithAlias(() => mtcDto.Nb)
)
.Where(Restrictions.Eq(Projections.Count<MessageThreadAccess>(x => x.MessageThread), profiles.Count()))
.TransformUsing(Transformers.AliasToBean<MessageThreadCountDTO>())
.List<MessageThreadCountDTO>().FirstOrDefault();
would profiles be a Profile[], and not an int[], then the following line :
.WhereRestrictionOn(x => x.Profile.Id).IsIn(profiles)
should be :
.WhereRestrictionOn(x => x.Profile).IsIn(profiles)
Hope this will help

RavenDB workaround for nested LINQ expression

I have simple set of objects stored in RavenDB:
public class Question
{
public string Id { get; set; }
public DateTime CreatedOn { get; set; }
public ICollection<User> Supporters { get; set; }
public ICollection<Answer> Answers { get; set; }
}
public class Answer
{
public string Id { get; set; }
public bool IsOfficial { get; set; }
}
Now I want to query RavenDB to give me set of questions, ordered firstly by number of supporters, next by condition - if a question has any official answer, and in the and, by question creation date. So I've written a query:
var questions = DocumentSession.Query<Question>().AsQueryable();
questions = questions
.OrderByDescending(x => x.Supporters.Count)
.ThenByDescending(x => x.Answers.Any(a => a.IsOfficial)) //EDIT: source of exception
.ThenByDescending(x => x.CreatedOn)
.Take(15);
var result = questions.ToList();
which throws an exception:
System.InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.MethodCallExpressionN' to type 'System.Linq.Expressions.MemberExpression'
The query is logically correct and works, when I use linq-to-objects, and simply add .ToList() to first line:
var questions = DocumentSession.Query<Question>().Tolist().AsQueryable();
// next lines stay unchanged
I don't want to do it because of performance issues (this change forces all questions to be loaded from database into memory before filtering).
How to make this working without performance impact ? Maybe shell I define an index ? How it should looks like then ?
A custom index for your purposes is basically going to be a recreation of your class with extra fields in it (and some logic to support it). It seems like you don't want to have to add more fields to your current class, are you okay with adding more classes to your project?
Here's an example:
public class Question_WithAnyOfficial: AbstractIndexCreationTask<Question>
{
public class Question_WithAnyOfficial()
{
Map = questions => from question in questions
// New Anonymous Type
select new
{
Id = question.Id,
CreatedOn = question.CreatedOn,
Supporters = question.Supporters,
Answers = question.Answers,
AnyOfficial = question.Answers.Where(a => a.IsOfficial).Any()
};
}
}
Then you can query this:
var questions = DocumentSession.Query<Question_WithAnyOfficial>()
.OrderByDescending(x => x.Supporters.Count)
.ThenByDescending(x => x.AnyOfficial)
.ThenByDescending(x => x.CreatedOn)
.Take(15)
.ToList();
Don't forget that you'll have to register the index when your app starts.
Raven can't support calculations like that inside the LINQ query, so this should work (problem clause removed):
var questions = DocumentSession.Query<Question>()
.OrderByDescending(x => x.Supporters.Count)
//.ThenByDescending(x => x.Answers.Any(a => a.IsOfficial))
.ThenByDescending(x => x.CreatedOn)
.Take(15);
var result = questions.ToList();
If you want to include that logic, you need a field on your class called AreAllAnswersOfficial (or something similar). Then you can put that inside the clause:
var questions = DocumentSession.Query<Question>()
.OrderByDescending(x => x.Supporters.Count)
.ThenByDescending(x => x.AreAllAnswersOfficial)
.ThenByDescending(x => x.CreatedOn)
.Take(15);
var result = questions.ToList();
Based on Bear Alexander response, I've done this like that:
public class QuestionByAnyOfficial : AbstractIndexCreationTask<Question, QuestionByAnyOfficial.Result>
{
public class Result
{
public string Id;
public bool AnyOfficial;
public int SupportersCount;
public DateTime CreatedOn;
}
public QuestionByAnyOfficial()
{
Map = questions => from question in questions
select new
{
Id = question.Id,
AnyOfficial = question.Answers.Any(a => a.IsOfficial),
SupportersCount = question.Supporters.Count,
CreatedOn = question.CreatedOn
};
}
}
var questionIds = DocumentSession.Query<QuestionByAnyOfficial.Result, QuestionByAnyOfficial>()
.OrderByDescending(x => x.SupportersCount)
.ThenByDescending(x => x.AnyOfficial)
.ThenByDescending(x => x.CreatedOn)
.Take(NumberOfQuestions)
.Select(x => x.Id);
var questions = DocumentSession.Load<Question>(questionIds);
var result = questions.ToList();
It works and I believe it is more efficient than my original version. If it can be done in any more elegant way, I'd appreciate any ideas. Regards.

Categories