Reputation: 149050
I have a collection of anonymous objects created like this:
var srcCategories = srcSet.Categories.Select(c => new
{
ApplicationId = c.IsGLobal ? (long?)null : c.App.Id,
c.Name
});
Note that this collection does not come from my data context; it's generated from input from an external system. I need to map both ApplicationId
and Name
with entities in my database. So far this is the only way I've been able to successfully make it work:
var trgCategoryIds =
(from c in core.Domain.Categories.AsEnumerable()
let ci = new { c.ApplicationId, c.Name }
where srcCategories.Contains(ci)
select c.Id)
.ToArray();
But this requires me to pull the entire Categories
table into memory first. I'm looking for a more efficient way to do this, preferably in a single query. I've tried all of the following options, none of them can be converted to 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();
Upvotes: 1
Views: 2694
Reputation: 13706
What you want to do isn't really possible, because there's no easy SQL for it in the first place. Effectively, you want:
select * from Catagories where (ApplicationID = 1 and Name = "Foo")
or (ApplicationID = 2 and Name = "Bar")
or (ApplicationID = 2345 and Name = "Fizbuzz")
or ...
Entity Framework, to the best of my knowledge, can't create that type of query automatically. It can handle a single test by converting Contains()
to IN (...)
, but there's no simple SQL for an anded IN
when you can't join
. However, you can use the Predicate Builder library to construct this type of OR
query. The second example on the page should be just what you need.
Adapted for your usage:
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);
}
Upvotes: 3
Reputation: 203843
If the two Categories
collections come from different database contexts then there is no way to get around pulling one of the two into memory.
If they share a database context then what you are trying to do is to simply join the two tables:
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;
Upvotes: 1
Reputation: 7800
what about adaptating the following code, with ks as the formatted categories (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);
}
}
}
}
}
Upvotes: 0
Reputation: 13399
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();
Upvotes: -1