I have the following class
public class SolicitacaoConhecimentoTransporte
{
public long ID { get; set; }
public string CodigoOriginal { get; set; }
public DateTime Data { get; set; }
public List<CaixaConhecimentoTransporte> Caixas { get; set; }
}
I would like to know if there is a way of achiveing the same behavior of the code below using Linq (with lambda expression syntax),
List<SolicitacaoConhecimentoTransporte> auxList = new List<SolicitacaoConhecimentoTransporte>();
foreach (SolicitacaoConhecimentoTransporte s in listaSolicitacao)
{
SolicitacaoConhecimentoTransporte existing =
auxList.FirstOrDefault(f => f.CodigoOriginal == s.CodigoOriginal &&
f.Data == s.Data &&
f.ID == s.ID);
if (existing == null)
{
auxList.Add(s);
}
else
{
existing.Caixas.AddRange(s.Caixas);
}
}
return auxList;
In other words, group all entities that have equal properties and flat all lists into one.
Thanks in advance.
Use anonymous object to group by three properties. Then project each group to new SolicitacaoConhecimentoTransporte instance. Use Enumerable.SelectMany to get flattened sequence of CaixaConhecimentoTransporte from each group:
listaSolicitacao.GroupBy(s => new { s.CodigoOriginal, s.Data, s.ID })
.Select(g => new SolicitacaoConhecimentoTransporte {
ID = g.Key.ID,
Data = g.Key.Data,
CodigoOriginal = g.Key.CodigoOriginal,
Caixas = g.SelectMany(s => s.Caixas).ToList()
}).ToList()
Related
I am looking for a way of optimizing my LINQ query.
Classes:
public class OffersObject
{
public List<SingleFlight> Flights { get; set; }
public List<Offer> Offers { get; set; } = new List<Offer>();
}
public class SingleFlight
{
public int Id { get; set; }
public string CarrierCode { get; set; }
public string FlightNumber { get; set; }
}
public class Offer
{
public int ProfileId { get; set; }
public List<ExtraOffer> ExtraOffers { get; set; } = new List<ExtraOffer>();
}
public class ExtraOffer
{
public List<int> Flights { get; set; }
public string Name { get; set; }
}
Sample object:
var sampleObject = new OffersObject
{
Flights = new List<SingleFlight>
{
new SingleFlight
{
Id = 1,
CarrierCode = "KL",
FlightNumber = "1"
},
new SingleFlight
{
Id = 2,
CarrierCode = "KL",
FlightNumber = "2"
}
},
Offers = new List<Offer>
{
new Offer
{
ProfileId = 41,
ExtraOffers = new List<ExtraOffer>
{
new ExtraOffer
{
Flights = new List<int>{1},
Name = "TEST"
},
new ExtraOffer
{
Flights = new List<int>{2},
Name = "TEST"
},
new ExtraOffer
{
Flights = new List<int>{1,2},
Name = "TEST"
}
}
}
}
};
Goal of LINQ query:
List of:
{ int ProfileId, string CommercialName, List<string> fullFlightNumbers }
FullFlightNumber should by created by "Id association" of a flight. It is created like: {CarrierCode} {FlightNumber}
What I have so far (works correctly, but not the fastest way I guess):
var result = sampleObject.Offers
.SelectMany(x => x.ExtraOffers,
(a, b) => {
return new
{
ProfileId = a.ProfileId,
Name = b.Name,
FullFlightNumbers = b.Flights.Select(f => $"{sampleObject.Flights.FirstOrDefault(fl => fl.Id == f).CarrierCode} {sampleObject.Flights.First(fl => fl.Id == f).FlightNumber}").ToList()
};
})
.ToList();
Final note
The part that looks wrong to me is:
.Select(f => $"{sampleObject.Flights.FirstOrDefault(fl => fl.Id == f)?.CarrierCode} {sampleObject.Flights.FirstOrDefault(fl => fl.Id == f)?.FlightNumber}").ToList()
I am basically looking for a way of "joining" those two lists of the OffersObject by Flight's Id.
Any tips appreciated.
If there will only be a few flights defined in sampleObject.Flights, a sequential search using a numeric key is hard to beat.
However, if the number of flights times the number of offers is substantial (1000s or more), I would suggest loading the list of flights into a dictionary with Id as the key for efficient lookup. Something like:
var flightLookup = sampleObject.Flights.ToDictionary(f => f.Id);
And then calculate your FullFlightNumbers as
FullFlightNumbers = b.Flights
.Select(flightId => {
flightLookup.TryGetValue(flightId, out SingleFlight flight);
return $"{flight?.CarrierCode} {flight?.FlightNumber}";
})
.ToList()
TryGetValue above will quietly return a null value for flight if no match is found. If you know that a match will always be present, the lookup cold alternately be coded as:
SingleFlight flight = flightLookup[flightId];
The above also uses a statement lambda. In short, lambda functions can have either expression or statement blocks as bodies. See the C# reference for more information.
I'd suggest replacing the double .FirstOrDefault() approach with .IntersectBy(). It is available in the System.Linq namespace, starting from .NET 6.
.IntersectBy() basically filters sampleObject.Flights by matching the flight ID for each flight in sampleObject with flight IDs in ExtraOffers.Flights.
In the code below, fl => fl.Id is the key selector for sampleObject.Flights (i.e. fl is a SingleFlight).
var result = sampleObject.Offers
.SelectMany(x => x.ExtraOffers,
(a, b) => {
return new
{
ProfileId = a.ProfileId,
Name = b.Name,
FullFlightNumbers = sampleObject.Flights
.IntersectBy(b.Flights, fl => fl.Id)
.Select(fl => fl.FullFlightNumber) // alternative 1
//.Select(fl => $"{fl.CarrierCode} {fl.FlightNumber}") // alternative 2
.ToList()
};
})
.ToList();
In my suggestion I have added the property FullFlightNumber to SingleFlight so that the Linq statement looks slightly cleaner:
public class SingleFlight
{
public int Id { get; set; }
public string CarrierCode { get; set; }
public string FlightNumber { get; set; }
public string FullFlightNumber => $"{CarrierCode} {FlightNumber}";
}
If defining SingleFlight.FullFlightNumber is not possible/desirable for you, the second alternative in the code suggestion can be used instead.
Example fiddle here.
I have this list that I am checking and then creating another list, where the definition is not equal to null.
var new = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
public class WebWordForm
{
public string definition { get; set; }
public string partOfSpeech { get; set; }
public int sourceId { get; set; }
public List<string> synonyms { get; set; }
public List<string> typeOf { get; set; }
public List<string> hasTypes { get; set; }
public List<string> derivation { get; set; }
public List<string> examples { get; set; }
}
Is there a simple way that I could also set the sourceId in my rootObject.webWordForms list to the value of 2?
var new = rootObject.webWordForms
.Where(w => w.definition != null)
.Select(w => new WebWordForm{
definition = w.definition,
partOfSpeech = w.partOfSpeech,
sourceId = 2,
synonyms= w.synonyms,
typeOf = w.typeOf,
hasTypes = w.hasTypes,
derivation = w.derivation,
examples = w.examples
}).ToList();
You could use List ForEach method to do this, but please mind this is nothing different than looping.
var list = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
list.ForEach(x=> x.sourceId =2);
Use .ForEach() on new, this would iterate on list new and update the param.
var finalList = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
finalList.ForEach(n=>n.sourceId = 2);
Note - If the queried final list is your return you need to do above operations before returning anything.
While I am suggesting ForEach(), many articles focus on avoiding it.
An alternative,
var finalList = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
finalList.All(n=>{
n.sourceId = 2;
return true;
});
I have to next 2 entities in my project
public class Product
{
public Product()
{
this.ProductImages = new HashSet<ProductImage>();
this.ProductParams = new HashSet<ProductParam>();
}
public int ID { get; set; }
public int BrandID { get; set; }
public int CodeProductTypeID { get; set; }
public string SeriaNumber { get; set; }
public string ModelNumber { get; set; }
public decimal Price { get; set; }
public bool AvailableInStock { get; set; }
public virtual Brand Brand { get; set; }
public virtual CodeProductType CodeProductType { get; set; }
public virtual ICollection<ProductImage> ProductImages { get; set; }
public virtual ICollection<ProductParam> ProductParams { get; set; }
}
public class ProductParam
{
public int Id { get; set; }
public int ProductId { get; set; }
public int CodeProductParamId { get; set; }
public string Value { get; set; }
public virtual Product Product { get; set; }
public virtual CodeProductParam CodeProductParam { get; set; }
}
and I want to get list of Products which has list of specified parameters
var prodParamCritria = new List<ProductParam>()
{
new ProductParam(){CodeProductParamId =1, Value="Black" },
new ProductParam(){CodeProductParamId =2, Value="Steal"}
};
in sql I can do it by using EXISTS clause twise
SELECT *
FROM Products p
WHERE EXISTS (
SELECT *
FROM ProductParams pp
WHERE pp.ProductId = p.ID
AND (pp.CodeProductParamId = 1 AND pp.[Value] = N'Black')
)
AND EXISTS (
SELECT *
FROM ProductParams pp
WHERE pp.ProductId = p.ID
AND pp.CodeProductParamId = 2
AND pp.[Value] = N'Steal'
)
How can i get same result by EF methods or linq
Try this:
var products= db.Products.Where(p=>p.ProductParams.Any(pp=>pp.CodeProductParamId == 1 && pp.Value == "Black") &&
p.ProductParams.Any(pp=>pp.CodeProductParamId == 2 && pp.Value == "Steal"));
Update
The problem in work with that list of ProductParam to use it as a filter is that EF doesn't know how to translate a PodructParam object to SQL, that's way if you execute a query like this:
var products2 = db.Products.Where(p => prodParamCritria.All(pp => p.ProductParams.Any(e => pp.CodeProductParamId == e.CodeProductParamId && pp.Value == e.Value)));
You will get an NotSupportedException as you comment in the answer of #BostjanKodre.
I have a solution for you but probably you will not like it. To resolve that issue you could call the ToList method before call the Where. This way you will bring all products to memory and you would work with Linq to Object instead Linq to Entities, but this is extremely inefficient because you are filtering in memory and not in DB.
var products3 = db.Products.ToList().Where(p => prodParamCritria.All(pp => p.ProductParams.Any(e => pp.CodeProductParamId == e.CodeProductParamId && pp.Value == e.Value)));
If you want filter by one criteria then this could be more simple and you are going to be able filtering using a list of a particular primitive type. If you, for example, want to filter the products only by CodeProductParamId, then you could do this:
var ids = new List<int> {1, 2};
var products = db.Products.Where(p => ids.All(i=>p.ProductParams.Any(pp=>pp.CodeProductParamId==i))).ToList();
This is because you are working with a primitive type and not with a custom object.
I suppose something like that should work
db.Product.Where(x => x.ProductParams.FirstOrDefault(y => y.CodeProductParamId == 1) != null && x.ProductParams.FirstOrDefault(y => y.CodeProductParamId == 2) != null).ToList();
or better
db.Product.Where(x => x.ProductParams.Any(y => y.CodeProductParamId == 1) && x.ProductParams.Any(y => y.CodeProductParamId == 2)).ToList();
Ok, if you need to make query on parameters in list prodParamCriteria it will look like this:
db.Product.Where(x => prodParamCritria.All(c=> x.ProductParams.Any(p=>p.CodeProductParamId == c.CodeProductParamId && p.Value== c.Value))).ToList();
I forgot that complex types cannot be used in query database, so i propose you to convert your prodParamCriteria to dictionary and use it in query
Dictionary<int, string> dctParams = prodParamCritria.ToDictionary(x => x.CodeProductParamId , y=>y.Value);
db.Product.Where(x => dctParams.All(c => x.ProductParams.Any(p=> p.CodeProductParamId == c.Key && p.Value== c.Value))).ToList();
another variation:
IEnumerable<Int32> lis = prodParamCritria.Select(x => x.CodeProductParamId).ToList();
var q = Products.Select(
x => new {
p = x,
cs = x.ProductParams.Where(y => lis.Contains(y.Id))
}
).Where(y => y.cs.Count() == lis.Count()).
ToList();
with a named class like (or maybe without, but not in linqpad)
public class daoClass {
public Product p {get; set;}
public Int32 cs {get; set;}
}
IEnumerable<Int32> lis = prodParamCritria.Select(x => x.CodeProductParamId).ToList();
var q = Products.Select(
x => new daoClass {
p = x,
cs = x.ProductParams.Where(y => lis.Contains(y.Id))
}
).Where(y => y.cs.Count() == lis.Count()).
SelectMany(y => y.p).
ToList();
I have two tables in Database:
PostCalculationLine
PostCaluclationLineProduct
PostCalculationLineProduct(table2) contains Foriegn key of PostCalucationLineId(table1)
In C# code I have two different Models for these two tables as follows:
public class PostCalculationLine : BaseModel
{
public long Id{ get; set; }
public string Position { get; set; }
public virtual Order Order { get; set; }
public virtual Task Task { get; set; }
//some other properties go here
public virtual IList<PostCalculationLineProduct> PostCalculationLineProducts { get; set; }
}
and
public class PostCalculationLineProduct : BaseModel
{
public long Id {get;set;}
public string Description { get; set; }
//some other properties go here
}
Now in Entityframework code, I fetch data from PostCalculationLineProduct as follows:
PostCalculationLineRepository pclr = new PostCalculationLineRepository();
DataSourceResult dsrResult = pclr.Get()
.SelectMany(p => p.PostCalculationLineProducts)
.Where(c => c.Product.ProductType.Id == 1 && c.DeletedOn == null)
.Select(c => new HourGridViewModel()
{
Id = c.Id,
Date = c.From,
EmployeeName = c.Employee != null ?c.Employee.Name:string.Empty,
Description= c.Description,
ProductName = c.Product != null?c.Product.Name :string.Empty,
From = c.From,
To = c.Till,
Quantity = c.Amount,
LinkedTo = "OrderName",
Customer ="Customer"
PostCalculationLineId = ____________
})
.ToDataSourceResult(request);
In the above query I want to get PostCalculationLineId(from Table1) marked with underLine. How can I achieve this?
Thanks
You can use this overload of SelectMany to achieve this:-
DataSourceResult dsrResult = pclr.Get()
.SelectMany(p => p.PostCalculationLineProducts,
(PostCalculationLineProductObj,PostCalculationLineObj) =>
new { PostCalculationLineProductObj,PostCalculationLineObj })
.Where(c => c.PostCalculationLineProductObj.Product.ProductType.Id == 1
&& c.PostCalculationLineProductObj.DeletedOn == null)
.Select(c => new HourGridViewModel()
{
Id = c.PostCalculationLineProductObj.Id,
Date = c.PostCalculationLineProductObj.From,
//Other Columns here
PostCalculationLineId = c.PostCalculationLineObj.Id
};
This will flatten the PostCalculationLineProducts list and returns the flattened list combined with each PostCalculationLine element.
I have a query that works fine when using an anonymous type but as soon as I try to un-anonymize it it fails to select all values into the class.
here is the linq i'm using (in combination with Subsonic 3):
var producten = (from p in Premy.All()
join pr in Producten.All() on p.dekking equals pr.ID
where p.kilometragemax >= 10000 &&
p.CCmin < 3000 &&
p.CCmax >= 3000 &&
p.leeftijdmax >= DateTime.Today.Subtract(car.datumEersteToelating).TotalDays / 365
group p by new { pr.ID, pr.Naam, pr.ShortDesc, pr.LongDesc } into d
select new
{
ID = d.Key.ID,
Dekking = d.Key.Naam,
ShortDesc = d.Key.ShortDesc,
LongDesc = d.Key.LongDesc,
PrijsAlgemeen = d.Min(x => x.premie),
PrijsAlgemeenMaand = d.Min(x => x.premie),
PrijsMerkdealerMaand = d.Min(x => x.premie),
PrijsMerkdealer = d.Min(x => x.premie)
}).ToList();
When I change it to:
List<QuotePremies> producten = (from p in Premy.All()
join pr in Producten.All() on p.dekking equals pr.ID
where p.kilometragemax >= 10000 &&
p.CCmin < 3000 &&
p.CCmax >= 3000 &&
p.leeftijdmax >= DateTime.Today.Subtract(car.datumEersteToelating).TotalDays / 365
group p by new { pr.ID, pr.Naam, pr.ShortDesc, pr.LongDesc } into d
select new QuotePremies
{
ID = d.Key.ID,
Dekking = d.Key.Naam,
ShortDesc = d.Key.ShortDesc,
LongDesc = d.Key.LongDesc,
PrijsAlgemeen = d.Min(x => x.premie),
PrijsAlgemeenMaand = d.Min(x => x.premie),
PrijsMerkdealerMaand = d.Min(x => x.premie),
PrijsMerkdealer = d.Min(x => x.premie)
}).ToList();
in combination with this class:
public class QuotePremies
{
public byte ID { get; set; }
public string Dekking { get; set; }
public string ShortDesc { get; set; }
public string LongDesc { get; set; }
public decimal PrijsAlgemeen { get; set; }
public decimal PrijsAlgemeenMaand { get; set; }
public decimal PrijsMerkdealer { get; set; }
public decimal PrijsMerkdealerMaand { get; set; }
}
it doesn't give me an error but all values in the class are 0 except for QuotePremies.ID, QuotePremies.ShortDesc and QuotePremies.LongDesc. No clue why that happens.
See if using conversion helps
PrijsAlgemeen = Convert.ToDecimal(d.Min(x => x.premie))
I believe the problem has to do with casting. Why not write and extension method for IEnumberable which would take this query result and return a collection of List. It could look something like this:
public static class Extensions
{
// extends IEnumerable to allow conversion to a custom type
public static TCollection ToMyCustomCollection<TCollection, T>(this IEnumerable<T> ienum)
where TCollection : IList<T>, new()
{
// create our new custom type to populate and return
TCollection collection = new TCollection();
// iterate over the enumeration
foreach (var item in ienum)
{
// add to our collection
collection.Add((T)item);
}
return collection;
}
}
Thanks to kek444 for helping me with a similar problem