How to write a method for OrderBy lambda expression - c#

I have the code:
var trips = _db.Trips
.OrderBy(u => u.TripStops.Where(p=>p.StopTypeId == destinationTypeId).OrderByDescending(c=>c.StopNo).Select(p=>p.Appt).FirstOrDefault())
and this part (sort by appt for the last TripStop with type = destinationTypeId) should be used in many places in code.
I want to write a method like:
private xxx LastTripStopAppt(...)
{
}
and then use it like:
var trips = _db.Trips
.OrderBy(LastTripStopAppt(u))
But a little confused how to implement this method correctly.
PS. I have tried to do it as
private DateTime? ReturnLastDeliveryAppointment(Trip u, int destinationTypeId)
{
return u.TripStops.Where(p => p.StopTypeId == destinationTypeId).OrderByDescending(c => c.StopNo).Select(p => p.Appt).FirstOrDefault();
}
and then
.OrderBy(u => ReturnLastDeliveryAppointment(u, destinationTypeId))
but I get an error:
LINQ to Entities does not recognize the method
'System.Nullable`1[System.DateTime]
ReturnLastDeliveryAppointment(Infrastructure.Asset.Trips.Trip, Int32)'
method, and this method cannot be translated into a store expression.

The signature is probably something like:
private Expression<Func<Trip, apptType>> LastTripStopAppt(...)
where appType is the type of p.Appt
You'll need to pass as a parameter to this method the destinationTypeId.
So, if appType is a string:
private static Expression<Func<Trip, string>> LastTripStopAppt(int destinationTypeId)
{
return u => u.TripStops.Where(p=>p.StopTypeId == destinationTypeId).OrderByDescending(c=>c.StopNo).Select(p=>p.Appt).FirstOrDefault();
}

SELECT *
FROM (VALUES(1), (4), (3)) t(v)
ORDER BY T;
Java 8:
Stream s = Stream.of(11, 41, 3);
s.sorted()
.forEach(System.out::println);
output:
3
11
41

Related

Method has no supported translation to SQL

