I have a class which has the following property:
[NotMapped]
public string Key
{
get
{
return string.Format("{0}_{1}", Process.Name, LocalSequenceNumber);
}
}
The local sequence number is a computed integer backed by a cache in form of a concurrent dictionary.
I wish to use the Key property above in a LINQ query but get the exception:
The specified type member 'Key' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
I understand why I'm getting this error, but I'm not too sure about how to remedy it. Currently, the Key property is providing a nice encapsulation over my class which I don't want to part with. Any suggestions in terms of libraries, or simple patterns to get around this?
Edit: Here's the query which is throwing the exception:
db.Cars.SingleOrDefault(c => c.Id == id && c.Key == key);
The DelegateDecompiler package https://github.com/hazzik/DelegateDecompiler handles this type of scenario.
Decorate your property with the Computed
attribute, then queries like the following should work if you add the Decompile
method:
db.Cars.Decompile().SingleOrDefault(c => c.Id == id && c.Key == key)
There are numerous third party packages that can solve this problem. I also believe that there are methods in EF.Core that can help, however, I will suggest 2 "pure Entity Framework 6" solutions.
db.Cars.Where(c => c.Id == id).ToList().SingleOrDefault(c => c.Key == key)
this will still keep your logic encapsulated in the class, but you do not get the benefit of the SQL execution.
Essentially, you create a "view" of the EF POCO that represents a data-transfer-object. it has the properties you need for your view, and also determines how to project the data from the database to the view.
// Poco:
public class Car {
public int Id {get;set;}
public string LocalSequenceNumber {get;set;}
public int ProcessId {get;set; }
public virtual Process Process {get;set;}
// ...
}
public class Process {
// ...
}
// View+Projector:
public class CarView
{
public int Id {get;set;}
public string Color {get;set;}
public string Key {get;set;}
public static Expression<Func<Car, CarView>> Projector = car => new CarView {
Id = car.Id,
Color = car.Color,
Key = car.Process.Name + " " + car.LocalSequenceNumber
}
}
// calling code
var car = db.Cars.Select(CarView.Project).SingleOrDefault(cv => cv.Id == id && cv.Key == key)
This will evaluate all code on the database, whilst encapsulating your business logic in code.
Alas you forgot to tell us what Process.Name
and LocalSequenceNumber
are. From the identifiers it seems that they are not part of your Cars
, but values in your local process. Why not calculate the Key before your query?
var key = string.Format("{0}_{1}", Process.Name, LocalSequenceNumber);
db.Cars.SingleOrDefault(c => c.Id == id && c.Key == key);
If, on the other hand, Process.Name
or LocalSequenceNumber
are Car
properties, you'll have to change the IQueryable.Expression
that is in your LINQ query using only properties and methods that can be translated by your IQueryable.Provider
into SQL.
Luckily, your Provider
knows ToSTring()
and the concept of string concatenation So you can use that
As you are using property Key
in a Queryable.Where
, I suggest extending IQueryable
with a function WhereKey
. If extension functions are a bit magic for you, see Extension Methods Demystified
public static IQueryable<Car> WhereKey(this IQueryable<Car> cars, int id, string key)
{
return cars.Where(car => car.Id == id
&& key == car.Process.Name.ToString() + "_" + car.LocalSequenceNumber.ToString());
}
Usage:
int carId = ...
string carKey = ...
var result = myDbContext.Cars
.WhereKey(carId, carKey)
.FirstOrDefault();
Consider creating a WhereKey
that only checks the key. The concatenate with a Where
that selects on Id
.
var result = myDbContext.Cars
.Where(car => car.Id == id)
.WhereKey(carKey)
.FirstOrDefault();
If either Process.Name
or LocalSequenceNumber
is not a part of Car, add it as a parameter. You get the gist.
Consider creating a WhereKey
that only checks the key. The concatenate with a Where
that selects on Id
.
If desired, you can create a WhereKeyFirstOrDefault()
, but I doubt whether this would be of much use.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.