Can't create Func expression for ordering LINQ query - c#

I receive this parameter from my request:
sort=homeCountry
I need to sort by whatever column is sent to the sort parameter, so I created a method like so:
string sort = "homeCountry";
Func<PaymentRateTrip, string> orderBy = (
x => sort == "homeCountry" ? x.HomeCountry :
sort == "hostCountry" ? x.HostCountry :
sort == "homeLocation" ? x.HomeLocation :
sort == "hostLocation" ? x.HostLocation :
x.HomeLocation
);
This works correctly.
However, the columns above are all strings. But, I also need to add decimal columns like so:
string sort = "homeCountry";
Func<PaymentRateTrip, string> orderBy = (
x => sort == "homeCountry" ? x.HomeCountry :
sort == "hostCountry" ? x.HostCountry :
sort == "homeLocation" ? x.HomeLocation :
sort == "hostLocation" ? x.HostLocation :
sort == "oneWayRate" ? x.oneWayRate :
x.HomeLocation
);
It gives me an error on the x.HostLocation that says:
Type of conditional expression cannot be determined because there is
no implicit conversion between 'string' and 'decimal?'
Is there a better way to do what I am trying to do? I am looking for:
A way that will work.
A way that is readable (something like a switch case).
Edit: PaymentRateTrip.cs
public class PaymentRateTrip
{
public Guid? Id { get; set; }
public Guid HomeCountryId { get; set; }
public string HomeCountry { get; set; }
public Guid HomeLocationId { get; set; }
public string HomeLocation { get; set; }
public Guid? HostCountryId { get; set; }
public string HostCountry { get; set; }
public Guid? HostLocationId { get; set; }
public string HostLocation { get; set; }
public decimal? OneWayRate { get; set; }
public decimal? RoundTripRate { get; set; }
public Guid? OneWayCurrencyId { get; set; }
public Guid? RoundTripCurrencyId { get; set; }
}

I'd just make an extension method:
public static IEnumerable<PaymentRateTrip> Sort(this IEnumerable<PaymentRateTrip> list, string sort)
{
switch (sort)
{
case "homeCountry":
return list.OrderBy(x => x.Whatever);
case "somethingElse":
return list.OrderBy(x => x.Whatever);
//....
}
}

It might seem to simple but just do this
var prop = typeof(PaymentRateTrip).GetProperty(sort);
var ordered = lst.OrderBy(p => prop.GetValue(p));
this works as long as the sort name is part of the object.
In your case the function is (p => prop.GetValue(p))

Related

