Can I restructure the following into a more compact linq query, ideally without the introduction of a helper function?
var revPerUnitChanges =
from row in this.DataTable.GetChanges(DataRowState.Modified).AsEnumerable()
let field = "Rev/Unit"
select new {
Field = field,
From = row.Field<decimal>(field, DataRowVersion.Original),
To = row.Field<decimal>(field, DataRowVersion.Current),
ItemIds = row.Field<string>("ItemIds"),};
var costPerUnitChanges =
from row in this.DataTable.GetChanges(DataRowState.Modified).AsEnumerable()
let field = "Cost/Unit"
select new {
Field = field,
From = row.Field<decimal>(field, DataRowVersion.Original),
To = row.Field<decimal>(field, DataRowVersion.Current),
ItemIds = row.Field<string>("ItemIds"), };
var numUnitsChanges =
from row in this.DataTable.GetChanges(DataRowState.Modified).AsEnumerable()
let field = "Units"
select new {
Field = field,
From = row.Field<decimal>(field, DataRowVersion.Original),
To = row.Field<decimal>(field, DataRowVersion.Current),
ItemIds = row.Field<string>("ItemIds"), };
var changes =
revPerUnitChanges
.Concat(costPerUnitChanges
.Concat(numUnitsChanges))
.Where(c => c.From != c.To);
Start out by creating a helper class to hold onto the data. (Your code doesn't have any problems using anonymous types, but if you want to refactor sections into methods it'll be much easier with a named class.)
public class MyClass //TODO give better name
{
public MyClass(DataRow row, string field) //You could have a public static generate method if it doesn't make sense for this to be a constructor.
{
Field = field;
From = row.Field<decimal>(field, DataRowVersion.Original);
To = row.Field<decimal>(field, DataRowVersion.Current);
ItemIds = row.Field<string>("ItemIds");
}
public string Field { get; set; }
public decimal From { get; set; }
public decimal To { get; set; }
public string ItemIds { get; set; }
}
Now that we have that out of the way the query is fairly straightforward.
var changes = dataTable.GetChanges(DataRowState.Modified)
.AsEnumerable()
.Select(row => new[]{ //create 3 new items for each row
new MyClass(row, "Rev/Unit"),
new MyClass(row, "Cost/Unit"),
new MyClass(row, "Units"),
})
.SelectMany(item => item) //flatten the inner array
.Where(item => item.From != item.To);
Related
I have below class structure and a list of CollectionInstance
public class CollectionInstance
{
public string Name { get; set; }
public List<CollectionProperty> CollectionProperties { get; set; }
}
public class CollectionProperty
{
public string Name { get; set; }
public object Value { get; set; }
public string DataType { get; set; }
}
Here is list of CollectionInstance. Currently it has only two data types double and string, but I have more data types
var lstCollectionInstances = new List<CollectionInstance>
{
new CollectionInstance
{
Name = "A",
CollectionProperties = new List<CollectionProperty>
{
new CollectionProperty {Name = "P1", Value = 10, DataType = "Double"}
}
},
new CollectionInstance
{
Name = "A",
CollectionProperties = new List<CollectionProperty>
{
new CollectionProperty {Name = "P2", Value = "H1", DataType = "String"}
}
},
new CollectionInstance
{
Name = "B",
CollectionProperties = new List<CollectionProperty>
{
new CollectionProperty {Name = "P1", Value = 20, DataType = "Double"}
}
},
new CollectionInstance
{
Name = "B",
CollectionProperties = new List<CollectionProperty>
{
new CollectionProperty {Name = "P2", Value = "H2", DataType = "String"}
}
},
};
Now my goal to fetch all the different data type and filter list of CollectionInstance based on the data type. May be a dictionary or could be other collection as well, where I should store data type as key and filtered CollectionInstance as a value.
I tried below, but what could be the best way?
var dictionary = new Dictionary<string, List<CollectionInstance>>();
var dataTypesGroups = lstCollectionInstances
.SelectMany(x => x.CollectionProperties).GroupBy(x => x.DataType);
foreach (var dataType in dataTypesGroups)
{
dictionary.Add(dataType.Key, GetFilterData(lstCollectionInstances, dataType.Key));
}
private static List<CollectionInstance> GetFilterData(IEnumerable<CollectionInstance> lst, string dataType)
{
return lst.Where(x => x.CollectionProperties.Any(y => y.DataType == dataType)).ToList();
}
You could keep reference to parent CollectionInstance when grouping and reuse that when selecting results:
lstCollectionInstances
.SelectMany(x => x.CollectionProperties, (i, c) => new {CollectionInstance = i, CollectionProperty = c})
.GroupBy(x => x.CollectionProperty.DataType)
.ToDictionary(c => c.Key, c => c.Select(d => d.CollectionInstance) )
UPD
here we leverage this overload of .SelectMany(). So Instead of List<CollectionProperty> you end up having List<Tuple<CollectionInstance,CollectionProperty>> (well, i opted for anonymous type, but this does not matter much). You basically enhance each child object with reference to its parent. And since all these are just references - you don't trade a lot of memory for having it.
And when you group it - you get an option to not select the CollectionProperty, but rather the parent object directly.
I hope this makes sense
Build a dictionary that for each data type stores a list of instances with a property of the key data type.
var result = instances
.SelectMany(x => x.Properties)
.Select(x => x.DataType)
.Distict()
.ToDictionary(x => x, x => GetInstancesWithPropertyOfType(x, instances));
Given the following is defined:
public class Instance
{
public string Name { get; set; }
public List<Property> Properties { get; set; }
}
public class Property
{
public string Name { get; set; }
public object Value { get; set; }
public string DataType { get; set; }
}
List<Instance> GetInstancesWithPropertyOfType(string dataType, IEnumerable<Instance> instances) =>
instances.Where(x => x.Properties.Any(y => y.DataType == dataType)).ToList();
Personally think that using LINQ on this just makes it more unreadable and harder to understand. This is basically a two loop operation; for each x in instances/foreach y in x.properties/add x to dictionary indexed by y.z and would be most easily understood by keeping it as such. This minimizes the amount of work done by the framework too; here we create no unnecessary extra objects, lists, groupings etc in the quest to enumerate a 2-deep object hierarchy and create a dictionary, and even a coder who never saw LINQ can understand it:
var dictionary = new Dictionary<string, List<CollectionInstance>>();
foreach (var ci in lstCollectionInstances){
foreach(var cp in ci.CollectionProperties){
if(!dictionary.ContainsKey(cp.DataType))
dictionary[cp.Key] = new List<CollectionInstance>();
dictionary[cp.Key].Add(ci);
}
}
LINQ is a hammer; not every problem is a nail
From the database I get the following values
PlanningID = GetValue<int>(dataReader["PlanningID"]),
PlanningStatus = GetValue<string>(dataReader["PlanningStatus"]),
Private = GetValue<int>(dataReader["Private"])
Social = GetValue<int>(dataReader["Social"])
PlanningID, PlanningStatus, Private
1, good, 10
1, fair, 5
1, bad, 1
I want to group these by planningID so that it looks like this
public class ClassResult
{
public int planningID { get; set; }
public List<PlanningStatus> PlanningStatus { get; set; }
}
public class PlanningStatus
{
public string PlanningStatus { get; set; }
public int Private { get; set; }
public int Social{ get; set; }
}
I tried this but the output was wrong:
IEnumerable<ClassResult> classResult =
from result in results
group result by new
{
result.PlanningID,
result.PlanningStatus
} into grouping
select new ClassResult
{
PlanningID = grouping.Key.PlanningID,
PlanningStatus = grouping.Key.PlanningStatus,
Private = grouping.First().Private,
Social = grouping.First().Social
};
return lrrResults;
To be honest I got no idea how to do this
Updated projection as advised below but still have multiple planning id's of lets say 1
var results =
from result in results
group result by result.PlanningID
into grouping
select new ClassResult
{
PlanningID = grouping.Key,
PlanningStatusType = grouping.Select(item => new PlanningStatusType
{
PlanningStatus = item.PlanningStatus,
Private = item.Private,
Social = item.Social,
}).ToList(),
LatestChange = grouping.First().LatestChange,
WeeklyChangeType = grouping.First().WeeklyChangeType,
Address = grouping.First().Address
};
In your query above you are grouping by both the PlanningID and PlanningStatus so the groups will contain 1 item each. What you want to do is as following:
Instantiate a new ClassResult as you did but setting the planningID but the .Key (which is now just a single property
Project each item of the grouping to a new object of type PlanningStatus using the .Select()
Code:
var classResult = from result in results
group result by result.PlanningID into grouping
select new ClassResult
{
PlanningID = grouping.Key,
PlanningStatus = grouping.Select(item => new PlanningStatus {
PlanningStatus = item.PlanningStatus,
Private = item.Private,
Social = item.Social
}).ToList()
};
Tested on some sample code and works:
I am trying to create a BindingList<> from anonymous type returned by LINQ query but BindingList<> do not accept anonymous type, following is my code
var data = context.RechargeLogs.Where(t => t.Time >= DateTime.Today).
Select(t => new
{
col1 = t.Id,
col2 = t.Compnay,
col3 = t.SubscriptionNo,
col4 = t.Amount,
col5 = t.Time
});
var tmp = new BindingList<???>(data);
In the last line generic argument what to place ???
You can write an extension method:
static class MyExtensions
{
public static BindingList<T> ToBindingList<T>(this IList<T> source)
{
return new BindingList<T>(source);
}
}
and use it like this:
var query = entities
.Select(e => new
{
// construct anonymous entity here
})
.ToList()
.ToBindingList();
If you need to use this object in other places, I would suggest either using dynamic, or even better, to simply create the object you need as a struct.
public class RechargeLogData
{
public int Id { get; set; }
public string Company { get; set; }
public string SubscriptionNo { get; set; }
public string Amount { get; set; }
public string Time { get; set; }
}
var data = context.RechargeLogs.Where(t => t.Time >= DateTime.Today).
Select(t => new RechargeLogData()
{
Id = t.Id,
Company = t.Compnay,
SubscriptionNo = t.SubscriptionNo,
Amount = t.Amount,
Time = t.Time
});
var tmp = new BindingList<RechargeLogData>(data);
The lowest common base type your data shares. For example object if thats the case.
var tmp = new BindingList<object>(data);
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();
Is there any better way doing this?
I have to get both property values.
XML always has only these 2 properties.
My xml:
<Template name="filename.txt">
<Property name="recordSeparator">\r\n</Property>
<Property name="fieldCount">16</Property>
</Template>
Linq:
var property = from template in xml.Descendants("Template")
select new
{
recordDelim = template.Elements("Property").Where(prop => prop.Attribute("name").Value == "recordSeparator")
.Select(f => new { f.Value }),
fieldCount = template.Elements("Property").Where(prop => prop.Attribute("name").Value == "fieldCount")
.Select(f => new { f.Value })
};
"Better way" depends on what exactly are you trying to achieve - performance, simplicity, etc.?
I guess I would create a class that contains what you are trying to get with anonymous classes.
public class Item {
public String Separator { get; set; }
public int FieldCount { get; set; }
}
and then I would modify the LINQ to:
var templates = from template in xml.Descendants("Template")
let children = template.Elements("Property")
select new Item() {
Separator = children.First(tag=>tag.Attribute("name").Value == "recordSeparator").Value,
FieldCount = Int32.Parse(children.First(tag=>tag.Attribute("name").Value == "fieldCount").Value)
};
List<Item> items = templates.ToList();
Note that this will cause NullReference exception in case your Template tag does not contain two Property tags, each with specified attributes.
Also it will throw an exception in parsing the integer from a FieldCount if it's not a number.
Idea:
If the xml generated is your own, and you can change it's format, why not do something like:
<Template>
<Name>filename.txt</Name>
<RecordSeparator>\r\n</RecordSeparator>
<FieldCount>16</FieldCount>
</Template>
It's easier to read and to parse, and it's a little bit shorter.
In the end, I think this is how I would do it:
Having this class:
public class Item
{
public String FileName { get; set; }
public String Separator { get; set; }
public int FieldCount { get; set; }
}
and this private method:
private Item GetItemFromTemplate(XElement node)
{
return new Item() {
FileName = node.Element("Name").Value,
Separator = node.Element("RecordSeparator").Value,
FieldCount = Int32.Parse(node.Element("FieldCount").Value)
};
}
I could do in code:
XDocument doc = XDocument.Load("myfile.txt");
List<Item> items = (from template in doc.Elements("Template")
select GetItemFromTemplate(template)).ToList();
This one is a little more efficient:
var properties =
from template in xml.Descendants("Template")
let propertyNodes = template.Elements("Property")
.Select(arg => new { Name = arg.Attribute("name").Value, Value = arg.Value })
select
new
{
recordDelim = propertyNodes.Single(arg => arg.Name == "recordSeparator").Value,
fieldCount = propertyNodes.Single(arg => arg.Name == "fieldCount").Value
};
If you have always one Template node:
var propertyNodes = xml.XPathSelectElements("/Template/Property")
.Select(arg => new { Name = arg.Attribute("name").Value, arg.Value })
.ToList();
var properties =
new
{
recordDelim = propertyNodes.Single(arg => arg.Name == "recordSeparator").Value,
fieldCount = propertyNodes.Single(arg => arg.Name == "fieldCount").Value
};