【问题标题】:How to use Linq or Lambda to join 1-to-many tables and project flattened results into an anonymous type如何使用 Linq 或 Lambda 连接一对多表并将展平结果投影到匿名类型
【发布时间】:2017-02-25 20:15:51
【问题描述】:

我有 3 个通过外键链接的表:ChangeSet、ObjectChanges、PropertyChanges。这些表彼此之间具有一对多的关系。我需要加入并投影并将结果展平为匿名类型。

我们在数据层使用实体框架,我基本上需要使用 linq 进行以下查询。

select c.Id as ChangeSetId,
c.Timestamp,
c.Author_Id,
u.Name as [User],
o.id as ObjectChangeId,
o.TypeName,
o.ObjectReference as EntityId,
o.DisplayName,
p.Id as PropertyChangeId,
p.PropertyName, 
p.ChangeType,
p.OriginalValue, 
p.Value  
from ChangeSets c
inner join ObjectChanges o
    on c.Id = o.ChangeSetId
left join PropertyChanges p
    on p.ObjectChangeId = o.Id
inner join Users u
    on u.Id = c.Author_Id
order by c.id desc

然而,有问题的方法看起来像这样:

GetAllWhereExpression(Expression<Func<ChangeSet, bool>> expression)

这种情况下的表达式很可能是一个 Where o.EntityId = [Some Value] and c.TimeStamp > X and

我在 linq 中感觉非常接近,但不知道如何注入表达式:(.GetRepository().Entities 基本上是 DbSet)

var foo = from c in _uow.GetRepository<ChangeSet>().Entities
        join o in _uow.GetRepository<ObjectChange>().Entities on c.Id equals o.ChangeSetId
        join p in _uow.GetRepository<PropertyChange>().Entities on o.Id equals p.ObjectChangeId
        where expression // This Line Not Valid
        select new
        {
            ChangeSetId = c.Id,
            Timestamp = c.Timestamp,
            User = c.User.DisplayName,  
            EntityType = o.TypeName,
            EntityValue = o.DisplayName,
            Property = p.PropertyName,
            OldValue = p.OriginalValue,
            NewValue = p.Value
        };

我更喜欢使用 Lambda 语法,但我不知道如何构造它。我知道我需要 SelectMany 来投影和展平结果,但我不知道如何在子集合的匿名类型中使用它们:

var queryable = _uow.GetRepository<ChangeSet>().Entities // This is basically the DbSet<ChangeSet>()
            .Where(expression)
            .SelectMany(c => new
            {
                ChangeSetId = c.Id,
                Timestamp = c.Timestamp,
                User = c.User.DisplayName,  
                EntityType = c.ObjectChanges.SelectMany(o => o.TypeName), //Doesn't work, turns string into char array
                //PropertyName = c. this would be a 1 to many on the entity
            }
                )

如何制作 linq 以产生与 sql 查询基本相同的结果?