Add item in nested array (mongodb and C#)

I have the following document called Attendances
{
"_id" : ObjectId("5a4ffb00762caf6b54f61ebb"),
"AttnDate" : ISODate("2018-01-05T22:24:00.490Z"),
"AllAttendances" : [
{
"FullName" : "DOMAIN\Zack",
"Logged" : ISODate("2018-01-05T22:23:46.835Z"),
"Pauses" : [
{
PauseStartAt: ISODate("2018-01-05T22:30:46.835Z"),
PauseEndAt: ISODate("2018-01-05T22:35:46.835Z")
}
]
}
]
}
How can i add new items to Pauses. This is my attempt but i have this error "Cannot convert lambda expression to type 'fielddefinition because it is not a delegate type.
My attempt
var filter = Builders<Attendance>.Filter.Eq(a => a.Id, currentAttn.Id) & Builders<Attendance>.Filter.ElemMatch(s => s.AllAttendances, Builders<TimeRecord>.Filter.Eq(n => n.FullName, userName));
var update = Builders<Attendance>.Update.Push(e => e.AllAttendances[-1].Pauses, pauses);
context.Attendances.FindOneAndUpdate(filter, update);
I followed this guide
Attendance Class
public class Attendance
{
[JsonConverter(typeof(ObjectIdConverter))]
public ObjectId Id { get; set; }
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime AttnDate { get; set; }
public List<TimeRecord> AllAttendances { get; set; }
}
TimeRecord Class (AllAttendances)
public class TimeRecord
{
public string FullName { get; set; }
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime Logged { get; set; }
public List<Pause> Pauses { get; set; }
}
Pause Class
public class Pause
{
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime PauseStartedAt { get; set; }
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime PauseEndedAt { get; set; }
}
You need to update your filter to
var filter = Builders<Attendance>.Filter.Eq(a => a.Id, id) &
Builders<Attendance>.Filter.ElemMatch(s => s.AllAttendances, x => x.FullName == userName);
The first argument of ElemMatch is the field, the second argument is the filter.
Looking at it from a different angle, I would suggest you don't use ObjectIDs in c#. I always define ObjectIds as strings in my models and use the Bson attribute decorators to define them as ObjectId's in the database
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
Purely for the pain it causes trying to use ObjectIds in C#. Strings are much easier to handle. Your document in mongodb will still look the same, and you will not need to cast as object id's in your code at all:
_id : ObjectId("xxxxxxx")
This should help you get around the issue of the compiler not knowing how to do the conversion

How to convert linq query to dynamic query?

I want to create linq query which should be dynamic enough to get all properties of specific class and build expression tree for 'where' and 'select' in linq.
There is a domain model which consists of bool properties and numeric properties. Numeric properties will be used for ranges.
Example:
Domain Model
public class MakeMeReal
{
public bool isA { get; set; }
public bool isB { get; set; }
public int Type1 { get; set; }
public double Type2 { get; set; }
public double Type3 { get; set; }
public string Type4 { get; set; }
}
Input Model:
public class LinqDynamic
{
public bool isA { get; set; }
public bool isB { get; set; }
public int lowerRangeForTyp1 { get; set; }
public int UpperRangeForTyp1 { get; set; }
public double LowerRangeForType2 { get; set; }
public double UpperRangeForType2 { get; set; }
}
Simple query:
public void query(LinqDynamic dynamicInput)
{
SampleDbContext db = new SampleDbContext();
var result = from m in db.MakeMeReal
where m.isB == dynamicInput.isB && m.isA == dynamicInput.isA &&
m.Type1 >= dynamicInput.lowerRangeForTyp1 && m.Type1 < dynamicInput.UpperRangeForTyp1 &&
m.Type2 >= dynamicInput.LowerRangeForType2 && m.Type1 < dynamicInput.UpperRangeForType2
select new { a = m.Type1, b = m.Type2, c = m.Type3, d = m.Type4 };
}
I want to build query which will go through domain model and it'll build expression tree and it'll run against the inputs which'll be provided to at runtime using 'LinqDynamic' class as input parameter.
I have more than 10 booleans and 30 range selectors, and number of booleans and range selctors keeps changing as per project requirements, so it's headache to change query each time. I'd like to make it dynamic, so that it'll be agnostic to any change
I read about https://msdn.microsoft.com/en-us/library/bb397951.aspx and some other links which I can't post here due to lack of reputation points
yet I am not sure how to achieve it
What might help is to use a predicate in your query. There is a NuGet package that will assist you to do. The documentation will explain how to do this. See LINQKit here

SUM and GROUPBY lambda expression on DataDomainService

I have a Silverlight application and a gridview bound from a domaindatasource control, and i am querying a view from domain data source using two parameters, but from the result, i need to sum 4 columns, grouping by the others.
My metadata class:
internal sealed class vwAplicacoesMetadata
{
// Metadata classes are not meant to be instantiated.
private vwAplicacoesMetadata()
{
}
public Nullable<int> CodInternoCliente { get; set; }
public Nullable<int> CodTipoOper { get; set; }
public string CPFCNPJCliente { get { return this.CPFCNPJCliente; } set { String.Format("{0,-14}", value); } }
public string TipoClientePFPJ { get; set; }
public string NomeCliente { get; set; }
public DateTime DataOper { get; set; }
public decimal LCQtde { get; set; }
public decimal LCValor { get; set; }
public decimal LDQtde { get; set; }
public decimal LDValor { get; set; }
}
}
And the IQueryable function i need to use the groupby and sum expressions:
public IQueryable<vwAplicacoes> GetVwAplicacoesPorData(DateTime pstrDataInicio, DateTime pstrDataFinal)
{
return this.ObjectContext.vwAplicacoes.Where(d => d.DataOper > pstrDataInicio && d.DataOper < pstrDataFinal)
}
Its working, and now i need to group by CPFCNPJCliente, NomeCliente, TipoClientePFPJ, CodInternoCliente and CodTipoOper, and sum the fields LCValor, LCQtde, LDValor, LDQtde.
Any suggestion?
Try this:
return this.ObjectContext.vwAplicacoes
.Where(d => d.DataOper > pstrDataInicio && d.DataOper < pstrDataFinal)
.GroupBy(x => new {x.CPFCNPJCliente,x.NomeCliente,x.TipoClientePFPJ,x.CodInternoCliente,x.CodTipoOper})
.Select(k => new {key = k.Key,
totalLCValor = k.Sum(x=>x.LCValor),
totalLCQtde = k.Sum(x=>x.LCQtde),
totalLDValor = k.Sum(x=>x.LDValor),
totalLDQtde = k.Sum(x=>x.LDQtde)})

Sort list of different models by two fields with different type

