How do I get the value of one of the new properties out of query in the DoIt function below?
public object GetData()
{
var table = GetDataTable();
var view = table.DefaultView;
//..... more code
var query = from row in view.ToTable().AsEnumerable()
group row by row.Field<string>("ShortName") into grouping
select new
{
ShortName = grouping.Key,
SCount = grouping.Sum( count => count.Field<int>("ProfCount")),
DisplayText = string.Empty
};
return query;
}
// this code doesn't work
public void DoIt()
{
var result = GetData();
string shortName = result.ShortName;
}
Thanks!
Anonymous types are not called anonymous types for nothing. So:
Define a type with a name (6 additional lines of code):
public class Foo
{
public string ShortName { get; set; }
public int SCount { get; set; }
public string DisplayText { get; set; }
}
Now modify your GetData signature to (0 additional lines of code):
public IEnumerable<Foo> GetData()
And your LINQ query to (3 additional characters, or a couple more if you choose more meaningful name):
var query =
from row in view.ToTable().AsEnumerable()
group row by row.Field<string>("ShortName") into grouping
select new Foo
{
ShortName = grouping.Key,
SCount = grouping.Sum( count => count.Field<int>("ProfCount")),
DisplayText = string.Empty
};
You're returning an anonymous type (via select new {}), which is only valid in the local scope. You need to create a concrete type and return that from your function rather than object.
public SomeClass GetData()
{
var table = GetDataTable();
var view = table.DefaultView;
//..... more code
var query = from row in view.ToTable().AsEnumerable()
group row by row.Field<string>("ShortName") into grouping
select new SomeClass
{
ShortName = grouping.Key,
SCount = grouping.Sum( count => count.Field<int>("ProfCount")),
DisplayText = string.Empty
};
return query;
}
// this code doesn't work
public void DoIt()
{
var result = GetData();
string shortName = result.ShortName;
}
public class SomeClass
{
public string ShortName { get; set; }
public int SCount { get; set; }
public string DisplayText { get; set; }
}
Well, DoIt has no idea that result has a property named ShortName because its typed as an object. You could create a concrete class that holds the results, use reflection, or use dynamic. Note that either way, GetData is really returning an IEnumerable<T> where T is currently an anonymous type.
Using a concrete class:
public class Foo {
public string ShortName { get; set; }
public int SCount { get; set; }
public string DisplayText { get; set; }
}
public IEnumerable<Foo> GetData() {
var table = GetDataTable();
var view = table.DefaultView;
//..... more code
var query = from row in view.ToTable().AsEnumerable()
group row by row.Field<string>("ShortName") into grouping
select new Foo
{
ShortName = grouping.Key,
SCount = grouping.Sum( count => count.Field<int>("ProfCount")),
DisplayText = string.Empty
};
return query;
}
public void DoIt() {
var result = GetData();
foreach(var item in result) {
Console.WriteLine(item.ShortName);
}
}
Using reflection:
public IEnumerable GetData() {
var table = GetDataTable();
var view = table.DefaultView;
//..... more code
var query = from row in view.ToTable().AsEnumerable()
group row by row.Field<string>("ShortName") into grouping
select new Foo
{
ShortName = grouping.Key,
SCount = grouping.Sum( count => count.Field<int>("ProfCount")),
DisplayText = string.Empty
};
return query;
}
public void DoIt() {
var result = GetData();
PropertyInfo property = result.First().GetType().GetProperty("ShortName");
foreach(var item in result) {
string shortName = property.GetValue(item, null);
Console.WriteLine(shortName);
}
}
You can't without using reflection. Since it is an anonymous type, you cannot cast to it in the DoIt() method either, since the type name is not known at compile time.
This gives me what I need:
public object GetData()
{
var table = GetDataTable();
var view = table.DefaultView;
//..... more code
var query = from row in view.ToTable().AsEnumerable()
group row by row.Field<string>("ShortName") into grouping
select new Object[]
{
grouping.Key,
grouping.Sum( count => count.Field<int>("ProfCount")),
string.Empty
};
return query;
}
public void DoIt()
{
// Note: Pretend that GetData returned only one result
object[] result = GetData() as object[];
var shortName = result[0];
}
Related
I'm new to LINQ and I'm trying to group a list by two columns and use the count aggregate function but I'm not sure how to write this query properly.
Here is my class
public class Result
{
public string? Type { get; set; }
public int Age { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public int Count { get; set; }
}
First I read some data from a dataTable and add it to a list of Result without Count property
List<Result> list = new();
foreach (DataRow row in dataTable.Rows)
{
list.Add(new Result
{
Type =row["Type"].ToString(),
Age = int.Parse(row["Age"].ToString()),
Name = row["Name"].ToString(),
Description = row["Description"].ToString(),
});
}
Now I want to group by Age and Type, I wrote this query and it returns the right result but I'm wondering if there is another cleaner way to write this instead of using Select().FirstOrDefault() ?
IEnumerable<Result> myResult = list.GroupBy(x => new { x.Age, x.Type }).Select(gr =>
new Result
{
Age = gr.Key.Age,
Type = gr.Key.Type,
Name = gr.Select(x => x.Name).FirstOrDefault(),
Description = gr.Select(x => x.Description).FirstOrDefault(),
Count = gr.Count()
}).ToList();
You can try to use FirstOrDefault()?. to make it simple which use Null-conditional
IEnumerable<Result> myResult = list.GroupBy(x => new { x.Age, x.Type }).Select(gr =>
new Result
{
Age = gr.Key.Age,
Type = gr.Key.Type,
Name = gr.FirstOrDefault()?.Name,
Description = gr.FirstOrDefault()?.Description,
Count = gr.Count()
}).ToList();
At the beginning I am aware that there are similar questions, but mine is a little bit different.
I implemented a function that allows the user to select the columns he wants to see.
I've created a stored procedure that gets all column names from the UserColumns table, creates a dynamic sql query and then runs the exec (#command) query. The functionality described above works very well, but there are more requirements that I can't handle this way.
There is TasksViewModel:
public class TasksViewModel
{
public List<Dictionary<List<string>, List<List<object>>>> Tasks { get; set; }
public List<UserDefaultStatusesViewModel> UserStatuses { get; set; }
public List<ZgloszenieStatus> TaskStatuses { get; set; }
public TasksViewModel()
{
}
}
Tasks is filled by stored procedure that runs SELECT x,y,z... FROM table... query.
I'm using this method:
private static IEnumerable<Dictionary<List<string>, List<List<object>>>> Read(DbDataReader reader)
{
var dict = new Dictionary<List<string>, List<List<object>>>();
var cols = new List<string>();
for (int temp = 0; temp < reader.FieldCount; temp++)
{
cols.Add(reader.GetName(temp));
}
var items = new List<List<object>>();
while (reader.Read())
{
var tmp = new List<object>();
for (int i = 0; i < reader.FieldCount; i++)
{
tmp.Add(reader.GetValue(i));
}
items.Add(tmp);
}
dict.Add(cols, items);
foreach (var item in dict)
{
}
yield return dict;
}
I find this very overcomplicated, but at the moment I have no idea if there is another way to do this.
I'm using Entity Framework in my application.
Imagine that I'm using List<Tasks> instead of List<Dictionary<List<string>, List<List<object>>>>. Tasks is database table.
public class Tasks
{
public int ID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime Date { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
User wants to see only ID,Title,Description columns, so the UserColumns table looks like this:
UserId | ColumnName
1 | ID
2 | Title
3 | Description
Is there a way to select specific columns from List<Tasks> based on UserColumns table using Entity Framework ?
You can create the lambda for Column List dynamically
static Func<Tasks, Tasks> CreateSelect(string[] columns)
{
var parameterExpression = Expression.Parameter(typeof(Tasks), "p");
var newExpression = Expression.New(typeof(Tasks));
var bindings = columns.Select(o => o.Trim())
.Select(o =>
{
var pi = typeof(Tasks).GetProperty(o);
var memberExpression = Expression.Property(parameterExpression, pi);
return Expression.Bind(pi, memberExpression);
}
);
var memberInitExpression = Expression.MemberInit(newExpression, bindings);
var lambda = Expression.Lambda<Func<Tasks, Tasks>>(memberInitExpression, parameterExpression);
return lambda.Compile();
}
and create a LINQ query based on that lambda (columnNameList array is rows from UserColumns table)
static void Foo()
{
var columnNameList = new string[] { "ID", "Title", "Description" };
var tasksList = new List<Tasks>
{
new Tasks{ ID=1, Title="T1", FirstName="F1", LastName="L1", Description="D1", Date=DateTime.UtcNow },
new Tasks{ ID=2, Title="T2", FirstName="F2", LastName="L2", Description="D2", Date=DateTime.UtcNow }
};
var tasks = tasksList.Select(CreateSelect(columnNameList)).FirstOrDefault();
}
I hope that answers your question.
So, I'm using Syncfusion controls and I've a MultipleSelectionCombobox where user can filter multiple arguments.
I have a query which will load a list based on parameters query.
So, first, I have a class to hold my values;
public class Orders
{
public int ID { get; set; }
public string OrderNum { get; set; }
public string Status { get; set; }
public DateTime Date { get; set; }
}
Then, the query:
public IEnumerable<Orders> LoadData()
{
var ctx = new DbContext();
var query = (from o in ctx.tblOrders.AsQueryable()
select new Orders
{
ID = o.OrderID,
OrderNum = o.OrderNum.ToString(),
Status = o.OrderStatus,
Date = o.OrderDate
});
if(CmbOrderStatus.SelectedItems != null)
{
List<string> list = new List<string>();
foreach (SelectedItems obj in CmbOrderStatus.SelectedItems)
{
list.Add(obj.ToString());
}
for(int i = 0; i < list.Count; i++)
{
var value = list[i];
query = query.Where(p => p.Status == value);
}
}
return query.ToList();
}
So, in Database in have many Orders and many OrderStatus, like "Opened", "Delayed", "Closed".
So, if I filter in CmbOrderStatus "Opened" and "Delayed", I get nothing! If only one is selected, I get nothing!
Any help here?
Thanks
The code use only last filter.
Try this:
public IEnumerable<Orders> LoadData()
{
var ctx = new DbContext();
var query = (from o in ctx.tblOrders.AsQueryable()
select new Orders
{
ID = o.OrderID,
OrderNum = o.OrderNum.ToString(),
Status = o.OrderStatus,
Date = o.OrderDate
});
if(CmbOrderStatus.SelectedItems != null)
{
List<string> list = new List<string>();
foreach (SelectedItems obj in CmbOrderStatus.SelectedItems)
{
list.Add(obj.ToString());
}
query = query.Where(p => list.Contains(p.Status));
}
return query.ToList();
}
I have a collection of filter criteria objects with each criteria having a name (data object property name) and value property. I have another collection (my data objects) and I need to filter this collection to return data objects which matches the filter criteria.The properties are string values so there is no worry on the type. How can I do this?
Below is the code:
public class FilterCriteria
{
public string ColumnName { get; set; }
public string ColumnValue { get; set; }
}
public class DataObject
{
public string Name { get; set; }
}
public static void Match()
{
var criteria1 = new FilterCriteria() {ColumnName = "Name", ColumnValue = "abc"};
var criteria2 = new FilterCriteria() { ColumnName = "Name", ColumnValue = "xyz" };
var criteriaCollection = new List<FilterCriteria> {criteria1, criteria2};
var data1 = new DataObject() {Name = "xyz"};
var data2 = new DataObject() { Name = "abc" };
var data3 = new DataObject() { Name = "def" };
var dataCollection = new List<DataObject> {data1, data2, data3};
//filter datacollection by the criterias, match any data object with Name property equal to column value
//After the matching I will get the result as data1 & data2.
}
Thanks,
-Mike
NOTE:The FilterCriteria will be serialized to disk using xml serialization.
To get property from its name stored in string you have to use Reflection, for example:
DataObject someDataObject = ...;
typeof(DataObject).GetProperty("SomePropertyName").GetValue(someDataObject)
Then, combining it with LINQ:
var filtered = dataCollection.Where(obj =>
criteriaCollection.Any(cond => obj.GetType()
.GetProperty(cond.ColumnName)
.GetValue(obj)
.Equals(cond.ColumnValue)))
.ToList();
I have the below class and linq query I am using to populate a grid!
The Title is the same for every row returned. What I am trying to do is populate mString with the distinct Title from the query so I can bind it to a seperate textblock.
I probably didnt need to show all the code, but maybe it will help. How can I show the distinct Title.
public class Items
{
public int Id { get; set; }
public string Details { get; set; }
public string Title { get; set; }
public int NewNumber { get; set; }
}
private ObservableCollection<Items> mItem = new ObservableCollection<Items>();
private string mString = string.Empty;
public string SpecTitle
{
get { return mString; }
}
public ObservableCollection<Items> GetItems
{
get { return mItem; }
}
Here is the linq query
var results = (from z in mContext.View
orderby z.ItemNumber ascending
where z.ItemId == mId
select new Items()
{
Id = z.ItemId,
Details = z.Details,
Title = z.ItemTitle,
NewNumber = z.ItemNumber
});
List<Items> mNewItems = results.ToList();
mItem.Clear();
mNewItems.ForEach(y => mItem.Add(y));
var titleList = mNewItems.Select(i => i.Title).Distinct().ToList();
Converting my comment into an answer:
just do Items.Select(x => x.Title).Distinct();.
There is an additional library called moreLinq https://code.google.com/p/morelinq/ that has an extenction distinctby that you can you to distinct based on the given key.
it would as simle as this
var results = (from z in mContext.View
orderby z.ItemNumber ascending
where z.ItemId == mId
select new Items()
{
Id = z.ItemId,
Details = z.Details,
Title = z.ItemTitle,
NewNumber = z.ItemNumber
}).DistinctBy(c=>c.Title).ToList();
You can implement your custom comparer for distinct:
public class ItemsComparer : IEqualityComparer<Items>
{
public bool Equals(Items x, Items y)
{
return x.Title == y.Title;
}
public int GetHashCode(Items obj)
{
return obj.Title.GetHashCode();
}
}
then just use
var titleList = mNewItems.Distinct(new ItemsComparer()).Select(t=>t.Items);