Linq group by using reflection - c#

I have data table "Car" which have 3 cols (owner, carType, colour). My question is how can i make the grouping portion more dynamic by using reflection. my idea is add the grouping col in to array, then use the reflection on the query grouping part. however i was struck at the reflection..
var gcols = new string[] { "owner", "carType" };
var reseult = dt.AsEnumerable()
.GroupBy(x => new
{
carType = x.Field<string>("carType"),
colour = x.Field<string>("colour")
})
.Select(x => new
{
CarType = x.Key.carType,
Colour = x.Key.colour,
count = x.Count()
})
.OrderBy(x => x.CarType).ToList();

If you added this extension method to object:
public static T Field<T>(this object source, string FieldName)
{
var type = source.GetType();
var field = type.GetField(FieldName);
return (T)field.GetValue(source);
}
You'd be able to use the syntax you've posted in your code.
I've not added any safety checking here so it'll need cleaning up, but it'll get you going. Ideally you'd want to check that the type of field is the same as T and a few other checks.

Related

Create a dynamic select function in Enity Framework

I have a question for you regarding the creation of a Dynamic select query in Entity Framework.
I already have a dynamic query for the select based on rights etc. But for each table I get 30+ fields that I have to parse via the .GetType().GetProperties().
Its complex and its quite costly in terms of resource due to the amount of data we have.
I have a service that tells me which fields I should select for each table. I would like to find a way to transform that into the query but I can't find something that is really dynamic.
That is not dynamic but manual:
using (var context = new StackOverflowContext())
{
var posts = context.Posts
.Where(p => p.Tags == "<sql-server>")
.Select(p => new {p.Id, p.Title});
// Do something;
}
I need to say, select only those fields but only the fields with this names.
I have the field list in a list of string but that could be changed.
Could you please help me?
Here is a .Net Fiddle code (made by msbendtsen) that allows to dynamically select columns (properties).
https://dotnetfiddle.net/3IMR1r
The sample is written for linq to objects but it should work with entity frameworks.
The key section is:
internal static IQueryable SelectProperties<T>(this IQueryable<T> queryable, IEnumerable<string> propertyNames)
{
// get propertyinfo's from original type
var properties = typeof(T).GetProperties().Where(p => propertyNames.Contains(p.Name));
// Create the x => expression
var lambdaParameterExpression = Expression.Parameter(typeof(T));
// Create the x.<propertyName>'s
var propertyExpressions = properties.Select(p => Expression.Property(lambdaParameterExpression, p));
// Creating anonymous type using dictionary of property name and property type
var anonymousType = AnonymousTypeUtils.CreateType(properties.ToDictionary(p => p.Name, p => p.PropertyType));
var anonymousTypeConstructor = anonymousType.GetConstructors().Single();
var anonymousTypeMembers = anonymousType.GetProperties().Cast<MemberInfo>().ToArray();
// Create the new {} expression using
var anonymousTypeNewExpression = Expression.New(anonymousTypeConstructor, propertyExpressions, anonymousTypeMembers);
var selectLambdaMethod = GetExpressionLambdaMethod(lambdaParameterExpression.Type, anonymousType);
var selectBodyLambdaParameters = new object[] { anonymousTypeNewExpression, new[] { lambdaParameterExpression } };
var selectBodyLambdaExpression = (LambdaExpression)selectLambdaMethod.Invoke(null, selectBodyLambdaParameters);
var selectMethod = GetQueryableSelectMethod(typeof(T), anonymousType);
var selectedQueryable = selectMethod.Invoke(null, new object[] { queryable, selectBodyLambdaExpression }) as IQueryable;
return selectedQueryable;
}

Find duplicates within multiple lists using linq