I have two models:
public class CarRent
{
public string CarName { get; set; }
public string SystemId { get; set; }
public DateTime RentEndDate { get; set; }
}
public class CarPurchase
{
public string CarName { get; set; }
public string SystemId { get; set; }
public decimal Mileage { get; set; }
}
I need to combine them into one list, group by CarName and then inside each group I need to sort models initially by SystemId, but then if models have the same SystemId - I need to sort CarRent models by RentEndDate and CarPurchase by Mileage.
What I have tried:
I defined an interface:
public interface ICarPurchaseOrdered
{
string CarName { get; }
string SystemId { get; }
string Order { get; }
}
and got my models to implement it, the Order property just returns string representation of second order criteria, then I defined a view model:
public class GroupedCardList
{
public string CarName { get; set; }
public IEnumerable<ICarPurchaseOrdered> Cars { get; set; }
}
then I have a grouper that just groups my models:
public class CarGrouper
{
IEnumerable<GroupedCardList> Group(IEnumerable<ICarPurchaseOrdered> cars)
{
return cars.GroupBy(c => c.CarName)
.OrderBy(c => c.Key)
.Select(c => new GroupedCardList()
{
CarName = c.Key,
Cars = c.OrderBy(n => n.SystemId)
.ThenBy(n => n.Order)
});
}
}
But it doesn't work right because it sorts strings and I get the car purchase with Milage=1200 before the car with Milage=90.
I know that example is a little bit contrived but it perfectly represents the issue that I have right now. Please give me some advice.
One way to do it would be to implement a custom IComparer. If you extract a common base class:
public class Car
{
public string CarName { get; set; }
public string SystemId { get; set; }
}
public class CarRent : Car
{
public DateTime RentEndDate { get; set; }
}
public class CarPurchase : Car
{
public decimal Mileage { get; set; }
}
Then a IComparer<Car> implementation might look like this:
public class CarComparer : IComparer<Car>
{
public int Compare(Car x, Car y)
{
// compare by system id first
var order = string.Compare(x.SystemId, y.SystemId);
if (order != 0)
return order;
// try to cast both items as CarRent
var xRent = x as CarRent;
var yRent = y as CarRent;
if (xRent != null && yRent != null)
return DateTime.Compare(xRent.RentEndDate, yRent.RentEndDate);
// try to cast both items as CarPurchase
var xPurc = x as CarPurchase;
var yPurc = y as CarPurchase;
if (xPurc != null && yPurc != null)
return decimal.Compare(xPurc.Mileage, yPurc.Mileage);
// now, this is awkward
return 0;
}
}
You can then pass the comparer instance to List.Sort and Enumerable.OrderBy.
You can use int.Parse and order integers instead of strings
c.OrderBy(n => n.SystemId)
.ThenBy(n => int.Parse(n.Order))
The ICarPurchaseOrdered.Order used to order (then) by is a string type; Hence why the ordering is done alphabetically.
I would suggest to change the type of ICarPurchaseOrdered.Order to object,
so the Orderbycan use the underlying object (eitherDateTimeorDecimal`) to order.
**Update:
Try this
c.OrderBy(n => n.SystemId)
.ThenBy(n => n.GetType())
.ThenBy(n => n.Order);

How can I use LINQ to filter and sort rows from one collection to another?

I have the following classes:
public class Menu
{
public int Order { get; set; }
public string Title { get; set; }
public string Status { get; set; }
public string Type { get; set; }
public bool Default { get; set; }
public string Link { get; set; }
public string Notes { get; set; }
public string Text { get; set; }
}
public class MenuItem
{
public int Order { get; set; }
public string Title { get; set; }
public string Type { get; set; }
public bool Default { get; set; }
public string Link { get; set; }
}
Also this which returns ICollection
var menu = _menuRepository.GetPk(pk);
Can someone show me how I can use LINQ to:
a) Get the data from menu
b) Select only rows where Status = "00"
c) Order by Order
d) Put the data into the MenuItem class
I heard there are maybe two ways of coding this. Can anyone explain these and advise which would be better.
Try this:
var menuItems = _menuRepository.GetPk(pk)
.Where(m => m.Status == "00")
.OrderBy(m => m.Order)
.Select(m => new MenuItem
{
Order = m.Order,
Title = m.Title,
Type = m.Type,
Default = m.Default,
Link = m.Link
});
You can throw .ToList() at the end to materialize collection immediately.
Jimmy's answer looks right to me - here's the equivalent in query expression form:
var query = from m in _menuRepoistory.GetPk(pk)
where m.Status == ""
order by m.Order
select new MenuItem
{
Order = m.Order, Title = m.Title, Type = m.Type,
Default = m.Default, Link = m.Link
};
You might want to consider adding a MenuItem constructor which takes a Menu, or a ToMenuItem method to Menu, so you could just use:
var query = from m in _menuRepoistory.GetPk(pk)
where m.Status == ""
order by m.Order
select m.ToMenuItem();
(As with Jimmy's answer, you can call ToList to materialize the results in a List<T>.)

Categories

Resources