【问题标题】:EF6 using custom property in a linq queryEF6 在 linq 查询中使用自定义属性
【发布时间】:2018-10-15 04:16:29
【问题描述】:

我有一个具有以下属性的类:

[NotMapped]
public string Key
{
    get
    {
        return string.Format("{0}_{1}", Process.Name, LocalSequenceNumber);
    }
}

本地序列号是一个计算整数,由并发字典形式的缓存支持。

我希望在 LINQ 查询中使用上面的 Key 属性,但出现异常:

LINQ to Entities 不支持指定的类型成员“Key”。仅支持初始化器、实体成员和实体导航属性。

我了解我收到此错误的原因,但我不太确定如何解决它。目前, Key 属性为我不想放弃的类提供了一个很好的封装。有关库或解决此问题的简单模式的任何建议?

编辑:这是引发异常的查询:

db.Cars.SingleOrDefault(c => c.Id == id && c.Key == key);

【问题讨论】:

  • 如果没有实际引发异常的代码,很难说出原因。
  • 我已添加查询。
  • 您告诉 EF 不要将 Key 映射到数据库列,然后您想在查询中使用它。它不起作用也就不足为奇了。
  • 请记住,LINQ to Entities 会将语句转换为 SQL,如果您使用 NotMappedAttribute,则该属性无法转换为 SQL 语句。
  • 是的,我已经说清楚了,我理解这个问题。这是一个计算属性,我想将其用作查询的一部分。我的问题是是否有办法解决这个问题。

标签: c# asp.net-mvc linq entity-framework-6


【解决方案1】:

DelegateDecompiler 包https://github.com/hazzik/DelegateDecompiler 处理这种情况。

使用Computed 属性装饰您的属性,然后如果您添加Decompile 方法,如下查询应该可以工作:

db.Cars.Decompile().SingleOrDefault(c => c.Id == id && c.Key == key)

【讨论】:

    【解决方案2】:

    有许多第三方软件包可以解决这个问题。我也相信 EF.Core 中有一些方法可以提供帮助,但是,我会建议 2 个“纯实体框架 6”解决方案。

    1. 分两部分执行查询 - SQL 部分,然后是“代码中”部分。

    db.Cars.Where(c => c.Id == id).ToList().SingleOrDefault(c => c.Key == key)

    这仍然会将您的逻辑封装在类中,但您不会从 SQL 执行中受益。

    1. 我喜欢称之为“投影仪”模式。这个有点啰嗦。

    本质上,您创建了一个表示数据传输对象的 EF POCO 的“视图”。它具有视图所需的属性,还确定如何将数据从数据库投影到视图。

    // 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)
    

    这将评估数据库上的所有代码,同时将您的业务逻辑封装在代码中。

    【讨论】:

    • 是的,最简单的方法是 1,其中 .ToList() 执行延迟查询并接收“关闭”服务器的结果,然后 Linq .SingleOrDefault(c =&gt; c.Key == key) 只是在内存 IEnumerable 上运行并且不是t 试图通过 LinqToEntities 执行。
    【解决方案3】:

    唉,你忘了告诉我们Process.NameLocalSequenceNumber 是什么。从标识符看来,它们不是您的Cars 的一部分,而是您本地进程中的值。为什么不在查询之前计算 Key?

    var key = string.Format("{0}_{1}", Process.Name, LocalSequenceNumber);
    db.Cars.SingleOrDefault(c => c.Id == id && c.Key == key);
    

    另一方面,如果 Process.NameLocalSequenceNumberCar 属性,则您必须仅使用可以由您翻译的属性和方法更改 LINQ 查询中的 IQueryable.Expression IQueryable.Provider 进入 SQL。

    幸运的是,您的 Provider 知道 ToSTring() 和字符串连接的概念,所以您可以使用它

    当您在Queryable.Where 中使用属性Key 时,我建议使用函数WhereKey 扩展IQueryable。如果扩展功能对您来说有点神奇,请参阅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());
    }
    

    用法:

    int carId = ...
    string carKey = ...
    var result = myDbContext.Cars
        .WhereKey(carId, carKey)
        .FirstOrDefault();
    

    考虑创建一个只检查密钥的WhereKey。与Where 连接,在Id 上选择。

     var result = myDbContext.Cars
        .Where(car => car.Id == id)
        .WhereKey(carKey)
        .FirstOrDefault();
    

    如果 Process.NameLocalSequenceNumber 不是 Car 的一部分,请将其添加为参数。你明白要点了。

    考虑创建一个只检查密钥的WhereKey。与在Id 上选择的Where 连接。

    如果需要,您可以创建一个WhereKeyFirstOrDefault(),但我怀疑这是否会有很大用处。

    【讨论】:

      猜你喜欢
      • 2021-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-29
      • 2020-10-09
      • 1970-01-01
      • 2021-06-27
      相关资源
      最近更新 更多