I've got 4 classes each inheriting from the same base class. They all have an Id & Path property in common. When my data is loaded from xml, I build 4 lists based on these classes.
I've got the following code which allows me to compare 2 lists:
var list1 = settings.EmailFolders.Select(m => m.Path).ToList().
Intersect(settings.LogPaths.Select(m=>m.Path).ToList()).ToList();
but I'd like
I want to compare the 1st list to the other 3 and find if there are duplicate path.
If none are found, I then want to apply the same logic comparing the second list and compare it to the other 2.
If none are found, I then want to apply the same logic comparing the third list and compare it to the last one.
Please note that I don't want point 1, 2 and 3 in a single process as I'd like to report the relevant error if duplicates are found.
If I re-use the above and add an additional Intersect with an additional list,
var list1 = settings.EmailFolders.Select(m => m.Path).ToList().
Intersect(settings.LogPaths.Select(m=>m.Path).ToList()).
Intersect(settings.SuccessPaths.Select(m=>m.Path).ToList()).
Intersect(settings.FailurePaths.Select(m=>m.Path).ToList()).List();
The above would like applying an and operator between each rather than an or operator which is not what I want. I need to know if any of the Path used in FailurePaths or LogPaths or SuccessPaths are already used in my EmailFolder.Path
UPDATE:
I've updated my question with an answer based on #Devuxer suggestion which is the correct answer but instead of working with string array, I'm basing it on list of classes which all contains a Path property.
List<EmailFolder> emailFolders = LoadEmailFolders();
List<LogPath> logPaths = LoadLogPaths();
List<SuccessPath> successPaths = LoadSuccessPaths();
List<FailurePath> failurePaths = LoadFailurePaths();
var pathCollisions = EmailFolders.Select(a=>a.Path)
.Intersect(LogPaths.Select(b=>b.Path))
.Select(x => new { Type = "LogPath", Path = x })
.Concat(EmailFolders.Select(c=>c.Path)
.Intersect(SuccessPaths.Select(d=>d.Path))
.Select(x => new { Type = "SuccessPath", Path = x }))
.Concat(EmailFolders.Select(e=>e.Path)
.Intersect(FailurePaths.Select(f=>f.Path))
.Select(x => new { Type = "FailurePath", Path = x })).ToList();
Note that in the above example, for simplicity sake, I've just declared my list with a function for each which loading the relevant data but in my case, all the data is deserialized and loaded at once.
I may be slightly misunderstanding your requirements, but it looks like you want something like this:
var emailFolders = new[] { "a", "b", "c" };
var logPaths = new[] { "c", "d", "e" };
var successPaths = new[] { "f", "g", "h" };
var failurePaths = new[] { "a", "c", "h" };
var pathCollisions = emailFolders
.Intersect(logPaths)
.Select(x => new { Type = "Email-Log", Path = x })
.Concat(emailFolders
.Intersect(successPaths)
.Select(x => new { Type = "Email-Success", Path = x }))
.Concat(emailFolders
.Intersect(failurePaths)
.Select(x => new { Type = "Email-Failure", Path = x }));
This results in:
Type | Path
--------------------
Email-Log | c
Email-Failure | a
Email-Failure | c
--------------------
If this is what you were looking for, Intersect was definitely the right way to find the duplicates. I just added some Concat clauses and provided a Type so you could see what type of path collision occurred.
Edit
Well the bullets in your question seem to ask for something different from the last sentence of your question.
If you really want all comparisons, one way to do that is to add more Concat clauses:
var pathCollisions = emailFolders
.Intersect(logPaths)
.Select(x => new { Type = "Email-Log", Path = x })
.Concat(emailFolders
.Intersect(successPaths)
.Select(x => new { Type = "Email-Success", Path = x }))
.Concat(emailFolders
.Intersect(failurePaths)
.Select(x => new { Type = "Email-Failure", Path = x }))
.Concat(logPaths
.Intersect(successPaths)
.Select(x => new { Type = "Log-Success", Path = x }))
.Concat(logPaths
.Intersect(failurePaths)
.Select(x => new { Type = "Log-Failure", Path = x }))
.Concat(successPaths
.Intersect(failurePaths)
.Select(x => new { Type = "Success-Failure", Path = x }));
This doesn't "short-circuit" the way you intended, though (so it will find all the path collisions rather than stop after the first type), but it should at least give you the data you need to generate a report.
You can use the Contains by using the property to compare
var result = list1.Where(x => list2.Contains(x.Path));
It will give all the items from list1 which are also in list2
If you want only the count then use .Count() at the end
Same rule apply to compare other 3 lists

Getting a amount of rows with same month with LINQ from an MVC model using dateTime, is it possible?

