【问题标题】:Filter related entities based on common identifier of their base entity根据基础实体的通用标识符过滤相关实体
【发布时间】:2015-09-03 13:38:07
【问题描述】:

我想通过基本实体的公共标识符对实体列表进行分组,选择每个组的第一个实体并返回一个新的实体列表。涉及 3 个不同的实体:GenericObject、ObjectVersion 和 ObjectDependency。

public class GenericObject
{
    public int Id { get; set; }
}

public class ObjectVersion
{
    public int Id { get; set; }
    public GenericObject GenericObject { get; set; }
}

public class ObjectDependency
{
    public string Name { get; set; }
    public ObjectVersion ObjectVersion1 { get; set; }
    public ObjectVersion ObjectVersion2 { get; set; }
}

示例设置如下所示:

GenericObject go1 = new GenericObject { Id = 1 };
GenericObject go2 = new GenericObject { Id = 2 };
GenericObject go3 = new GenericObject { Id = 3 };

ObjectVersion ov1 = new ObjectVersion { Id = 1, GenericObject = go1 };
ObjectVersion ov2 = new ObjectVersion { Id = 2, GenericObject = go2 };
ObjectVersion ov3 = new ObjectVersion { Id = 3, GenericObject = go3 };
ObjectVersion ov4 = new ObjectVersion { Id = 4, GenericObject = go1 };

List<ObjectDependency> dependencies = new List<ObjectDependency>
{
     new ObjectDependency { Name = "d1", ObjectVersion1 = ov1, ObjectVersion2 = ov2 },
     new ObjectDependency { Name = "d2", ObjectVersion1 = ov2, ObjectVersion2 = ov3 },
     new ObjectDependency { Name = "d3", ObjectVersion1 = ov4, ObjectVersion2 = ov2 }
};

为了得到所有包含 ov2 的 ObjectDependencies,我会这样过滤:

var ov2Dependencies = dependencies.Where(d => d.ObjectVersion1.Id == ov2.Id 
                                              || d.ObjectVersion2.Id == ov2.Id)
                                  .OrderBy(d => d.Name);
foreach (ObjectDependency dependency in ov2Dependencies)
{
    Console.WriteLine(dependency.Name);
}
// Output:
// d1
// d2
// d3

为了获取所有依赖于 ov2 的 ObjectVersions:

var ov2AllDependentObjectVersions = 
             dependencies.Where(d => d.ObjectVersion1.Id == ov2.Id 
                                     || d.ObjectVersion2.Id == ov2.Id)
                         .Select(d => d.ObjectVersion1)
                         .Union(dependencies.Where(d => d.ObjectVersion1.Id == ov2.Id 
                                                       || d.ObjectVersion2.Id == ov2.Id)
                         .Select(d => d.ObjectVersion2))
                         .Where(o => o.Id != ov2.Id)
                         .OrderBy(o => o.Id);
foreach (ObjectVersion ov in ov2AllDependentObjectVersions)
{
    Console.WriteLine(ov.Id);
}
// Output:
// 1
// 3
// 4

为了获得依赖于ov2的最新ObjectVersions,使用不同的GenericObject:

var ov2LatestDependentObjectVersions = 
         dependencies.Where(d => d.ObjectVersion1.Id == ov2.Id 
                                 || d.ObjectVersion2.Id == ov2.Id)
                     .Select(d => d.ObjectVersion1)
                     .Union(dependencies.Where(d => d.ObjectVersion1.Id == ov2.Id 
                                                    || d.ObjectVersion2.Id == ov2.Id)
                     .Select(d => d.ObjectVersion2))
                     .Where(o => o.Id != ov2.Id)
                     .GroupBy(o => o.GenericObject.Id)
                     .Select(g => g.OrderByDescending(o => o.Id).FirstOrDefault())
                     .OrderBy(o => o.Id);
foreach (ObjectVersion ov in ov2LatestDependentObjectVersions)
{
    Console.WriteLine(ov.Id);
}
// Output:
// 3
// 4

为了实现以下输出,过滤应该是什么样子?基本上,我想获得包含具有不同 GenericObject 的 ov2 的最新 ObjectDependencies。过滤应该可以使用 IQueryable 直接翻译成 T-SQL。