【问题讨论】:

    标签: c# entity-framework linq lambda


    【解决方案1】:

    这是它在方法语法中的样子。

    _uow.GetRepository<ChangeSet>().Entities
        .Where(expression)
    .Join(_uow.GetRepository<ObjectChanges>().Entities, cs => cs.Id, oc => oc.ChangeSetId, 
        (cs, oc) => new { cs, oc })
    .Join(_uow.GetRepository<PropertyChanges>().Entities, outer => outer.oc.Id, pc => pc.ObjectChangeId, 
        (outer, pc) => new { cs = outer.cs, oc = outer.cs, pc })
    .Join(_uow.GetRepository<User>().Entities, outer => outer.cs.Author_Id, u => u.Id, 
        (outer, u) => new { 
            ChangeSetId = outer.cs.Id,
            Timestamp = outer.cs.Timestamp,
            User = u.DisplayName,  
            EntityType = outer.oc.TypeName,
            EntityValue = outer.oc.DisplayName,
            Property = outer.pc.PropertyName,
            OldValue = outer.pc.OriginalValue,
            NewValue = outer.pc.Value
        })
    

    【讨论】:

    • 有左加入属性表的方法语法吗?在删除实体的情况下,它当前在属性表中没有条目。
    【解决方案2】:

    请按照给定的演示,通过 LINQ 扩展方法连接的四个实体。
    N.B: .Join 用于 Inner Join

    var foo=_uow.GetRepository<ChangeSet>().Entities
            .Join(_uow.GetRepository<ObjectChange>().Entities,
            c=>c.Id,
            o=>o.ChangeSetId,
            (c,o)=>new{ChangeSet=c,ObjectChange=o})
    
            .Join(_uow.GetRepository<PropertyChange>().Entities,
            o=>o.ObjectChange.Id,
            p=>p.ObjectChangeId,
            (o,p)=>new{o.ChangeSet,o.ObjectChange,PropertyChange=p})
    
            .Join(_uow.GetRepository<Users>().Entities,
            c=>c.ChangeSet.Author_Id,
            u=>u.Id,
            (c,u)=>new{c.ChangeSet,c.ObjectChange,c.PropertyChange,User=u})
    
            .Select(x=>new
            {
                 ChangeSetId=x.ChangeSet.Id,
                 x.ChangeSet.Timestamp,
                 x.ChangeSet.Author_Id,
                 User=x.User.Name,
                 ObjectChangeId=x.ObjectChange.id,
                 x.ObjectChange.TypeName,
                 EntityId=x.ObjectChange.ObjectReference,
                 x.ObjectChange.DisplayName,
                 PropertyChangeId=x.PropertyChange.Id,
                 x.PropertyChange.PropertyName, 
                 x.PropertyChange.ChangeType,
                 x.PropertyChange.OriginalValue, 
                 x.PropertyChange.Value
            }).OrderByDescending(x=>x.ChangeSetId).ToList() //ChangeSetId=c.Id
    

    【讨论】:

    • 如果 PropertyChange 是左连接,我是否只需将 .Join 更改为 .GroupJoin 并将 Select 更改为 SelectMany?如果是,我应该将 DefaultIfEmpty 应用于什么?
    • 跟着它走。 .GroupJoin(_uow.GetRepository().Entities‌​‌​, o=>o.ObjectChange.Id, p=>p.ObjectChangeId, (o,p)=>new{o.ChangeSet,o.ObjectChange, PropertyChange=p.Defau‌​‌​ltIfEmpty()}) .SelectMany(p=> p.PropertyChange .Select(x=>new{ }) )
    • 点击此链接,希望您能得到答案:stackoverflow.com/questions/40964154/…
    【解决方案3】:

    为了简单起见,假设每个表都有一个名为 Description 的列,我们想要检索一个像这样的平面结果:

    -------------------------------------------------------------------------------------
    ChangeSetDescription  |   ObjectChangeDescription   |    PropertyChangeDescription  |
    -------------------------------------------------------------------------------------
    

    简答

    您不需要进行联接,因为 EF 会根据您的数据模型为您计算联接。你可以这样做。想象一下ctx 是一个派生DbContext 的类的实例:

    var query = ctx.ChangeSets
        .SelectMany(x => x.ObjectChanges, (a, oc) => new
        {
            ChangeSetDescription = a.Description,
            ObjectChangeDescription = oc.Description
        ,
            Pcs = oc.PropertyChanges
        })
        .SelectMany(x => x.Pcs, (a, pc) => new
        {
            ChangeSetDescription = a.ChangeSetDescription,
            ObjectChangeDescription = a.ObjectChangeDescription,
            PropertyChangeDesription = pc.Description
        });
    var result = query.ToList();
    

    长答案

    为什么我不需要加入?

    当您创建 EF 模型时,无论您是使用代码优先、数据库优先还是混合方法,如果您正确创建了关系,那么 EF 都会为您找出连接。在数据库查询中,我们需要告诉数据库引擎如何加入,但在 EF 中我们不需要这样做。 EF 将根据您的模型找出关系并使用导航属性进行连接。我们应该使用连接的唯一时间是如果我们正在连接一些任意属性。换句话说,我们应该很少在 EF 查询中使用连接。

    不应将您的 EF 查询编写为 SQL 查询的直接翻译。充分利用 EF,让它为您完成工作。

    你能解释一下简短答案中的查询吗?

    SelectMany 方法会让读者有点反感。但这很容易。让我们以一个简单的类为例:

    public class Student
    {
        public string Name { get; private set; }
        public IEnumerable<string> Courses { get; private set; }
        public Student(string name, params string[] courses)
        {
            this.Name = name;
            this.Courses = courses;
        }
    }
    

    让我们创建 2 个学生并将他们添加到一个通用列表中:

    var s1 = new Student("George", "Math");
    var s2 = new Student("Jerry", "English", "History");
    var students = new List<Student> { s1, s2 };
    

    现在让我们假设我们需要将学生的姓名和他们正在上的课程作为平面结果集。换句话说,我们不希望 Jerry 有一个包含 2 个主题的列表,而是希望 Jerry 有 2 个记录。自然,我们会想到SelectMany 并这样做:

    var courses = students.SelectMany(x => new { Name = x.Name, Courses = x.Courses });
    

    但这不起作用并导致编译错误:

    The type arguments for method 'Enumerable.SelectMany<TSource, TResult> 
    (IEnumerable<TSource>, Func<TSource, IEnumerable<TResult>>)'
    cannot be inferred >from the usage. Try specifying the type arguments explicitly.
    

    SelectMany 方法采用 Func,它将返回 IEnumerable&lt;TResult&gt; 而不是 TResult,因此这将不起作用。幸运的是,它有一个我们可以使用的重载。但是重载有一个,嗯,令人生畏的签名:

    public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>
    (
        this IEnumerable<TSource> source, 
        Func<TSource, IEnumerable<TCollection>> collectionSelector,
        Func<TSource, TCollection, TResult> resultSelector);
    

    基本上,第一个Func 应该返回一个IEnumerable&lt;TCollection&gt;,第二个将接收TSourceTCollection 作为输入,我们可以返回一个TResult 作为输出。好的,让我们这样做:

    var courses = students.SelectMany(x => x.Courses, 
        (a, b) => new { Name = a.Name, Course = b });
    

    所以在上面的行中,a 是我们的students 列表的来源,bstring yield从第一个@987654347 一个接一个地发送给我们我们说的@将返回x.Courses。我们返回的TResult 是一个匿名类型。

    希望这个例子能澄清简短回答中发生的事情。

    【讨论】:

      【解决方案4】:

      或者你可以做的是,因为你的where表达式是changeSet table,你可以在linqselect过滤它 p>

        var foo = from c in _uow.GetRepository<ChangeSet>().Entities.Where(expression) //Notice lambda exp here
          join o in _uow.GetRepository<ObjectChange>().Entities on c.Id equals o.ChangeSetId
          join p in _uow.GetRepository<PropertyChange>().Entities on o.Id equals p.ObjectChangeId
         select new
          {
              ChangeSetId = c.Id,
              Timestamp = c.Timestamp,
              User = c.User.DisplayName,  
              EntityType = o.TypeName,
              EntityValue = o.DisplayName,
              Property = p.PropertyName,
              OldValue = p.OriginalValue,
              NewValue = p.Value
          };
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-04
        • 1970-01-01
        相关资源
        最近更新 更多