Linq到实体- SQL查询-其中列表包含具有2个(或更多)属性的对象



有以下示例:

 var myIds = db.Table1.Where(x=>x.Prop2 == myFilter).Select(x=>x.Id).ToList();
 var results = db.Table2.Where(x=> myIds.Contains(x.T1)).ToList();

这部分很简单。

然而,现在我正面临一个"轻微"的改变,我的"过滤器列表"有2个属性,而不是只有一个:

// NOTE: for stackoverflow simplification I use a basic query to 
// get my "myCombinationObject".
// In reality this is a much more complex case, 
// but the end result is a LIST of objects with two properties.
var myCombinationObject = db.Table3.Where(x=>x.Prop3 == myFilter)
                                   .Select(x=> new { 
                                          Id1 = x.T1, 
                                          Id2 = x.T2
                                    }).ToList();
 var myCombinationObjectId1s = myCombinationObject.Select(x=>xId1).ToList();
 var myCombinationObjectId2s = myCombinationObject.Select(x=>xId2).ToList();
 // step#1 - DB SQL part
 var resultsRaw = db.Tables.Where( x=> 
                     myCombinationObjectId1s.Contains(x.Prop1) 
                  || myCombinationObjectId2s.Contains(x.Prop2))
                .ToList();
//  step#2 - Now in memory side - where I make the final combination filter.
var resultsFiltered = resultsRaw.Where( x=>
            myCombinationObject.Contains( 
                       new {Id1 = x.Prop1, Id2 = x.Prop2 }
            ).ToList();

我的问题:是否有可能将步骤#2合并到步骤#1中(在linq中查询实体)?

我曾经做过你想要的,但是它是相当困难的,需要改变实体模型一点。您需要一个实体来映射类型

new {Id1 = x.Prop1, Id2 = x.Prop2 }

所以你需要有两个属性的实体- Id1和Id2。如果你有一个——很好,如果没有,那就把这样的实体添加到你的模型中:

public class CombinationObjectTable
{
    public virtual Guid Id1 { get; set; }
    public virtual Guid Id2 { get; set; }
}

添加到你的模型:

public DbSet<CombinationObjectTable> CombinationObjectTable { get; set; }

创建新的迁移并应用于数据库(数据库现在将有额外的表组合objecttable)。然后开始构建一个查询:

DbSet<CombinationObjectTable> combinationObjectTable = context.Set<CombinationObjectTable>();
StringBuilder wholeQuery = new StringBuilder("DELETE * FROM CombinationObjectTable");
foreach(var obj in myCombinationObject)
{
    wholeQuery.Append(string.Format("INSERT INTO CombinationObjectTable(Id1, Id2) VALUES('{0}', '{1}')", obj.Id1, obj.Id2);
}
wholeQuery.Append(
    db.Tables
        .Where( x=> 
                 myCombinationObjectId1s.Contains(x.Prop1) 
              || myCombinationObjectId2s.Contains(x.Prop2))
        .Where( x=>
           combinationObjectTable.Any(ct => ct.Id1 == x.Id1 && ct.Id2 == x.Id2)
        ).ToString();
    );
 var filteredResults = context.Tables.ExecuteQuery(wholeQuery.ToString());

多亏了这个,你的主查询保持在linq中编写。如果您不想在数据库中添加新表,这也是可以实现的。向模型中添加新的类CombinationObjectTable,生成新的迁移来添加它,然后从迁移代码中删除创建该表的代码。之后应用迁移。这样,数据库模式不会被改变,但EF会认为数据库中有一个组合对象表。相反,您需要创建一个临时表来保存数据:

StringBuilder wholeQuery = new StringBuilder("CREATE TABLE #TempCombinationObjectTable(Id1 uniqueidentifies, Id2 uniqueidentifier);");

当你在linq查询中调用ToString方法时,将CombinationObjectTable更改为#TempCombinationObjectTable:

...
.ToString()
.Replace("CombinationObjectTable", "#TempCombinationObjectTable")

另一件值得考虑的事情是在INSERT语句中使用查询参数来传递值,而不是仅仅在查询中包含它们——当然,这也可以通过EF实现。

这个解决方案还没有完全准备好应用,而是提示了您可以使用该解决方案的方向。

你能不能这样做:

var result= 
        db.Tables
           .Where(t=> 
              db.Table3
                 .Where(x=>x.Prop3 == myFilter)
                 .Any(a=>a.T1==t.Prop1 || a.T2==t.Prop2)
         ).ToList();

如果您只是想避免中间结果(并创建第二个中间列表),您可以执行以下操作

var resultsFiltered = db.Tables.Where( x=> 
                 myCombinationObjectId1s.Contains(x.Prop1) 
              || myCombinationObjectId2s.Contains(x.Prop2))
            .AsEnumerable() // everything past that is done in memory but isn't materialized immediately, keeping the streamed logic of linq
            .Where( x=>
                 myCombinationObject
                     .Contains(new {Id1 = x.Prop1, Id2 = x.Prop2 })
            .ToList();

相关内容

  • 没有找到相关文章

最新更新