Being rather new to LINQ, I am probably not looking for the correct search terms, that's why I am asking here.
I have a data transfer object (DTO) for the current timestep and a DTO for the previous timestep. I am intending to JOIN this information into a single (nested) list and display procentual changes (for all entries of the list). The nested contains nodes of type myObject. In other words, the list consists of a couple of parent entries and (different levels of) child elements of the same type. The merging step towards dataFullDto works perfectly for the parent entries only, for the child entries, the destination object does not contain the information of the previous timestep, PreviousValue and DeltaPercentage are null. Is it possible to perform that action on all nodes of a nested list without a foreach loop?
Assume that there is a function inputData(int timestep) which gives back all the required data. I inherited the following code which contains a LINQ inner JOIN:
var dataCurrentDto = inputData(time).GroupBy(x => x.Position) // Position just contains .Id and .Name
.Select(x => new myObject {
PositionId = x.Key?.Id,
PositionName = x.Key?.Name,
children = x.Select(Mapper.Map<myObjectDto>),
Value = x.Sum(y => y.Value),
PreviousValue = 0, // just to initialize, to be overwritten asap
DeltaPercentage = 0, // dito
});
var dataPreviousDto = inputData.Select(time-1).GroupBy(x => x.Position)
.Select(x => new myObject {
PositionId = x.Key?.Id,
PositionName = x.Key?.Name,
children = x.Select(Mapper.Map<myObjectDto>),
Value = x.Sum(y => y.Value),
});
dataFullDto = from current in dataCurrentDto join previous in dataPrevious on
new { current.PositionId, current.SubPositionId }
equals new { previous.PositionId, previous.SubPositionId }
select new myObjectDto {
PositionId = current.PositionId,
PositionName = current.PositionName,
children = current.children,
SubPositionId = current.SubPositionId,
SubPositionName = current.SubPositionName,
Value = current.Value,
PreviousValueAdjusted = previous.Value,
DeltaPercentage = (previous.!= null || previous.Value != 0) ?
(current.Value - previous.Value) / previous.Value : null,
};
I assume the GroupBy() was added to enable the sum over the children. But apparently, the sum over a leave node does not simply give back the value of the property itself?
The DTO is defined as follows:
namespace API.Models
{
public class myObjectDto
{
public int? PositionId { get; set; }
public string PositionName { get; set; }
public int SubPositionId { get; set; }
public string SubPositionName { get; set; }
public decimal? Value { get; set; }
public decimal? PreviousValue { get; set; }
public decimal? DeltaPercentage { get; set; }
}
}
The parent entries have SubPositionId = null and SubPositionName = null, the child entries have those two fields filled. Leaf-nodes have children=null.
References
LINQ: How to join nested Lists in an ObjectCollection
LINQ inner join for nested list
LINQ JOIN Child Collection
Related
I have two lists of Client Object:
public class Client
{
public int ClientID { get; set; }
public string Name { get; set; }
public string DCCode { get; set; }
public string CountryName { get; set; }
}
my list A hold 4 items ClientID , and name fields are populated,
My List B Hold the same 4 items but there is no name and hold the ClientID DCCode, and CountryName
i need to either Update list A DCCode and Countryname with corresponding values in list B
or create List C which it hold 4 items with complete value of list A and B together
Like :
List C L: item 1 : ClientID : 1, Name: XXYY, DCCode :4, CountryName: UK
I can do this using for loop, but i have been trying to use LINQ
i have tried the following codes but i could not find the correct way to get the result i want
Solution 1
Clients1.Where(i => Clients2.Any(a=> i.CLinetID == a.CLinetID))
Solution 2:
Clients1.Concat(Clients1).Concat(Clients2).ToList();
Any help would be welcomed
As you have the ClientID field populated in both lists join them by that property and project a new object populated with all fields:
var result = from c1 in Clients1
join c2 in Clients2 on c1.ClientID equals c2.ClientID
select new Client { ClientID = c1.ClientID, Name = c1.Name, DCCode = c2.DCCode, CountryName = c2.CountryName };
This will create the third list. You can also update the items of Clients1 likewise:
foreach (var c1 in Clients1)
{
var c2 = Clients2.FirstOrDefault(i => i.ClientID == c1.ClientID);
if(c2 != null)
{
c1.DCCode = c2.DCCode;
c1.CountryName = c2.CountryName;
}
}
For updating entities in first list you can create dictionary from second list - that will allow you to quickly find corresponding entity with O(1):
var clientsByID = listB.ToDictionary(c => c.ClientID);
foreach(var clientA in listA)
{
var clientB = clientsByID[clientA.ClientID];
clientA.DCCode = clientB.DCCode;
clientA.CountryName = clientB.CountryName;
}
You can also join two lists on ClientID property and produce new list from results (if enumerable is good for you, then I would go with query syntax instead):
var listC = listA.Join(listB,
a => a.ClientID,
b => b.ClientID,
(a,b) => new Client {
ClientID = a.ClientID,
Name = a.Name,
DCCode = b.DCCode,
CountryName = b.CountryName
}).ToList();
I have the following entity:
public class Item
{
public int Id { get; set; }
public int? ParentId { get; set; }
public Item Parent { get; set; }
public List<Item> Children { get; set; }
public double PropertyA { get; set; }
public double PropertyB { get; set; }
...
}
Now I want to query the database and retrieve data of all the nested children.
I could achieve this by using Eager Loading with Include():
var allItems = dbContext.Items
.Include(x => Children)
.ToList();
But instead of Eager Loading, I want to do the following projection:
public class Projection
{
public int Id { get; set; }
public List<Projection> Children { get; set; }
public double PropertyA { get; set; }
}
Is it possible to retrieve only the desired data with a single select?
We are using Entity Framework 6.1.3.
Edit:
This is what I have tried so far.
I really don't know how to tell EF to map all child Projection the same way than their parents.
An unhandled exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll
Additional information: The type 'Projection' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.
var allItems = dbContext.Items
.Select(x => new Projection
{
Id = x.Id,
PropertyA = x.PropertyA,
Children = x.Children.Select(c => new Projection()
{
Id = c.Id,
PropertyA = c.PropertyA,
Children = ???
})
})
.ToList();
Generally speaking, you can't load a recursive structure of unknown unlimited depth in a single SQL query, unless you bulk-load all potentially relevant data irregardless whether they belong to the requested structure.
So if you just want to limit the loaded columns (exclude PropertyB) but its ok to load all rows, the result could look something like the following:
var parentGroups = dbContext.Items.ToLookup(x => x.ParentId, x => new Projection
{
Id = x.Id,
PropertyA = x.PropertyA
});
// fix up children
foreach (var item in parentGroups.SelectMany(x => x))
{
item.Children = parentGroups[item.Id].ToList();
}
If you want to limit the number of loaded rows, you have to accept multiple db queries in order to load child entries. Loading a single child collection could look like this for example
entry.Children = dbContext.Items
.Where(x => x.ParentId == entry.Id)
.Select(... /* projection*/)
.ToList()
I see only a way with first mapping to anonymous type, like this:
var allItems = dbContext.Items
.Select(x => new {
Id = x.Id,
PropertyA = x.PropertyA,
Children = x.Children.Select(c => new {
Id = c.Id,
PropertyA = c.PropertyA,
})
})
.AsEnumerable()
.Select(x => new Projection() {
Id = x.Id,
PropertyA = x.PropertyA,
Children = x.Children.Select(c => new Projection {
Id = c.Id,
PropertyA = c.PropertyA
}).ToList()
}).ToList();
A bit more code but will get the desired result (in one database query).
Let's say we have the following self-referencing table:
public class Person
{
public Person()
{
Childern= new HashSet<Person>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int? ParentId { get; set; }
[StringLength(50)]
public string Name{ get; set; }
public virtual Person Parent { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
And for some point of time you need to get all grandsons for specific persons.
So, first of all I will create stored procedure(using code-first migration) to get all persons in the hierarchy for those specific persons:
public override void Up()
{
Sql(#"CREATE TYPE IdsList AS TABLE
(
Id Int
)
GO
Create Procedure getChildIds(
#IdsList dbo.IdsList ReadOnly
)
As
Begin
WITH RecursiveCTE AS
(
SELECT Id
FROM dbo.Persons
WHERE ParentId in (Select * from #IdsList)
UNION ALL
SELECT t.Id
FROM dbo.Persons t
INNER JOIN RecursiveCTE cte ON t.ParentId = cte.Id
)
SELECT Id From RecursiveCTE
End");
}
public override void Down()
{
Sql(#" Drop Procedure getChildIds
Go
Drop Type IdsList
");
}
After that you can use Entity Framework to load the ids(you could modify stored procedure to return persons instead of only returning ids) of persons under the passed persons(ex grandfather) :
var dataTable = new DataTable();
dataTable.TableName = "idsList";
dataTable.Columns.Add("Id", typeof(int));
//here you add the ids of root persons you would like to get all persons under them
dataTable.Rows.Add(1);
dataTable.Rows.Add(2);
//here we are creating the input parameter(which is array of ids)
SqlParameter idsList = new SqlParameter("idsList", SqlDbType.Structured);
idsList.TypeName = dataTable.TableName;
idsList.Value = dataTable;
//executing stored procedure
var ids= dbContext.Database.SqlQuery<int>("exec getChildIds #idsList", idsList).ToList();
I hope my answer will help others to load hierarchical data for specific entities using entity framework.
Lets say I have two lists objects with this classes
class Sku
{
public string skuId { get; set; }
public int qty { get; set; }
public string city { get; set; }
}
class SkuWithCity
{
public string skuId { get; set; }
public string city { get; set; }
}
And I have two lists with objects:
List<Sku> skuList = new List<Sku>();
List<SkuWithCity> skuListWithCity = List<SkuWithCity>();
Imagine that in the first list(skuList), the property "City" of each object is null.
What I want to do is, using linq, select the sku objects that have the same skuId and add the city. Somethis like:
var result = from skuElements in skuList
from skuWCity in skuListWithCity
where skuElements.sku == skuWCity.sku
select skuElements
{
skuElements.city = skuWCity.city,
};
And get the whole object, not just the city
int order to get:
|Object | Qty | City
|---- |----|
|AAA | 2 | Panama|
|BBB | 5 | Rio De Janeiro|
is this even possible, get the whole object and modify one or many properties?
UPDATE: In real life the object that I'm trying to copy has a lot of members, that is why I'm trying to "copy" de object of list A and just modify some attributes using the match object of list B.
If you just want to update the existing objects instead of projecting to a new set of objects then first use a join to get the matching items.
var result = from skuElement in skuList
join skuWCity in skuListWithCity
on skuElements.skuId == skuWCity.skuId
select skuElements
{
skuElement,
skuWCity
};
Then iterate them and do the update.
foreach(var x in result)
{
x.skuElement.City = x.skuWCity.City;
}
Note this does not handle the case where more than one item in either list has more than one match in the other. I'm assuming that the lists have a one-to-one match already.
Alternatively you could just use a dictionary.
var cities = skuListWithCity.ToDictionary(s => s.skuId, s => s.City);
foreach(var s in skuList)
{
s.City = cities[s.skuId];
}
Note that this fails if there are duplicate skuId in skuListWithCity or if a skuId in skuList is not in skuListWithCity
You could use a join and then make a projection of the result set as below:
var result = from skuElements in skuList
join skuWCity in skuListWithCity
on skuElements.skuId equals skuWCity.skuId
select new Sku
{
skuId = skuElements.skuId,
qty = skuElements.qty,
city = skuWCity.city,
};
I have two list with objects:
class Class1
{
public int Id { get; set; }
public string Name { get; set; }
public Guid UniqueIdentifier { get; set; }
public bool IsValid { get; set; }
}
class Class2
{
public int Identifier { get; set; }
public string Producer{ get; set; }
public Guid Guid { get; set; }
public int SupplierId { get; set; }
}
Is there a way to use linq to get the elements of type Class1 from the list that have the same Id (identifier) and Guid with the elements of type Class2 from the second list?
Here is one way to do it:
var result = list1
.Where(x => list2.Any(y => x.Id == y.Identifier && x.UniqueIdentifier == y.Guid))
.ToList();
Please note that this version is not optimized for large lists. If your lists are small, this is fine. If you have large lists, you need a solution that involves things like HashSets. Here is an example:
var list2HashSet = CreateHashset(list2.Select(x => new {x.Identifier, x.Guid}));
var result = list1
.Where(x => list2HashSet.Contains(new {Identifier = x.Id, Guid = x.UniqueIdentifier}))
.ToList();
Where CreateHashset is defined like this:
public static HashSet<T> CreateHashset<T>(IEnumerable<T> collection)
{
return new HashSet<T>(collection);
}
I had to create this simple method because the compiler is complaining that it cannot resolve the correct constructor overload. I am not really sure why.
You could try something like this:
var result = from item1 in list1
join item2 in list2 on
new { G = item1.UniqueIdentifier, Id = item1.Id }
equals new { G = item2.Guid, Id = item2.Identifier }
select new { item1, item2 };
foreach(var item in result)
{
Console.WriteLine($"Producer: {item.item2.Producer} with product: {item.item1.Name}");
}
Let's say you have the two lists
List<Class1> List1;
List<Class2> List2;
You can select all items of List1 containing the Id and Guid of the second list with
List1.Where(C1 => List2.Any(C2 => C1.Id == C2.Identifier && C1.UniqueIdentifier == C2.Guid));
Note that Guid is a class. If you don't want to check if C1.UniqueIdentifier and C2.Guid are exactly the same objects, you should implement IsEqual and use it like
List1.Where(C1 => List2.Any(C2 => C1.Id == C2.Identifier && C1.UniqueIdentifier.Equals(C2.Guid)));
If it suffice for you that the ids or the guids match, see the answer from Jeroen van Langen. Otherwise, I see two options:
Add a where clause afterwards, i.e.,
var result = from item1 in list1
join item2 in list2 on item1.Id equals item2.Identifier
where item1.UniqueIdentifier = item2.Guid
select new { item1, item2 };
Create a tuple class and join on the tuples of Guid and Id. You cannot reuse the .NET 4 tuple types (Tuple<,>), but you could reuse the C# 7 tuple types as they correctly implement equality.
Both versions should also be fine with large lists. Basically, the whole thing should scale as long as you use join.
I using Linq (together with EF) in order to access my database. I have object "Job", which contains several properties, some of them are "complex". My goal is to group jobs by these properties, and to get a count for each group.
Here my objects (simplified):
public class Job
{
[Key]
public int Id
{
get;
set;
}
[Required]
public Salary Salary
{
get;
set;
}
[Required]
public ICollection<Category> Categories
{
get;
set;
}
}
"Category" is a complex class, and looks like this:
public class Category
{
[Key]
public int Id
{
get;
set;
}
public Industry Industry //Example: Software
{
get;
set;
}
public Field Field //Example: .NET
{
get;
set;
}
public Position Position //Example: Developer
{
get;
set;
}
}
Industry, Field, Position and Salary classes contains just "int" id and "string" name.
I need to group list of Jobs by Industry, Field, Position and Salary and to get a count of each group. This is how I doing it right now:
var IndustryGroupsQuery = from t in Jobs.SelectMany(p => p.Categories)
group t by new { t.Industry} into g
select new
{
Tag = g.Key.Industry,
Count = g.Count()
};
var FieldsGroupsQuery = from t in Jobs.SelectMany(p => p.Categories)
group t by new { t.Field} into g
select new
{
Tag = g.Key.Field,
Count = g.Count()
};
var PositionsGroupsQuery = from t in Jobs.SelectMany(p => p.Categories)
group t by new { t.Position} into g
select new
{
Tag = g.Key.Position,
Count = g.Count()
};
Jobs.GroupBy(job => job.Salary)
.Select(group => new
{
Tag = group.Key,
Count = group.Count()
}))
This is works fine, but I wondering is it possible to improve somehow its performance.
Q1: I think, that probably one single query will perform better that four. Is it possible to combine these queries into one single query?
Q2: When I asking Linq to group by "Industry", how exactly it able to distinguish between one Industry to another? Is it implicitly comparing records' keys? Is it will be faster if I explicitly tell to linq which property to group by (e.g. "id") ?
Thanks!
Answer in reverse order:
Q2:
When you group by an object instead of a base type, it uses the standard equality comparer (obj x == obj y) which does a simple reference comparison (http://msdn.microsoft.com/en-us/library/bsc2ak47(v=vs.110).aspx). If that suits, it works, otherwise you can implement a custom equality comparer (How to implement IEqualityComparer to return distinct values?)
Q1:
If you wanted sub-groups of the groups, then you can do it in a single query. If you just want the counts for each, then you are doing it exactly the right way.
You can user conditional GROUP BY.
You can define a variable to tell the query which column to use for grouping. You can define an ENUM for GROUP BY columns.
int groupByCol = 1; //Change the value of this field according to the field you want to group by
var GenericGroupsQuery = from t in Jobs
group t by new { GroupCol = ( groupByCol == 1 ? t.Industry:(groupByCol == 2 ? t.Field:(groupByCol == 3 ? t.Position : t.Job)))} into g
select new
{
Tag = g.Key,
Count = g.Count()
};