var ov2LatestDependencies = dependencies.Where(d => d.ObjectVersion1.Id == ov2.Id 
                                                     || d.ObjectVersion2.Id == ov2.Id)
                                          // ???
                                         .OrderBy(d => d.Name);
foreach (ObjectDependency dependency in ov2LatestDependencies)
{
    Console.WriteLine(dependency.Name);
}
// Output:
// d2
// d3

我在这里创建了一个小提琴:https://dotnetfiddle.net/OZQlWO

任何帮助将不胜感激!

编辑:

根据 Jason Boyd 的回答,我最终使用了以下支持 LINQ to entity 的解决方案:https://dotnetfiddle.net/YSj8ki

var ov2LatestDependencies = dependencies.Where(d => d.ObjectVersion1.Id == ov2.Id 
                                                     || d.ObjectVersion2.Id == ov2.Id)
    .Where(x => x.ObjectVersion1.Id == ov2.Id)
    .Select(x => new
    {
        ObjectDependency = x,
        ObjectVersion = x.ObjectVersion2
    })
    .Union(
        dependencies.Where(d => d.ObjectVersion1.Id == ov2.Id 
                                                     || d.ObjectVersion2.Id == ov2.Id)
        .Where(x => x.ObjectVersion2.Id == ov2.Id)
        .Select(x => new
        {
            ObjectDependency = x,
            ObjectVersion = x.ObjectVersion1
        })
    )
    .GroupBy(x => x.ObjectVersion.GenericObject.Id)
    .Select(x => x.OrderByDescending(y => y.ObjectVersion.Id).FirstOrDefault())
    .Select(x => x.ObjectDependency)
    .OrderBy(d => d.Name);
foreach (ObjectDependency dependency in ov2LatestDependencies)
{
    Console.WriteLine(dependency.Name);
}
// Output:
// d2
// d3