My project is using MVC 4 C# LINQ to SQL.
For some reason the method used to get data for one of my properties is giving a "has no supported translation to SQL" error. The method to fetch this data is nearly identical to the method of another property in the same query except the one that works grabs a string where the one that doesn't gets a decimal.
Exact error code:
Method 'System.Decimal GetModelDifficulty(System.String)' has no supported translation to SQL.
I've tried numerous variations on the below code but I always get the same error as above:
public List<ProductionSchedule> GetBaseProductionSchedule(DateTime selectedDate)
{
var spaList = (from x in db.WO010032s
join y in db.MOP1042s on x.MANUFACTUREORDER_I equals y.MANUFACTUREORDER_I into x_y
where x.STRTDATE == selectedDate && (x.MANUFACTUREORDERST_I == 2 || x.MANUFACTUREORDERST_I == 3)
from y in x_y.DefaultIfEmpty()
select new ProductionSchedule()
{
MO = x.MANUFACTUREORDER_I,
BOMNAME = x.BOMNAME_I,
SpaModel = x.ITEMNMBR,
MoldType = GetMoldType(x.ITEMNMBR.Trim()),
SerialNumber = y.SERLNMBR,
Difficulty = GetModelDifficulty(x.ITEMNMBR.Trim())
}).OrderByDescending(x => x.Difficulty).ToList();
return spaList;
}
public string GetMoldType(string model)
{
return db.SkuModelDatas.Where(x => x.Value == model).Select(x => x.MoldType).FirstOrDefault();
}
public decimal GetModelDifficulty(string model)
{
return (decimal)db.SkuModelDatas.Where(x => x.Value == model).Select(x => x.Difficulty).FirstOrDefault();
}
Well I've tweaked the code around enough times to where I've stumbled on a variation that works:
public List<ProductionSchedule> GetBaseProductionSchedule(DateTime selectedDate)
{
var spaList = (from x in db.WO010032s
join y in db.MOP1042s on x.MANUFACTUREORDER_I equals y.MANUFACTUREORDER_I into x_y
where x.STRTDATE == selectedDate && (x.MANUFACTUREORDERST_I == 2 || x.MANUFACTUREORDERST_I == 3)
from y in x_y.DefaultIfEmpty()
select new ProductionSchedule()
{
MO = x.MANUFACTUREORDER_I,
BOMNAME = x.BOMNAME_I,
SpaModel = x.ITEMNMBR,
MoldType = GetMoldType(x.ITEMNMBR.Trim()),
SerialNumber = y.SERLNMBR,
Difficulty = GetModelDifficulty(x.ITEMNMBR)
}).ToList();
return spaList.OrderByDescending(x => x.Difficulty).ToList();
}
public string GetMoldType(string model)
{
return db.SkuModelDatas.Where(x => x.Value == model).Select(x => x.MoldType).FirstOrDefault();
}
public decimal GetModelDifficulty(string model)
{
decimal difficulty = (String.IsNullOrEmpty(model)) ? 0M : Convert.ToDecimal(db.SkuModelDatas.Where(x => x.Value == model.Trim()).Select(x => x.Difficulty).FirstOrDefault());
return difficulty;
}
Why it worked when trapping for null string for x.ITEMNMBR (model parameter) in one method and not the other and needing to OrderByDescending outside of the main LINQ query, I have no idea.
Thanks for all the suggestions and help with this.
The problem is your query is calling code that LINQ cannot translate into SQL.
First try this, it may help. There may be a problem with your (decimal) cast. Modify your method GetModelDifficulty to the following:
public decimal GetModelDifficulty(string model)
{
return Convert.ToDecimal(db.SkuModelDatas.Where(x => x.Value == model).Select(x => x.Difficulty).FirstOrDefault());
}
If that doesn't work I'm afraid you'll have to break your query down further to narrow down the issue. Use the documentation provided here: Standard Query Operator Translation (LINQ to SQL) to be sure that any extension methods you are using have supported translations.
If you run into a piece of code that cannot be translated, you may need to declare a separate variable with the necessary value already stored inside of it, that you can then use inside of your query.
I think it's because your call to FirstOrDefault() can return a null value. When you assign to a typed object you can use ? operator to signify that it can be null:
decimal? myDec = <some code returning decimal value or null>
Then you can check if myDec is null or not by:
myDec.HasValue

Encapsulate queries to Navigation Properties [duplicate]

