【问题标题】:Effective linq contains with anonymous type有效的 linq 包含匿名类型
【发布时间】:2013-03-25 09:24:20
【问题描述】:

我有一个这样创建的匿名对象集合:

var srcCategories = srcSet.Categories.Select(c => new
{
     ApplicationId = c.IsGLobal ? (long?)null : c.App.Id,
     c.Name
});

请注意,此集合不是来自我的数据上下文;它是从外部系统的输入生成的。我需要将 both ApplicationIdName 与我数据库中的实体进行映射。到目前为止,这是我能够成功使其工作的唯一方法:

var trgCategoryIds =
    (from c in core.Domain.Categories.AsEnumerable()
     let ci = new { c.ApplicationId, c.Name }
     where srcCategories.Contains(ci)
     select c.Id)
    .ToArray();

但这需要我先将整个Categories 表拉入内存。我正在寻找一种更有效的方法来执行此操作,最好是在单个查询中。以下选项我都试过了,都不能转sql:

// Removed .AsEnumerable()
var trgCategoryIds =
    (from c in core.Domain.Categories 
     let ci = new { c.ApplicationId, c.Name }
     where srcCategories.Contains(ci)
     select c.Id)
     .ToArray();

// Use .Any() instead of .Contains()
var trgCategoryIds =
    (from c in core.Domain.Categories
     where srcCategories.Any(s => s.ApplicationId == c.ApplicationId && s.Name == s.Name)
     select c.Id)
    .ToArray();

// Use Tuples instead of anon types
var srcCategories = srcSet.Categories.Select(c => Tuple.Create(...));
var trgCategoryIds =
    (from c in core.Domain.Categories
     let ci = Tuple.Create(c.ApplicationId, c.Name)
     where srcCategories.Contains(ci)
     select c.Id)
    .ToArray();

【问题讨论】:

  • 这是两个不同的数据库,还是一个数据库?两个Categories 集合是否来自同一个DataContext
  • @Servy 不,srcCategories 源自外部系统的输入 - 具体而言,Web 服务公开了一个类别列表,然后根据命令行参数对其进行过滤。跨度>
  • 那么,如果两个 IQueryable 来自不同的来源,您将如何避免将两个数据集之一拉入内存?两人几乎肯定不知道如何交谈。
  • @Servy - 按照我的阅读方式,第一个数据源实际上是无关紧要的 - 在我们到达第二个数据库之前,它变成了一个对象,它是 string/long 对。所以它只是将内存中的对象列表与数据库进行比较。
  • .Contains 方法转换为 sql IN 运算符。要匹配 sql 中的列,您必须创建和填充表变量,然后加入。这基本上就是我想做的,除非有更好的选择。

标签: c# linq entity-framework contains anonymous-types


【解决方案1】:

您想要做的事情实际上是不可能的,因为一开始就没有简单的 SQL。实际上,您想要:

select * from Catagories where (ApplicationID = 1 and Name = "Foo") 
                            or (ApplicationID = 2 and Name = "Bar") 
                            or (ApplicationID = 2345 and Name = "Fizbuzz")
                            or ...

据我所知,Entity Framework 无法自动创建这种类型的查询。它可以通过将Contains() 转换为IN (...) 来处理单个测试,但是当您不能join 时,没有简单的SQL 用于anded IN。但是,您可以使用Predicate Builder 库来构造这种类型的OR 查询。页面上的第二个示例应该正是您所需要的。

适合您的使用:

var predicate = PredicateBuilder.False<Category>();

  foreach (var cat in srcCategories)
  {
    var temp = cat;
    predicate = predicate.Or (p => p.ApplicationId == temp.ApplicationId && p.Name == temp.Name);
  }
  return core.Domain.Categories.AsExpandable().Where (predicate);
}

【讨论】:

  • +1 这看起来很有希望,但我得到:“不能递归调用调用 - 尝试使用临时变量。
  • 没关系,我不小心输入了predicate.Name == temp.Name 它有效!
【解决方案2】:

如果两个 Categories 集合来自不同的数据库上下文,则无法绕过将两者之一拉入内存。

如果他们共享一个数据库上下文,那么您要做的就是简单地连接两个表:

var query =
    from domainCat in srcCategories
    join sourceCat in srcSet.Categories
    on new { domainCat.ApplicationId, domainCat.Name } equals
        new { sourceCat.ApplicationId, sourceCat.Name }
    select sourceCat.Id;

【讨论】:

    【解决方案3】:

    如何修改以下代码,将 ks 作为格式化类别(Id + "-" + Name)。

    using System;
    using System.Linq;
    using System.Data.Entity;
    using System.Collections.Generic;
    using System.Data.Entity.ModelConfiguration;
    using System.Data.Objects.SqlClient;
    
    namespace testef {
        //Model
        public class CObj {
            public CObj() {
            }
            public Int32 Id { get; set; }
            public String SomeCol { get; set; }
        }
    
        //Configuration for CObj
        public class CObjConfiguration : EntityTypeConfiguration<CObj> {
            public CObjConfiguration() {
                HasKey(x => x.Id);
            }
        }
    
        public class TestEFContext : DbContext {
            public IDbSet<CObj> objects { get; set; }
    
            public TestEFContext(String cs)
                : base(cs) {
                Database.SetInitializer<TestEFContext>(new DropCreateDatabaseAlways<TestEFContext>());
            }
    
            protected override void OnModelCreating(DbModelBuilder modelBuilder) {
                base.OnModelCreating(modelBuilder);
    
                modelBuilder.Configurations.Add(new CObjConfiguration());
            }
        }
    
        class Program {
            static void Main(String[] args) {
                String cs = @"Data Source=ALIASTVALK;Initial Catalog=TestEF;Integrated Security=True; MultipleActiveResultSets=True";                
    
                using (TestEFContext c = new TestEFContext(cs)) {
                    c.objects.Add(new CObj { Id = 1, SomeCol = "c"});
                    c.SaveChanges();
                }
    
                IEnumerable<String> ks = new List<String> { String.Format("{0,10}-c", 1) };
                foreach (var k in ks) {
                    Console.WriteLine(k);
                }
    
                using (TestEFContext c = new TestEFContext(cs)) {
                    var vs = from o in c.objects
                             where ks.Contains(SqlFunctions.StringConvert((Decimal?)o.Id, 10) + "-" + o.SomeCol)
                             select o;
    
                    foreach (var v in vs) {
                        Console.WriteLine(v.Id);
                    }
                }
    
            }
        }
    }
    

    【讨论】:

      【解决方案4】:
      var trgCategoryIds =
          (from c in core.Domain.Categories.AsEnumerable()
           where sourceCategories.Any(sc=> sc.ApplicationId == c.ApplicationId 
                           && sc.Name == c.Name)
           select c.Id)
          .ToArray();
      

      【讨论】:

      • 我已经尝试过了。即使您使用.AsEnumerable(),它也不会翻译成sql。
      猜你喜欢
      • 1970-01-01
      • 2016-06-22
      • 1970-01-01
      • 2019-06-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-07
      相关资源
      最近更新 更多