I have two classes which share two common attributes, Id and Information.
public class Foo
{
public Guid Id { get; set; }
public string Information { get; set; }
...
}
public class Bar
{
public Guid Id { get; set; }
public string Information { get; set; }
...
}
Using LINQ, how can I take a populated list of Foo objects and a populated list of Bar objects:
var list1 = new List<Foo>();
var list2 = new List<Bar>();
and merge the Id and Information of each into a single dictionary:
var finalList = new Dictionary<Guid, string>();
Thank you in advance.
Sounds like you could do:
// Project both lists (lazily) to a common anonymous type
var anon1 = list1.Select(foo => new { foo.Id, foo.Information });
var anon2 = list2.Select(bar => new { bar.Id, bar.Information });
var map = anon1.Concat(anon2).ToDictionary(x => x.Id, x => x.Information);
(You could do all of this in one statement, but I think it's clearer this way.)
var finalList = list1.ToDictionary(x => x.Id, y => y.Information)
.Union(list2.ToDictionary(x => x.Id, y => y.Information))
.ToDictionary(x => x.Key, y => y.Value);
Make sure the ID's are unique. If not they will be overwritten by the first dictionary.
EDIT: added .ToDictionary(x => x.Key, y => y.Value);
Related
I am having trouble juggling 3 dictionaries to find differences such as missing entries and also property values.
The dictionaries key and values are these objects:
public class Car
{
public string CarBrand{ get; set; }
public string RentingCompany{ get; set; }
public string CompanyPhone{ get; set; }
public ComplexObj MyObj { get; set; } //This is a deserialized XML
}
public class Drivers
{
public string DriverID{ get; set; } // Unique
public string Name{ get; set; }
public string LastName{ get; set; }
}
public Dictionary<Drivers, List<Car>> List1 = new Dictionary<Drivers, List<Car>>();
public Dictionary<Drivers, List<Car>> List2 = new Dictionary<Drivers, List<Car>>();
public Dictionary<Drivers, List<Car>> List3 = new Dictionary<Drivers, List<Car>>();
I need to search in List1.Values all the CarBrands that are not in list2 or List3 and the save the entire KeyValue pair Driver and Value into a new dictionary.
I would gladly accept any guidance on what would be an optimal way to approach this.
Thanks
This approach is going to be much much faster than comparing every car brand in list1 with every car brand in list2 and list3. The approach shown in the other answer has high computational complexity, it would scale badly for large amounts of data.
I haven't really tested it, but I think it is correct.
// only the distinct car brands throughout list2 and list3 remain in this hash set
HashSet<string> carBrandsInlist2and3 = new(
List2.Values
.Concat(List3.Values)
.SelectMany(cars => cars.Select(car => car.CarBrand)));
// using a hashset for IsSubsetOf below is going to be much quicker than
// using a 'simple' IEnumerable because set operations have optimizations in place in case
// both sets are of the same type and have the same IEqualityComparer
HashSet<string> tempHashset = new();
Dictionary<Drivers, List<Car>> newDictionary = new(
List1
.Where(keyValuePair =>
{
// fill the temporary hashset with car brands of this kvp
tempHashset.Clear();
tempHashset.UnionWith(keyValuePair.Value.Select(car => car.CarBrand));
// if tempHashset is not a subset of carBrandsInlist2and3
// then in this keyValuePair there is a car brand that does not exist in list2 or list3
return !tempHashset.IsSubsetOf(carBrandsInlist2and3);
}));
var brands2 = List2.Values.SelectMany(v => v.Select(c => c.CarBrand));
var brands3 = List3.Values.SelectMany(v => v.Select(c => c.CarBrand));
var allBrands = brands2.Concat(brands3);
var keyValuePairsToSave = List1
.Where(pair => !pair.Value.Any(car => allBrands.Any(brand => brand == car.CarBrand)))
// or
//.Where(pair => !allBrands.Any(brand => pair.Value.Any(c => c.CarBrand == brand)))
.ToArray();
//or
//.ToDictionary(pair => pair.Key, pair => pair.Value);
I have a class OrderLineRequest that I want to map to an OrderLine class with a list of barcodes. The properties Barcode1,2,3 needs to be mapped to Barcodes only if the contain a value. Barcode1 is always filled, Barcode2 and Barcode3 are optional. I have created a mapping but this gives me always 3 barcodes in the list. If Barcode1 or 2 is an empty string i don't want to add them to the list. How can i do this?
public class OrderLineRequest
{
public string OrderLineId { get; set; }
public string Barcode1 { get; set; }
public string Barcode2 { get; set; }
public string Barcode3 { get; set; }
public int Quantity { get; set; }
}
public class OrderLine
{
public int Id { get;set;}
public int OrderId { get;set;}
public string OrderLineNumber { get; set; }
public int Qty { get; set; }
public List<Barcode> Barcodes { get;set;}
}
public class Barcode
{
public int Id { get;set;}
public int OrderLineId { get;set;}
public string Code { get;set;}
}
CreateMap<OrderLineRequest, OrderLine>()
.ForMember(b => b.Id, e => e.Ignore())
.ForMember(d => d.OrderId, p => p.Ignore())
.ForMember(d => d.OrderLineNumber, p => p.MapFrom(s => s.OrderLineId))
.ForMember(d => d.Qty, p => p.MapFrom(s => s.Quantity))
.ForMember(d => d.BarCodes, p => p.MapFrom(s => new List<EanCode>() { new EanCode(){Code = s.Barcode1}, new EanCode() { Code = s.Barcode2 }, new EanCode() { Code = s.Barcode3 } }));
Why are you always creating those three barcodes?
I would suggest you to create a function for the predicate
that accepts OrderLineRequest and returns your List and handle the creation within the function. Like that:
private List<EanCode> Foo(OrderLineRequest orderLineRequest)
{
var result = new List<EanCode>();
if(!string.IsNullOrEmpty(orderLineRequest.Barcode1)
result.Add(new EanCode {Code = orderLineRequest.Barcode1});
//...
return result;
}
And then you could use it like:
.ForMember(d => d.BarCodes, p => p.MapFrom(s => Foo(s)));
If you're using Automapper, the step of adding three specific properties from the source to a list in the destination can't be accomplished with a simple function. You have to tell Automapper how to accomplish it.
You can do that by telling it to ignore those properties during the initial mapping, and then add items to the destination list after that mapping is complete.
For brevity this includes only those properties:
var configuration = new MapperConfiguration(
cfg => cfg.CreateMap<OrderLineRequest, OrderLine>()
.ForMember(d => d.Barcodes, opt => opt.Ignore())
.ForSourceMember(s => s.Barcode1, opt => opt.DoNotValidate())
.ForSourceMember(s => s.Barcode2, opt => opt.DoNotValidate())
.ForSourceMember(s => s.Barcode3, opt => opt.DoNotValidate())
.AfterMap((source, destination) =>
{
destination.Barcodes = new List<Barcode>
{
new Barcode { Code = source.Barcode1 }
};
if (source.Barcode2 != null)
destination.Barcodes.Add(new Barcode { Code = source.Barcode2 });
if (source.Barcode3 != null)
destination.Barcodes.Add(new Barcode { Code = source.Barcode3 });
}));
It could be said that this makes a case for just writing your own extension instead of using Automapper. It's convenient when the mapping is simple, but if it's not then using it could arguably be more trouble than it's worth. That's a matter of preference.
Create an array of the three properties. Once it's in an array, you can use Where to remove the nulls and Select to instantiate the EanCode instances. Once the data are in good shape, call ToList().
.ForMember
(
d => d.BarCodes,
p => p.MapFrom
(
s =>
(new [] { s.BarCode1, s.BarCode2, s.BarCode3 })
.Where( x => x != null)
.Select( x => new EanCode { Code = x } )
.ToList()
)
);
How to write 'Where Any In' in LINQ to Entity?
Here is my model :
class Chair
{
public int Id { get; set; }
public int TableId { get; set; }
public Table Table { get; set; }
}
class Table
{
public int Id { get; set; }
public ICollection<Chair> Chairs { get; set; }
public ICollection<Category> Categories { get; set; }
public Table()
{
Chairs = new List<Chair>();
Categories = new List<Category>();
}
}
class Category
{
public int Id { get; set; }
public ICollection<Table> Tables { get; set; }
}
I also got a simple list of Category :
List<Category> myCategories = new List<Category>(c,d,e);
I want to get only that Chairs that belongs to Table that got one of the Category from myCategories List. Thats what im trying to do :
var result =
ctx.Chairs.Where(x => x.Table.Categories.Any(y => myCategories.Any(z => z.Id == y.Id))).ToList();
I think its ok but what i get is error :
"Unable to create a constant value of type 'ConsoleApplication1.Category'. Only primitive types or enumeration types are supported in this context"
Try to compare with in-memory categories Ids collection, instead of categories collection.
var myCategoriesIds = myCategories.Select(c => c.Id).ToArray();
var result =
context.Chairs
.Where(
x => x.Table.Categories.Any(
y => myCategoriesIds.Contains(y.Id)))
.ToList();
this is because ctx.Chairs is a collection that is in database, you should retrieve that collection first in order to compare it with in-memory data:
var result = ctx
.Chairs
.AsEnumerable() // retrieve data
.Where(x =>
x.Table.Categories.Any(y =>
myCategories.Any(z => z.Id == y.Id)))
.ToList();
EDIT: that wouldn't be the correct thing to do if you have a lot of entities on database, what you can do is to split it into two queries:
var tables = ctx.Tables
.Where(x =>
x.Categories.Any(y =>
myCategories.Any(z => z.Id == y.Id)));
var result = ctx.Chairs
.Where(x =>
tables.Any(t=> t.Id == x.TableId))
.ToList();
You can select Ids from myCategories and use it last statement.
var CategoryIds = myCategories.Select(ct => ct.Id);
var result = ctx.Chairs.Where(x => x.Table.Categories.Any(y => CategoryIds.Any(z => z == y.Id))).ToList();
Im trying to GroupBy a field, Select keys into a new type (Country), then SelectMany into a new collection type (CountryPrefix)
Through intuition i've come up with the following, however , i'm having trouble "sealing the deal"
given the following class
public class TempPrefix
{
public String CountryName { get; set; }
public String Prefix { get; set; }
public int ClassificationId { get; set; }
}
and tempPrefixes is a List<TempPrefix>
var countries = tempPrefixes
.GroupBy(x => x.CountryName)
.Select(x => new Country
{
Name = x.Key,
CountryPrefixes = x.SelectMany(y => new CountryPrefix
{
ClassificationId = y.ClassificationId,
Prefix = y.Prefix
})
});
Compile Error on SelectMany
The type arguments for method
'System.Linq.Enumerable.SelectMany(System.Collections.Generic.IEnumerable,
System.Func>)'
cannot be inferred from the usage. Try specifying the type arguments
explicitly.
I'm sure this is telling me something however i'm not quite sure what it is
ANSWER
A stated in the accepted answer i just needed to use Select and not SelectMany
Additionally i had to convert the result to a list
var countries = tempPrefixes
.GroupBy(x => x.CountryName)
.Select(x => new Country
{
Name = x.Key,
CountryPrefixes = x.Select(y => new CountryPrefix
{
ClassificationId = y.ClassificationId,
Prefix = y.Prefix
}).ToList()
});
Try changing that to Select instead..
In
CountryPrefixes = x.SelectMany(y => new CountryPrefix
{
ClassificationId = y.ClassificationId,
Prefix = y.Prefix
})
x is already a collection of TempPrefix, under the respective group, so you can simply get the CountryPrefix by a Select
I have a simple model that I'm trying to group:
public class PracticeArea
{
[Key]
public int PracticeAreaID { get; set; }
public string Name { get; set; }
public string Type { get; set; }
}
I'd like to group by Type, how can I convert this:
var practiceAreas = (from c in db.PracticeAreas
select c).
to:
public Dictionary<string, string> groupedPracticeAreas { get; set; }
I'm not sure how grouping works within Linq - I can run .ToGroup(),. but that doesn't give me my dictionary.
I've tried:
practiceAreas = practiceAreas.ToDictionary(x => x.Type, x => x.Name);
But that gave me a cast error
This should not throw cast exception if both type and name are strings:
practiceAreas.ToDictionary(x => x.Type, x => x.Name)
But this would throw if there is more than one practice area exist for some type. You can use one of following options:
1) Use lookup instead of dictionary. It will create lookup for names by area type
practiceAreas.ToLookup(pa => pa.Type, pa => pa.Name)
2) Use dictionary with collection to hold all names for each type, e.g. Dictionary<string, List<string>> :
practiceAreas.GroupBy(pa => pa.Type)
.ToDictionary(g => g.Key, g => g.Select(pa => pa.Name).ToList())
3) Join all names in single string
practiceAreas.GroupBy(pa => pa.Type)
.ToDictionary(g => g.Key, g => String.Join("," g.Select(pa => pa.Name)))