This question already has answers here:
LINQ to Entities does not recognize the method
(5 answers)
Closed 8 years ago.
To make my top layers more readable I usually make extension methods to encapsulate long hard-to-read queries into something as simple as db.Matches.By(period)
This 'By' method looks something like this:
public static IQueryable<PlayedMatch> By(this IQueryable<PlayedMatch> matches, Period period)
{
return matches.Where(pm => pm.Details.DateTime >= period.Start && pm.Details.DateTime < period.End);
}
Problem is that I would like to have something similar for querying Navigation Properties, so I could do something like this:
var query = Db.Players.Select( p => new
{
Player = p,
TotalPoints = p.Matches.By(period).Sum(m => m.Points)
});
Problem is that first of all Navigation Properties are of type ICollection<>. Second is that when I change the extension method to use IEnumerable<> or ICollection<> I get the following exception while running the query:
LINQ to Entities does not recognize the method 'System.Collections.Generic.IEnumerable'1[Match] By(System.Collections.Generic.ICollection`1[Match], Period)' method, and this method cannot be translated into a store expression.
Question:
Is there any other way for me to encapsulate queries on navigation properties like I do with my normal queries?
You'll need to add an extension method for each type:
public static IQueryable<PlayedMatch> By(this IQueryable<PlayedMatch> matches, Period period)
{
return matches.Where(pm => pm.Details.DateTime >= period.Start && pm.Details.DateTime < period.End);
}
public static ICollection<PlayedMatch> By(this ICollection<PlayedMatch> matches, Period period)
{
return matches.Where(pm => pm.Details.DateTime >= period.Start && pm.Details.DateTime < period.End);
}
public static IEnumerable<PlayedMatch> By(this IEnumerable<PlayedMatch> matches, Period period)
{
return matches.Where(pm => pm.Details.DateTime >= period.Start && pm.Details.DateTime < period.End);
}
The compiler will pick the most appropriate at compile time.
Linq-to-Entities cannot translate your By method into sql. It would work if you brought all the players into memory because then you'd be using Linq-to-Objects and it can work with your C# code:
var query = Db.Players
.AsEnumerable //pulls all players into memory
.Select( p => new
{
Player = p,
TotalPoints = p.Matches.By(period).Sum(m => m.Points)
});
But you probably don't want to pay the price of bringing all that data into memory....
If you want to encapsulate long hard-to-read queries, you could declare them as fields. Then you could do something like this:
Func<Bar, bool> NameIsTom = b => b.Name == "Tom";
Foos.Select(f => new { Foo = f, Toms = f.Bars.Where(NameIsTom) });

How do I translate a linq query to an extension method chain with lambda expressions?

I'm using LINQ to Entities and I want to know how do I translate the following query to lambda expression using extension methods.
public _Deposito RegresaDepositosBancarios(int id)
{
return (from d in context.depositos_bancarios
where d.IDDeposito == id
select new _Deposito
{
idDeposito = d.IDDeposito,
cantidad = d.Monto,
fecha = d.FechaDeposito,
aplicado = d.Aplicado
}).Single();
}
Notice that I'm returning a _Deposito type, how do I achieve this using extension methods?
I need something like the following:
public Persona RegresaPersonaPorNombres(string nombres, string apellidoP, string apellidoM)
{
var p = context.personas.Where(x => x.Nombres == nombres &&
x.ApellidoP == apellidoP &&
x.ApellidoM == apellidoM).FirstOrDefault();
return p;
}
I don't want to return an entity type but a custom type instead
This is how this would be written with extension methods, but you really should not need to worry as they are both the same thing.
return context.depositos_bancarios
.Where(d=>d.IDDeposito == id)
.Select(d=>new _Deposito
{
idDeposito = d.IDDeposito,
cantidad = d.Monto,
fecha = d.FechaDeposito,
aplicado = d.Aplicado
})
.Single();
An interesting side note: I could have used a d=> in the Where and then an e=> in the Select. Whereas, the d propogates down throughout the phrase. The only way to reset it would be to use a let phrase. This has nothing to do with the direct question, but I just thought it interesting and wanted to point it out :)

LINQ query null exception when Where returns 0 rows

I have the following LINQ method that works as expected except if there are No Rows Found then I get a Null Exception. I am struggling on how I modify this to return 0 if that occurs.
public static int GetLastInvoiceNumber(int empNumber)
{
using (var context = new CmoDataContext(Settings.Default.LaCrosse_CMOConnectionString))
{
context.Log = Console.Out;
IQueryable<tblGreenSheet> tGreenSheet = context.GetTable<tblGreenSheet>();
return (tGreenSheet
.Where(gs => gs.InvoiceNumber.Substring(2, 4) == empNumber.ToString())
.DefaultIfEmpty()
.Max(gs => Convert.ToInt32(gs.InvoiceNumber.Substring(6, gs.InvoiceNumber.Length)))
);
}
}
Thanks
I tried one of Jon Skeet's suggestions, below, and now I get Unsupported overload used for query operator 'DefaultIfEmpty'
public static int GetLastInvoiceNumber(int empNumber)
{
using (var context = new CmoDataContext(Settings.Default.LaCrosse_CMOConnectionString))
{
context.Log = Console.Out;
IQueryable<tblGreenSheet> tGreenSheet = context.GetTable<tblGreenSheet>();
return tGreenSheet
.Where(gs => gs.InvoiceNumber.Substring(2, 4) == empNumber.ToString())
.Select(gs => Convert.ToInt32(gs.InvoiceNumber.Substring(6, gs.InvoiceNumber.Length)))
.DefaultIfEmpty(0)
.Max();
}
}
You're using
.Where(...)
.DefaultIfEmpty()
which means if there are no results, pretend it's a sequence with a single null result. You're then trying to use that null result in the Max call...
You can probably change it to:
return tGreenSheet.Where(gs => ...)
.Max(gs => (int?) Convert.ToInt32(...)) ?? 0;
This uses the overload finding the maximum of int? values - and it returns an int? null if there were no values. The ?? 0 then converts that null value to 0. At least, that's the LINQ to Objects behaviour... you'll have to check whether it gives the same result for you.
Of course, you don't need to use the ?? 0 if you're happy to change the method signature to return int? instead. That would give extra information, in that the caller could then tell the difference between "no data" and "some data with a maximum value of 0":
return tGreenSheet.Where(gs => ...)
.Max(gs => (int?) Convert.ToInt32(...));
Another option is to use the overload of DefaultIfEmpty() which takes a value - like this:
return tGreenSheet.Where(gs => ...)
.Select(gs => Convert.ToInt32(...))
.DefaultIfEmpty(0)
.Max();
In situations like this when there may or may not be a matching item, I prefer to return an object rather than a value type. If you return a value type, you have to have some semantics about what value means "there is nothing here." I would change it to return the last invoice, then (when it is non-null) get the invoice number from the invoice. Add a method to the class to return the numeric invoice number from the string.
public static tbleGreenSheet GetLastInvoice(int empNumber)
{
using (var context = new CmoDataContext(Settings.Default.LaCrosse_CMOConnectionString))
{
context.Log = Console.Out;
return context.GetTable<tblGreenSheet>()
.Where(gs => gs.InvoiceNumber.Substring(2, 4) == empNumber.ToString())
.OrderByDescending(gs => Convert.ToInt32(gs.InvoiceNumber.Substring(6, gs.InvoiceNumber.Length)))
.FirstOrDefault();
}
}
public class tbleGreenSheet
{
....
public int NumericInvoice
{
get { return Convert.ToInt32(InvoiceNumber.Substring(6, InvoiceNumber.Length)); }
}
...
}
Used as
var invoice = Foo.GetLastInvoice( 32 );
if (invoice != null)
{
var invoiceNumber = invoice.NumericInvoice;
...do something...
}
else
{
...do something else...
}
I had a remarkably similar experience with IQueryable<T> and NHibernate. My solution:
public static TExpr MaxOrDefault<TItem, TExpr>(this IQueryable<TItem> query,
Expression<Func<TItem, TExpr>> expression) {
return query.OrderByDescending(expression).Select(expression).FirstOrDefault();
}
The only drawback is that you are stuck with the standard default value, instead of getting to specify one.

How to refactor multiple similar Linq-To-Sql queries?

Suppose I have the two following Linq-To-SQL queries I want to refactor:
var someValue1 = 0;
var someValue2= 0;
var query1 = db.TableAs.Where( a => a.TableBs.Count() > someValue1 )
.Take( 10 );
var query2 = db.TableAs.Where( a => a.TableBs.First().item1 == someValue2)
.Take( 10 );
Note that only the Where parameter changes. There is any way to put the query inside a method and pass the Where parameter as an argument?
All the solutions posted in the previous question have been tried and failed in runtime when I try to enumerate the result.
The exception thrown was: "Unsupported overload used for query operator 'Where'"
Absolutely. You'd write:
public IQueryable<A> First10(Expression<Func<A,bool>> predicate)
{
return db.TableAs.Where(predicate).Take(10);
}
(That's assuming that TableA is IQueryable<A>.)
Call it with:
var someValue1 = 0;
var someValue2= 0;
var query1 = First10(a => a.TableBs.Count() > someValue1);
var query2 = First10(a => a.TableBs.First().item1 == someValue2);
I believe that will work...
The difference between this and the answers to your previous question is basically that this method takes Expression<Func<T,bool>> instead of just Func<T,bool> so it ends up using Queryable.Where instead of Enumerable.Where.
If you really want reusability you can try to write your own operators. E.g. instead of repeatedly writing:
var query =
Products
.Where(p => p.Description.Contains(description))
.Where(p => p.Discontinued == discontinued);
you can write simple methods:
public static IEnumerable<Product> ByName(this IEnumerable<Product> products, string description)
{
return products.Where(p => p.Description.Contains(description));
}
public static IEnumerable<Product> AreDiscontinued(IEnumerable<Product> products, bool isDiscontinued)
{
return products.Where(p => p.Discontinued == discontinued);
}
and then use it like this:
var query = Products.ByName("widget").AreDiscontinued(false);

Categories