GroupBy with dyanmic multiple fields in Linq - c#

I have a Datatable result. I want to group by with some of the fields may be one or two or three. The main fact is developers can't know the numbers of fields clients want to group. So, for that case, I find some solutions about putting List parameters to group by fields. But can't find the proper one yet. Is there anybody who could help me with it? I was stuck with this issue for three days.

Use reflection to dynamically group your DB-rows:
//something like:
var props = typeof(Foo).GetProperties().Where( x=> x.Name.Equals("MyField");
var result = rows.GroupBy( x => prop.GetValue( x ) );
For multiple fields, loop over them and apply the GroupBy to the result of the last iteration.

I got it by using the groupby in Dynamic LinQ.
Like this
var qfields = string.Join(",", stringarray.Select(x => "it[\"" + x + "\"] as " + x));
var collVal = datatableresult.AsQueryable()
.GroupBy("new (" + qfields + ")", "it")
.Select("new (it.Key as Key,it as Data)");
This code group by the data table successfully but I still need to do the order by with multiple columns.

Related

Equivalent Sql group by to Linq to get value and text properties

I've been spending quite a long time trying to make a Linq query with a group by in it, just to return a List for a SelectList with the common Text an Value properties.
I think and still have hope that it does not have to be as difficult as it looks, and i prefer to think that is me and my tired brain sometimes, but honestly i gave up.
I've been able to achieve what i wanted with raw sql but, it's just simple curiosity because I deny to think Linq is that much of a time waster...
So please if someone out there, who knows how to do this in a Linq lambda query, please illuminate my tired brain...
What i got with raw sql and want with linq lambda (it works perfectly, took me 2 minutes and I can populate my MultiSelectFor nicely):
string sql = " select [AlphabeticCode] as 'Value', concat([AlphabeticCode], ' - ', Currency) as Text from [table] " +
" group by AlphabeticCode, Currency; ";
var currency = db.Database
.SqlQuery<Models.myViewModels.LinqToSql.SelectListViewModel>(sql)
.ToList();
model.currency = new SelectList(currency, "Value", "Text");
What i thought i was doing right, made me spend a lonnnng time and ended up giving me a collection inside my text property... not what i wanted at all... (I know is not correct, i tried quite a few more options), but this one kinda made sense to me, please, give a reason to keep working with this weird Linq thing...
var cc = db.table.GroupBy(item => item.Currency)
.Select(group =>
new
{
Value = group.Key,
Text = group.Select(m => m.Currency).Take(1), // here is where i want just ONE string that is the currency name
}
)
.ToList();
Thank you so much to whoever takes some time to read my frustrations...
I am not sure if this is what you need but I think this will make the same query as your SQL
var items = from item in table
group item by new { item.AlphabeticCode, item.Currency } into g
select new { Value = g.Key.AlphabeticCode, Text = g.Key.AlphabeticCode + " - " + g.Key.Currency };
//Fluent or inline
table.
GroupBy(i => new { i.AlphabeticCode, i.Currency }).
Select(g => new { Value = g.Key.AlphabeticCode, Text = g.Key.AlphabeticCode + " - " + g.Key.Currency });

In Entity Framework 5 CodeFirst, how to create a query that will return when any pair of values are found?

I have an unknown number of complex keys passed into my function at runtime. They will be structured like this:
var keys = new List<List<string>>
{
new List<string> { "1", "a" },
new List<string> { "2", "b" },
new List<string> { "3", "c" }
};
The inner list will always have two values. The outter list could have n values. I am attempting to query a table where records match any of the pairs in the List. I tried this query like this:
var filtered =
dataContext.T.Where(
s => keys.Any(k =>
k[0] == s.Column0
&& k[1] == s.Column1));
At this point, LinqToEntities fails because it seems that linq is unable to process lists (or arrays?) inside an .Any() method.
This is the error I get when I run this code:
"LINQ to Entities does not recognize the method 'System.String get_Item(Int32)' method, and this method cannot be translated into a store expression."
So my question is, how can I query for records that match any pair of values in the lists? I can change anything about the structure as long as I can query for any in a set of pairs.
Thanks for any guidance.
Don't think you can use any enumerable like that in linq 2 entities.
One workaround would be to concatenate your pairs, with a "should never appear" string in the middle.
var concatenatedkeys = keys.Select(m => m[0] + "~" + m[1]).ToList();
var filtered =
dataContext.T.Where(s => concatenatedKeys.Contains(
s.Column0 ?? string.Empty +
"~" +
s.Column1));
The answer does solve the problem that I posed in the question. However, I ran into a similar issue once I needed to compare values across linked entities.
The concatenation solution ran into problems with this scenario:
var filtered =
dataContext.T.Where(s => concatenatedKeys.Contains(
s.AnotherEntity.Column0 ?? string.Empty +
"~" +
s.AnotherEntity.Column1));
To solve this, I ended up using LinqKit to create the .Where() expression using the PredicateBuilder.

LINQ - Comparing a List of strings against a colon separated field

Is it possible to do a LINQ where clause and split a varchar field on a : into a collection and compare this to another collection to see if any values match.
For example, we have a List<string> called AllowedHolders which contains ARR ACC etc.. however the field in the database (which unfortunately we cannot change) is a varchar but has values separated by a colon: i.e. ":ARR:ACC:"
What we would like to do is write a LINQ where clause which can check if any of the AllowedHolders appear in the field. As we wish to restrict the results to only bring back records where the field contains a value in the AllowedHolders collection.
We have done the following where the field only contains a single value
searchResults = searchResults.Where(S => searchParams.AllowedBusinessAreas.Contains(S.SIT_BusinessArea));
But the following will not work because SIT_HolderNames contains values separated by a colon:
searchResults = searchResults.Where(S => searchParams.AllowedHolders.Contains(S.SIT_HolderName)
Any ideas would be much appreciated. If you need me to explain anything further please let me know.
Andy
Use Intersect(), Any() and String.Split().
searchResults = searchResults.Where(s => searchParams.AllowedHolders.Intersect(S.SIT_HolderName.Split(':')).Any());
For example:
":ACC:TEST::ARR:".Split(':') -> string[] { "", "ACC", "TEST", "", "ARR", "" };
You can notice the empty strings, if you don't want to take them into account use String.Split(char[], StringSplitOptions.RemoveEmptyEntries):
":ACC:TEST::ARR:".Split(new char[] {':'}, StringSplitOptions.RemoveEmptyEntries) -> string[] { "ACC", "TEST", "ARR" };
UPDATE
You will have to fetch the data before calling String.Split() using ToList().
searchResults = searchResults.ToList().Where(s => searchParams.AllowedHolders.Intersect(S.SIT_HolderName.Split(':')).Any());
If the data in searchResults is too big what you can do is to fetch only a primary key and the SIT_HolderName.
var keys = searchResults.Select(s => new { Key = s.SIT_PKey, SIT_HolderName = s.SIT_HolderName })
.ToList()
.Where(s => searchParams.AllowedHolders.Intersect(s.SIT_HolderName.Split(':')).Any())
.Select(s => s.Key)
.ToList();
searchResult = searchResults.Where(s => keys.Contains(s.SIT_PKey));
I don't know what can be the performances of the above query. Otherwise, you can try with a Join():
searchResult = searchResults.Join(keys, s => s.SIT_PKey, key => key, (s, key) => s);
Maybe you can use:
searchResults = searchResults.Where(S => searchParams.AllowedHolders
.Any(H => S.SIT_HolderName.Contains(H))
);
or
searchResults = searchResults.Where(S => searchParams.AllowedHolders
.Any(S.SIT_HolderName.Contains)
);
As pointed out by the first comment, this only works if no holder name contains another holder name as a substring. I was implicitly assuming that all your holder names were three-letter strings like ARR and ACC. If this is not the case, consider using (":" + H + ":"), or find a more safe solution.
Edit: Just for completeness, here are two versions with colons prepended and appended:
// OK if some name is contained in another name as a substring
// Requires colon before the first and after the last name
searchResults = searchResults.Where(S => searchParams.AllowedHolders
.Any(H => S.SIT_HolderName.Contains(":" + H + ":"))
);
And:
// OK if some name is contained in another name as a substring
// Ugly checks included to handle cases where no colons are present in the extreme ends
searchResults = searchResults.Where(S => searchParams.AllowedHolders
.Any(H => S.SIT_HolderName.Contains(":" + H + ":") || S.SIT_HolderName.StartsWith(H + ":") || S.SIT_HolderName.EndsWith(":" + H) || S.SIT_HolderName == H)
);
If in the DB column's values the separators are indeed in the format:
:AAA:BBB:CCC:DDD:
and not just (please note the first and last character!)
AAA:BBB:CCC:DDD
then you may perform a LIKE lookup:
select .... from ... where column LIKE '%:BBB:%'
which translates into LINQ:
var idWithColons = ":" + "BBB" + ":";
from ... where .... column.Contains(idWithColons)
for many possible IDS, you'd have to produce:
select .... from ... where column LIKE '%:BBB:%' OR column LIKE '%:DDD:%' OR ..
which translates into LINQ:
var idWithColons = ":" + "BBB" + ":";
var idWithColons2 = ":" + "DDD" + ":";
from ... where .... column.Contains(idWithColons) or column.Contains(idWithColons2)
But that's good only for small number of alternatives. For unknown list of IDs, you can try to rewrite it as a dynamically built filter, but that's not so easy if you are not familiar with Expression<Func<>>.. Anyways, searching via LIKE is not so good idea anyways.. but that's not many other options :/
otherwise, well, that's unpretty.. you could probably prepare a scalar-valued function on the sql server and register it somehow in your linq provider, but I dont think it's worth it..
EDIT:
building where-clause dynamically is explained ie. here http://www.albahari.com/nutshell/predicatebuilder.aspx - look for the PredicateBuilder. The builder is actually generic and will be directly usable, but you still will have to write the small loop that concatenates OR-LIKE by yourself. I think the article is well written enough, drop me anote if you find any problems. except for the performance. LIKE %% is not fast.

Lambda for summing shorthand

I have the following LINQ query:
var q = from bal in data.balanceDetails
where bal.userName == userName && bal.AccountID == accountNumber
select new
{
date = bal.month + "/" + bal.year,
commission = bal.commission,
rebate = bal.rebateBeforeService,
income = bal.commission - bal.rebateBeforeService
};
I remember seeing a lambda shorthand for summing the commission field for each row of q.
What would be the best way of summing this? Aside from manually looping through the results?
It's easy - no need to loop within your code:
var totalCommission = q.Sum(result => result.commission);
Note that if you're going to use the results of q for various different calculations (which seems a reasonable assumption, as if you only wanted the total commission I doubt that you'd be selecting the other bits) you may want to materialize the query once so that it doesn't need to do all the filtering and projecting multiple times. One way of doing this would be to use:
var results = q.ToList();
That will create a List<T> for your anonymous type - you can still use the Sum code above on results here.

Numbering the rows per group after an orderby in LINQ

Say you have columns AppleType, CreationDate and want to order each group of AppleType by CreationDate. Furthermore, you want to create a new column which explicitly ranks the order of the CreationDate per AppleType.
So, the resulting DataSet would have three columns, AppleType, CreationDate, OrderIntroduced.
Is there a LINQ way of doing this? Would I have to actually go through the data programmatically (but not via LINQ), create an array, convert that to a column and add to the DataSet? I have there is a LINQ way of doing this. Please use LINQ non-method syntax if possible.
So are the values actually appearing in the right order? If so, it's easy - but you do need to use method syntax, as the query expression syntax doesn't support the relevant overload:
var queryWithIndex = queryWithoutIndex.Select((x, index) => new
{
x.AppleType,
x.CreationDate,
OrderIntroduced = index + 1,
});
(That's assuming you want OrderIntroduced starting at 1.)
I don't know offhand how you'd then put that back into a DataSet - but do you really need it in a DataSet as opposed to in the strongly-typed sequence?
EDIT: Okay, the requirements are still unclear, but I think you want something like:
var query = dataSource.GroupBy(x => x.AppleType)
.SelectMany(g => g.OrderBy(x => x.CreationDate)
.Select((x, index ) => new {
x.AppleType,
x.CreationDate,
OrderIntroduced = index + 1 }));
Note: The GroupBy and SelectMany calls here can be put in query expression syntax, but I believe it would make it more messy in this case. It's worth being comfortable with both forms.
If you want a pure Linq to Entities/SQL solution you can do something like this:
Modified to handle duplicate CreationDate's
var query = from a in context.AppleGroup
orderby a.CreationDate
select new
{
AppleType = a.AppleType,
CreationDate = a.CreationDate,
OrderIntroduced = (from b in context.AppleGroup
where b.CreationDate < a.CreationDate
select b).Count() + 1
};

Categories