【问题讨论】:

    标签: c# entity-framework linq linq-to-entities iqueryable


    【解决方案1】:

    所以我冒昧地将您的 LINQ 查询重构为扩展方法 - 它可以更容易地跟踪正在发生的事情:

    public static class Extensions
    {
        public static IQueryable<ObjectDependency> WhereContainsObjectVersion(this IQueryable<ObjectDependency> source, int objectVersionId)
        {
            return
                source
                .Where(x => x.ObjectVersion1.Id == objectVersionId || x.ObjectVersion2.Id == objectVersionId);
        }
    
        public static IQueryable<ObjectVersion> SelectDependentObjectVersions(this IQueryable<ObjectDependency> source, int objectVersionId)
        {
            return
                source
                .WhereContainsObjectVersion(objectVersionId)
                .Select(x => x.ObjectVersion1.Id == objectVersionId ? x.ObjectVersion2 : x.ObjectVersion1);
        }
    
        public static IQueryable<TResult> SelectDependentObjectVersions<TResult>(this IQueryable<ObjectDependency> source, int objectVersionId, Func<ObjectDependency, ObjectVersion, TResult> selector)
        {
            return
                source
                .WhereContainsObjectVersion(objectVersionId)
                .Select(x => x.ObjectVersion1.Id == objectVersionId ? selector(x, x.ObjectVersion2) : selector(x, x.ObjectVersion1));
        }
    
        public static IQueryable<ObjectVersion> SelectByLatestDistinctGenericObject(this IQueryable<ObjectVersion> source)
        {
            return
                source
                .GroupBy(x => x.GenericObject.Id)
                .Select(x => x.OrderByDescending(y => y.Id).FirstOrDefault());
        }
    
        public static IQueryable<ObjectDependency> SelectByLatestDistinctGenericObject(this IQueryable<ObjectDependency> source, int objectVersionId)
        {
            return
                source
                .SelectDependentObjectVersions(objectVersionId, (x, y) => new { ObjectDependency = x, ObjectVersion = y })
                .GroupBy(x => x.ObjectVersion.GenericObject.Id)
                .Select(x => x.OrderByDescending(y => y.ObjectVersion.Id).FirstOrDefault())
                .Select(x => x.ObjectDependency);
        }
    }
    

    然后您可以通过以下方式调用它们(我注释掉了您的部分 LINQ 查询并插入了我的扩展方法,以便您可以看到扩展方法正在替换每个查询的哪一部分):

    // Get all ObjectDependencies containing ov2
    // Output:
    // d1
    // d2
    // d3
    Console.WriteLine("Get all ObjectDependencies containing ov2");
    IEnumerable<ObjectDependency> ov2Dependencies =
        dependencies
        //.Where(d => d.ObjectVersion1.Id == ov2.Id || d.ObjectVersion2.Id == ov2.Id)
        .WhereContainsObjectVersion(ov2.Id)
        .OrderBy(d => d.Name);
    foreach (ObjectDependency dependency in ov2Dependencies)
    {
        Console.WriteLine(dependency.Name);
    }
    
    // Get all ObjectVersions dependent on ov2
    // Output:
    // 1
    // 3
    // 4
    Console.WriteLine("Get all ObjectVersions dependent on ov2");
    IEnumerable<ObjectVersion> ov2AllDependentObjectVersions =
        dependencies
        //.Where(d => d.ObjectVersion1.Id == ov2.Id || d.ObjectVersion2.Id == ov2.Id)
        //.Select(d => d.ObjectVersion1)
        //.Union(dependencies.Where(d => d.ObjectVersion1.Id == ov2.Id || d.ObjectVersion2.Id == ov2.Id)
        //.Select(d => d.ObjectVersion2))
        //.Where(o => o.Id != ov2.Id)
        .SelectDependentObjectVersions(ov2.Id)
        .OrderBy(o => o.Id);
    foreach (ObjectVersion ov in ov2AllDependentObjectVersions)
    {
        Console.WriteLine(ov.Id);
    }
    
    // Get newest ObjectVersions dependent on ov2 with different GenericObject
    // Output:
    // 3
    // 4
    Console.WriteLine("Get newest ObjectVersions dependent on ov2 with different GenericObject");
    IEnumerable<ObjectVersion> ov2NewestDependentObjectVersions =
        dependencies
        //.Where(d => d.ObjectVersion1.Id == ov2.Id || d.ObjectVersion2.Id == ov2.Id)
        //.Select(d => d.ObjectVersion1)
        //.Union(dependencies.Where(d => d.ObjectVersion1.Id == ov2.Id || d.ObjectVersion2.Id == ov2.Id)
        //.Select(d => d.ObjectVersion2))
        //.Where(o => o.Id != ov2.Id)
        //.GroupBy(o => o.GenericObject.Id)
        //.Select(g => g.OrderByDescending(o => o.Id).FirstOrDefault())
        .SelectDependentObjectVersions(ov2.Id)
        .SelectByLatestDistinctGenericObject()
        .OrderBy(o => o.Id);
    foreach (ObjectVersion ov in ov2NewestDependentObjectVersions)
    {
        Console.WriteLine(ov.Id);
    }
    
    // Get newest ObjectDependencies containing ov2 with different GenericObject
    // Output:
    // d2
    // d3
    Console.WriteLine("Get newest ObjectDependencies containing ov2 with different GenericObject");
    IEnumerable<ObjectDependency> ov2NewestDependencies =
        dependencies
        //.Where(d => d.ObjectVersion1.Id == ov2.Id || d.ObjectVersion2.Id == ov2.Id)
        // ???
        .SelectByLatestDistinctGenericObject(ov2.Id)
        .OrderBy(d => d.Name);
    foreach (ObjectDependency dependency in ov2NewestDependencies)
    {
        Console.WriteLine(dependency.Name);
    }
    

    【讨论】:

    • 这看起来很有希望,但我认为这不会很好地分别转化为 IQueryable / T-SQL?
    • 我错过了iqueryable 标签。无论如何,我认为这并不重要。我很确定您可以将所有 IEnumerable 类型更改为 IQueryable(参数和返回类型),它应该可以正常工作。
    • @nilr - 根据新信息更新答案。将所有 IEnumerable 替换为 IQueryable 并删除了迭代器块。我相信这对你有用。
    猜你喜欢
    • 1970-01-01
    • 2019-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-14
    • 2019-10-09
    • 2021-07-05
    • 1970-01-01
    相关资源
    最近更新 更多