使用Entity Framework,我想仅包含第一层子对象而不是子级的子级(子级的子级)。

22

使用Entity Framework,我想要包含仅第一级子对象而不是子对象的子对象。

我有这两个类:

public class BusinessesTBL
{
    public string ID { get; set; }
    public string FirstName { get; set; }
    public string lastName { get; set; }

    public ICollection<OffersTBL> OffersTBLs { get; set; }
}

public class OffersTBL 
{
    public int ID { get; set; }
    public string Name { get; set; }

    public int CatId { get; set; }

    public string BusinessesTBLID { get; set; }
    public virtual BusinessesTBL BusinessesTBLs { get; set; }
}

当我尝试根据CatId字段带来所有优惠时,我需要返回BusinessesTBLs,但该方法也会针对每个BusinessesTBL对象再次返回优惠,我的代码如下:

public IQueryable<OffersTBL> GetOffersTBLsCat(int id)
{
    db.OffersTBLs.Include(s => s.BusinessesTBLs);
}
您可以在以下链接中看到错误的结果: http://mycustom.azurewebsites.net/api/OffersApi/GetOffersTBLsCat/4 正如您所看到的,它返回每个业务对象下的所有优惠,而在每个优惠下的业务对象中,我只想返回其业务对象的优惠,而不是业务对象下的优惠。
请问有人能帮忙吗?

我也尝试使用 this.Configuration.LazyLoadingEnabled = false; 但是没有改变任何东西。 - Bashar Abu Shamaa
你问题中的链接已经失效了。这表明最好将结果等作为问题的文本包含在内。 - Gert Arnold
4个回答

16

我现在认识到,原回答的一个很大的部分是无意义的。

果然,无限循环的原因是关系修复。但你不能阻止EF这样做。即使使用AsNoTracking,EF在一个查询中实例化的对象中执行关系修复。因此,带有Include的查询将导致完全填充的导航属性OffersTBLsBusinessesTBLs

信息很简单:如果您不想在结果中显示这些引用循环,您必须投影到视图模型或DTO类中,例如其他答案之一。另一种选择,我个人认为不太吸引人,当序列化处于播放状态时,是配置序列化程序以忽略引用循环。另一个不太吸引人的替代方案是使用AsNoTracking单独获取对象,并自行选择性填充导航属性。


原回答:

这是因为Entity Framework执行关系修复,这是自动填充导航属性的过程,当属于那里的对象存在于上下文中时。因此,即使禁用了惰性加载,您也可以通过循环引用无限钻取导航属性。Json序列化程序正是这样做的(但显然它被指示处理循环引用,因此它不会陷入无限循环)。

窍门是防止关系修复永远发生。关系修复依赖于上下文的ChangeTracker,它缓存对象以跟踪其更改和关联。但是,如果没有要跟踪的内容,就没有什么可修复的了。您可以通过调用AsNoTracking()来停止跟踪:

db.OffersTBLs.Include(s => s.BusinessesTBLs)
             .AsNoTracking()

如果你还在上下文中禁用了懒加载(通过设置contextConfiguration.LazyLoadingEnabled = false),你会发现只有OffersTBL.BusinessesTBLs被填充到Json字符串中,而BusinessesTBL.OffersTBLs则是空数组。

额外的好处是AsNoTracking()可以提高性能,因为EF不需要跟踪所有物化对象。实际上,在断开连接的情况下总是应该使用它。


10

您已关闭了OffersTBLs上的懒加载,使其非虚拟。如果您启用懒加载呢?像这样:

public class BusinessesTBL
{
    public string ID { get; set; }
    public string FirstName { get; set; }
    public string lastName { get; set; }

    //put a virtual here
    public virtual ICollection<OffersTBL> OffersTBLs { get; set; }
}

那么,请确保在序列化时不要调用/包含OffersTBLs。如果OffersTBLs仍然返回,则是因为您在代码中某处获取它们。如果发生这种情况,请编辑您的问题并粘贴所有代码,包括序列化逻辑。


3

由于OffersTBL与BusinessesTBL有关联,而BusinessesTBL又与OffersTBL有关联,因此可以无限循环地通过实体(如OffersTBL.BusinessesTBL.OffersTBL.BusinessesTBL等)。

为了控制实体的嵌套深度,我通常使用具有所需属性的辅助类。

对于BusinessesTBL

public class BusinessesTBLHelper
{
    private BusinessesTBLHelper(BusinessesTBL o){
        ID = o.ID;
        FirstName = o.FirstName;
        lastName = o.LastName;
        OffersTBLids = new List<int>();

        foreach(OffersTBL offersTbl in o.OffersTBLs){
            OffersTBLids.Add(offersTbl.ID);
        }
    }

    public string ID { get; set; }
    public string FirstName { get; set; }
    public string lastName { get; set; }

    public IEnumerable<int> OffersTBLids { get; set; } //no references anymore
}

同样适用于您的OffersTBL实体。
public class OffersTBLHelper
{
    private OffersTBLHelper(OffersTBL o){
        ID = o.ID;
        Name = o.Name;
        CatId = o.CatId;
        BusinessesTBLID = o.BusinessesTBLID;
        BusinessesTBLs = new BusinessesTBLHelper(o.BusinessesTBLs);
    }

    public string ID { get; set; }
    public string Name{ get; set; }
    public intCatId{ get; set; }

    public string BusinessesTBLID { get; set; }
    public BusinessesTBLHelper BusinessesTBLs { get; set; }
}

在查询数据库时,您可以直接从查询结果创建新的帮助对象:

public IEnumerable<OffersTBLHelper> GetOffersTBLsCat(int id)
{
    return db.OffersTBLs.where(s => s.CatId == id).Select(x=> new OffersTBLHelper(x)).ToList();
}

现在您拥有了所有包含业务表(BusinessesTBLs)的Offer表(OffersTBL)。循环停止在这里,因为业务表(BusinessesTBLs)下面没有Offer表(OffersTBLs)。但是,它只有它们的ID列表以供参考和识别。

-1
假设对象不是空而仅仅是空的:
 public IQueryable<OffersTBL> GetOffersTBLsCat(int id)
 {
     db.OffersTBLs.Include(s => s.BusinessesTBLs).Where(x => !x.BusinessesTBLs.OffersTBLs.Any());
 }

编辑:在包含之前进行筛选:
public IQueryable<OffersTBL> GetOffersTBLsCat(int id)
{
     db.OffersTBLs.Where(x => !x.BusinessesTBLs.OffersTBLs.Any())
         .Include(s => s.BusinessesTBLs);
}

谢谢你的尝试,我尝试了你的代码,但它没有返回任何结果! - Bashar Abu Shamaa
我需要返回前两个级别,即每个报价的“offers”和“business obj”,但不要返回三个级别,同时也要显示与每个业务相关的所有报价作为第三个级别! - Bashar Abu Shamaa
你的db对象是如何加载的?如果你进入这个方法,db.OffersTBLs中有什么东西吗? - nik0lai
每个 BusinessesTBL 对象都有许多 offer(offer 与之相关联的数量很多)。在我的代码中,我使用 OffersTBLs.Include(s => s.BusinessesTBLs) 来获取父 Business 对象,并将其与每个 offer 一起返回。但是,正如您所看到的,它会先返回 offer,然后是与之相关联的 business,最后是所有与该 business 相关的 offer! - Bashar Abu Shamaa
抱歉,它也返回空数组!你确定要使用Where吗? - Bashar Abu Shamaa
显示剩余2条评论

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