简体   繁体   English

EF6在linq查询中使用自定义属性

[英]EF6 using custom property in a linq query

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: 我希望在LINQ查询中使用上面的Key属性,但会出现异常:

The specified type member 'Key' is not supported in LINQ to Entities. LINQ to Entities不支持指定的类型成员'Key'。 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. 当前,Key属性为我不想提供的类提供了很好的封装。 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. DelegateDecompiler软件包https://github.com/hazzik/DelegateDecompiler可以处理这种情况。

Decorate your property with the Computed attribute, then queries like the following should work if you add the Decompile method: 使用Computed属性装饰属性,然后在添加Decompile方法的情况下进行如下查询:

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. 我也相信EF.Core中有一些方法可以提供帮助,但是,我将建议2个“纯实体框架6”解决方案。

  1. Execute your query in two parts - the SQL part, then the "in code" part. 分两部分执行查询-SQL部分,然后是“ in code”部分。

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. 这仍将您的逻辑封装在类中,但是您没有从SQL执行中受益。

  1. What I like to call the "projector" pattern. 我喜欢称之为“投影仪”模式。 This one is a bit more long-winded. 这一点有点冗长。

Essentially, you create a "view" of the EF POCO that represents a data-transfer-object. 本质上,您将创建一个表示数据传输对象的EF POCO的“视图”。 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. LocalSequenceNumber您忘了告诉我们什么是Process.NameLocalSequenceNumber From the identifiers it seems that they are not part of your Cars , but values in your local process. 从标识符看来,它们不是您Cars一部分,而是本地流程中的值。 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. 如果,另一方面, Process.NameLocalSequenceNumberCar性能,你必须改变IQueryable.Expression只使用性能,并且可以通过你的翻译方法就是在你的LINQ查询IQueryable.Provider到SQL。

Luckily, your Provider knows ToSTring() and the concept of string concatenation So you can use that 幸运的是,您的Provider知道ToSTring()和字符串串联的概念,因此您可以使用它

As you are using property Key in a Queryable.Where , I suggest extending IQueryable with a function WhereKey . 当您在Queryable.Where中使用属性Key时,建议使用功能WhereKey扩展IQueryable 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. 考虑创建只检查密钥的WhereKey The concatenate with a Where that selects on Id . 与在Id上选择的Where串联。

 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. 如果Process.NameLocalSequenceNumber不是Car的一部分,请将其添加为参数。 You get the gist. 你明白了。

Consider creating a WhereKey that only checks the key. 考虑创建只检查密钥的WhereKey The concatenate with a Where that selects on Id . 与在Id上选择的Where串联。

If desired, you can create a WhereKeyFirstOrDefault() , but I doubt whether this would be of much use. 如果需要,可以创建WhereKeyFirstOrDefault() ,但是我怀疑这是否有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM