当Web API返回IQueryable时,EF引用丢失

3

我已经在这个问题上寻找了相当长的时间。

事情是这样的。我正在构建一个网站,调用Web API来获取数据。我的Web API使用库,并使用存储库模式。我的数据库模型(EF Model-first)是在库中构建的。在那个模型中,我有一个基类Pass。然后我有两个派生类:CustomerCard:Pass和Voucher:Pass。我的 EF Designer 模型

我有一个方法来获取所有的顾客卡。

public IQueryable<CustomerCard> GetAllPasses() {
        IList<CustomerCard> allCards = new List<CustomerCard>();

        var c_cards = context.Passes;
        foreach (var c_card in c_cards) {
            if (c_card is CustomerCard) {
                allCards.Add((CustomerCard)c_card);
            }
        }
        return allCards.AsQueryable<CustomerCard>();
    }

在我的ApiController中,我使用这个方法来获取通行证并将它们返回给网站,就像这样:
    [HttpGet]
    [Queryable]
    public IQueryable<CustomerCard> GetAllPasses(string version) {
        return passRepo.GetAllPasses().AsQueryable();
    }

我的Web API返回JSON格式。这是我保留引用等内容的配置:

        var json = config.Formatters.JsonFormatter;
        json.SerializerSettings.PreserveReferencesHandling =
            Newtonsoft.Json.PreserveReferencesHandling.Objects;
        json.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
        config.Formatters.Remove(config.Formatters.XmlFormatter);

我使用IQueryable是因为我想在网站上对数据进行分页。该api方法可在“/api/v1/passes/all”处获得。

奇怪的是,为了测试我的分页,我每页调用一个pass。 对于第一个Pass,它正常工作。但是当我转到第二页时,它也获取了正确的pass,但User的引用消失了。

如您在我的模型中所看到的,CustomerCard类具有用户属性。这表示谁拥有客户卡。

因此,此调用从pass加载用户:'api/v1/passes/all?$top=1' 但是当我调用这个时,用户实例是NULL: 'api/v1/passes/all?$top=1&$skip=1'。

然而,当我调用“api/v1/passes/all?$top=2”时,第二个pass的User被加载。 所以这就是我不理解的地方!我不明白?为什么第二个pass的用户引用没有出现?这可能与EF的惰性加载功能有关吗?

编辑 当我在context.passes上使用Include扩展方法时,会抛出错误:

指定的包含路径无效。实体类型'LCS_Model.Pass' 没有声明名为'User'的导航属性。

这是因为Passes作为一个dbset,包含CustomerCard以及Voucher。有没有办法告诉我的上下文去期望或将其转换为CustomerCard?

能否有人帮助我。如果您不明白我的问题,请随时提问!

感谢您的帮助!

编辑2

我的API控制器上的方法现在是:

[HttpGet]
[Queryable]
public IQueryable<CustomerCard> GetAllPasses(string version) {
     return context.Passes.Include("User").OfType<CustomerCard>();
}

这使我得到了正确的项目。我的数据库中有2张客户卡,它们都来自同一个用户。我的API仍然加载着这个用户。当我的网站接收到响应时,用户属性变为null。我猜测这是因为它仍在引用数组的第一元素中相同的用户。这可能吗?如果是,我该如何防止发生这种情况?


你能为方法“all”添加控制器代码吗? - Dirk Trilsbeek
第二个代码块是控制器代码。路由“api/{version}/passes/all”路由到操作=“GetAllPasses”。 - SandersD
1个回答

2
是的,当你进行查询时,需要确保任何相关记录都被包含在内。请参阅此链接中的一些示例:http://msdn.microsoft.com/en-us/library/bb344857.aspx。其次……我不明白为什么你要用for循环做那么多工作……如果你想进行分页,那么这是服务器完全不必要和浪费的大量工作。我认为,除了你可能想要应用的其他过滤器之外,你的GetAllPasses应该看起来像这样。
public IQueryable<CustomerCard> GetAllPasses() {
    return context.Passes.Include(r => r.User);
}

编辑(2):我需要更好的阅读理解。 我必须承认,我不熟悉EF中的类型继承。 我找到了一些可能适用的内容: 层次结构表具体类型表,还可以参考MSDN Queryable.OfType<TResult>。 这只是一个猜测,但让我们尝试:

public IQueryable<CustomerCard> GetAllPasses() {
    return context.Passes.OfType<CustomerCard>().Include(r => r.User);
}

1
这也是我的最初解决方案。问题在于我的EF模型只创建了一个dbset。Users属性仅适用于子类型CustomerCard(在我的数据库[dbo]中生成的单独表Passes_CustomerCard)。因此,当我想使用您建议的Include方法时,会出现错误,指出User不是属性。这是我的最大问题。我能告诉我的上下文它需要从CustomerCard中获取吗? - SandersD
谢谢,我明天一早就试试!我会告诉你的。 - SandersD
1
OfType<CustomerCard>() 比我的初始 for-loop 更好。所以感谢您。但是我似乎无法在其后使用 Include(r => r.User) 方法。Include 仅适用于 DbSet 扩展。此外,Include 仅接受字符串作为路径。但这不是问题。我的当前代码是 return context.Passes.Include("User").OfType<CustomerCard>(); 它给了我正确类型的通行证,但第二个中仍然没有用户。这是一个奇怪的问题。不过还是谢谢您的答案。 - SandersD
你有没有把 Include 放在 OfType 后面?只是好奇。 - JayC
是的,当我把Include放在OfType之后时,我可以包含子属性。现在它已经可以工作了。每次我使用我的上下文时,我都会关闭ProxyCreationEnabled(将其设置为False)。然后在我的库中添加适当的include语句。现在我没有问题了。感谢您的帮助! - SandersD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接