I have this need to know how many rows have the same month from a table and I have no idea of how to do it. I thought I'd try some LINQ but I've never used it before so I don't even know if it's possible. Please help me out!
public ActionResult returTest()
{
ViewData["RowsWithSameMonth"] = // I'm guessing I can put some LINQ here?
var returer = from s in db2.ReturerDB select s;
return View(returer.ToList());
}
The ideal would be to get, maybe a two dimensional array with the month in the first cell and the amount of rows from the db in the second?
I'd like the result to be sort of :
string[,] statistics = new string[,]
{
{"2013-11", "5"},
{"2013-12", "10"},
{"2014-01", "3"}
};
Is this doable? Or should I just query the database and do a whole lot of stuff? I'm thinking that I can solve this on my own, but it would mean a lot of ugly code. Background: self taught C# developer at IT-company with 1 years experience of ugly codesmanship and no official degree of any kind.
EDIT
var returer = from s in db2.ReturerDB select s;
var dateRange = returer.ToList();
var groupedData = dateRange.GroupBy(dateRow => dateRow.ToString())
.OrderBy(monthGroup => monthGroup.Key)
.Select(monthGroup => new
{
Month = monthGroup.Key,
MountCount = monthGroup.Count()
});
string test01 = "";
string test02 = "";
foreach (var item in groupedData)
{
test01 = item.Month.ToString();
test02 = item.MountCount.ToString();
}
In debug, test01 is "Namespace.Models.ReturerDB" and test02 is "6" as was expected, or at least wanted. What am I doing wrong?
You can do this:
var groupedData = db2.ReturerDB.GroupBy(r => new { r.Date.Year, r.Date.Month })
.Select(g => new { g.Key.Year, g.Key.Month, Count = g.Count() })
.OrderBy(x => x.Year).ThenBy(x => x.Month);
.ToList();
var result = groupedData
.ToDictionary(g => string.Format("{0}-{1:00}", g.Year, g.Month),
g => g.Count);
Which will give you
Key Value
---------------
2013-11 5
2013-12 10
2014-01 3
(Creating a dictionary is slightly easier than a two-dimensional array)
This will work against a SQL back-end like entity framework of linq-to-sql, because the expressions r.Date.Year and r.Date.Month can be translated into SQL.
with a nod to mehrandvd, here is how you'd achieve this using linq method chain approach:
var dateRange = { // your base collection with the dates};
// make sure you change MyDateField to match your won datetime field
var groupedData = dateRange
.GroupBy(dateRow => dateRow.MyDateField.ToString("yyyy-mm"))
.OrderBy(monthGroup => monthGroup.Key)
.Select(monthGroup => new
{
Month = monthGroup.Key,
MountCount = monthGroup.Count()
});
This would give you the results you required, as per the OP.
[edit] - as requested, example of how to access the newly created anonymous type:
foreach (var item in groupedData)
{
Console.WriteLine(item.Month);
Console.WriteLine(item.MountCount);
}
OR, you could return the whole caboodle as a jsonresult to your client app and iterate inside that, i.e the final line of your view would be:
return Json(groupedData, JsonRequestBehavior.AllowGet);
hope this clarifies.
What you need is grouping.
Considering you have a list of dates a solution would be this:
var dateRows = // Get from database
var monthlyRows = from dateRow in dateRows
group dateRow by dateRow.ToString("yyyy/mm") into monthGroup
orderby monthGroup.Key
select new { Month=monthGroup.Key, MountCount=monthGroup.Count };
// Your results would be a list of objects which have `Month` and `MonthCount` properties.
// {Month="2014/01", MonthCount=24}
// {Month="2014/02", MonthCount=28}

How to access to a field of a dynamic type?

I'm trying to wrap the results of a query with a class called QueryResultViewModel from a list of dynamic objects retrieved by LINQ. These contain a integer field called Worked. I should not use a non-dynamic type because depending on the query it has other fields. I tried that:
var query = new HoursQuery( .. parameters .. );
this.Result = new ObservableCollection<QueryResultViewModel>(
query.Execute().Select( x => new QueryResultViewModel( x.Worked )));
But I got "'object' does not contain a definition for 'Worked'" and I don't know If it can be fixed without changing query's return type.
The Execute code may be useful too:
var res = some_list.GroupBy(a => new { a.Employee, a.RelatedTask, a.Start.Month })
.Select(g => new { K = g.Key, Worked = g.Sum(s => s.Duration.TotalHours) });
EDIT: This worked great but maybe it's not very elegant.
public class HQueryDTO
{
public double Worked;
public object K;
}
public IEnumerable<dynamic> Execute()
{
var list = base.Execute();
return res = list.GroupBy(a => new { a.Employee, a.RelatedTask } )
.Select(g => new HQueryDTO { K = g.Key, Worked = g.Sum(s => s.Duration.TotalHours) });
}
Now that the result has a type it can be returned dynamic.
I'm assuming you get that error at compile-time, in which case simply introduce dynamic via a cast:
.Select(x => new QueryResultViewModel( ((dynamic)x).Worked ))
I assume that the signature of Execute is something like object Execute(). If you return dynamic, it should work.

c# More elegant way to assign array to a list?

I have this:
// Load changelog types
ChangeLogType[] Types = ChangeLogFunctions.GetAllChangelogTypes();
foreach(ChangeLogType Rec in Types){
ListItem N = new ListItem();
N.Text = Rec.Type;
N.Value = Rec.ID.ToString();
LstChangeLogType.Items.Add(N);
}
It calls a function that returns an array of ChangeLogTypes, and then adds each one into a list control. Is there a more elegant way of doing this? I feel I'm repeating code each time I do this or something similar.
Yup, LINQ to Objects is your friend:
var changeLogTypes = ChangeLogFunctions.GetAllChangelogTypes()
.Select(x => new ListItem {
Text = x.Type,
Value = x.ID.ToString() })
.ToList();
The Select part is projecting each ChangeLogType to a ListItem, and ToList() converts the resulting sequence into a List<ListItem>.
This is assuming you really wanted a new list with all these entries. If you need to add the results to an existing list, you'd do that without the ToList call, but calling AddRange on an existing list with the result of the Select call.
It's well worth learning more about LINQ in general and LINQ to Objects in particular - it can make all kinds of things like this much simpler.
var range = Types.Select(rec =>
new ListItem { Text = rec.Type, Value = rec.ID.ToString() });
LstChangeLogType.AddRange(range);
Linq?
LstChangeLogType.Items = Types.Select(x => new ListItem()
{ Text = x.Type, Value = x.ID.ToString() }).ToList();
using System.Linq;
var items = Types
.Select (rec => ListItem
{
Text = Rec.Type;
Value = Rec.ID.ToString();
}
LstChangeLogType.Items.AddRange(items);
Using some LINQ extension methods:
LstChangeLogType.AddItems.AddRange(
Types.Select(t =>
new ListItem() { Text = t.Type, Value = t.ID.ToString() }).ToArray());

Categories