AsEnumerable and Query Syntax - c#

I have a rather complex Linq expression written using query syntax. Simplified, it looks like:
var query =
from a in ctx.A
from b in ctx.B
where a.Bid == b.Id
select new MyType()
{
Foo = a.Foo,
Bar = b.Bar
}
I have to modify the query to set a property on the new instance of MyType in a manner that cannot be evaluated by the query provider. The result set fairly small, so it is reasonable to use .AsEnumerable(). I found an example on MSDN that shows how to do this in a simple case
IEnumerable<DataRow> query =
from product in products.AsEnumerable()
select product;
How do I use AsEnumerable() in my more complex case? That is, how do I achieve something like
var query =
from a in ctx.A
from b in ctx.B
where a.Bid == b.Id
AsEnumerableGoesHereSomehow
select new MyType()
{
Foo = a.SomeClientSideCalculation(),
Bar = b.Bar
}
I don't want to perform the join and filtering client side.

Feel free to reduce {a=a,b=b} to just the properties you actually need, but you can use this as a start:
var query =(
from a in ctx.A
from b in ctx.B
where a.Bid == b.Id
select new
{
a = a,
b = b
})
.AsEnumerable()
.Select(x=>new MyType {
Foo=Something(x.a),
Bar=x.b
});

Related

Linq Left Join selecting a collection as a property

I'm new to Linq and I'm trying to join two table's - left join to be precise, I'm expecting top 10 results where the property of a result is a collection.
consider the sample model
Class A { int Id, List<B> Collection }
Class B { int Id, int x, int y }
I'm trying to perform left join such that the response I expect needs to be in the following format:
int A
Collection (part of B) =>{int x}
I tried with the following query
From A in _context.A
Join B in (from B in _context.B select new {Id, x }) on B.Id equals A.Id Into subB
From minimalB in subB.defaultIfEmpty()
Select {A.Id, minimalB.x}
How do i achieve the result such that the x property maps in as a collection to the result.
I Apologize for absurd explanation of the question in prior!
I don't think you need from minimalB in subB.defaultIfEmpty part, because it will create something similar to a cartesian product from your sets of data which represented as range variables (a and minimalB ). So to have a collection of X-s, you can try the following:
var query = from a in context.A
join b in context.B on a.Id equals b.Id into groupedB
where groupedB.Any()
select new { a.Id, Xs = groupedB.Select(b => b.X) };
By the way, if you already have the B collection in your A class, you can make the query without explicit joins (under the hood the query provider still will make join)
var query = _context.A.Select(a => new
{
a.Id,
Bs = a.Collection.Select(b => new { b.X, b.Y })
});

How to use Linq or Lambda to join 1-to-many tables and project flattened results into an anonymous type

I have 3 tables linked by Foreign Keys: ChangeSet, ObjectChanges, PropertyChanges. These tables have 1-To-Many Relationships with each other. I need to join and project and flatten the results into an anonymous type.
We use Entity Framework at the data layer and I essentially need to make the following Query with 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
The Method in question however looks like this:
GetAllWhereExpression(Expression<Func<ChangeSet, bool>> expression)
The expression in this case is likely to be a Where o.EntityId = [Some Value] and c.TimeStamp > X and < Y.
I got very close I felt in linq with the following but couldn't figure out how to inject the expression: (The .GetRepository().Entities is basically 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
};
I'd prefer to use Lambda syntax but I can't figure out how to construct it. I know I need SelectMany to project and flatten the results but I can't figure out how to use them within the anonymous type for the subcollections:
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
}
)
How do I craft the linq to produce basically the same results as the sql query?
Here is how it may look like in method syntax.
_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
})
Please follow the given demonstration, Four entities joined by LINQ extension methods.
N.B: .Join use for 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
To keep it simple imagine each table has a column named Description and we want to retrieve a flat result like this:
-------------------------------------------------------------------------------------
ChangeSetDescription | ObjectChangeDescription | PropertyChangeDescription |
-------------------------------------------------------------------------------------
Short Answer
You do not need to do a join because EF will figure out the joining for you based on your data model. You can just do this. Imagine ctx is an instance of a class which derives 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();
Long Answer
Why do I not need the join?
When you create your EF model, regardless of whether you are using code first, database first or a hybrid approach, if you create the relationships correctly then EF will figure out the joins for you. In a database query, we need to tell the database engine how to join, but in EF we do not need to do this. EF will figure out the relationship based on your model and do the joins using the navigation properties. The only time we should be using joins is if we are joining on some arbitrary property. In other words, we should seldom use joins in EF queries.
Your EF queries should not be written as a direct translation of SQL queries. Take advantage of EF and let it do the work for you.
Can you explain the query in the short answer?
It is the SelectMany method which will throw readers off a little. But it is pretty easy. Let's use a simple class as example:
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;
}
}
Let's create 2 students and add them to a generic list:
var s1 = new Student("George", "Math");
var s2 = new Student("Jerry", "English", "History");
var students = new List<Student> { s1, s2 };
Now let's imagine we need to get the name of the students and the classes they are taking as flat result set. In other words, we do not want Jerry to have a list with 2 subjects but instead we want 2 records for Jerry. Naturally, we will think of SelectMany and do this:
var courses = students.SelectMany(x => new { Name = x.Name, Courses = x.Courses });
But that will not work and cause a compilation error:
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.
The SelectMany method takes a Func which will return IEnumerable<TResult> NOT TResult so that will not work. Luckily, it has an overload that we can use. But the overload has a, well, intimidating signature:
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>
(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TCollection>> collectionSelector,
Func<TSource, TCollection, TResult> resultSelector);
Basically what that is saying is the first Func should return an IEnumerable<TCollection> and the second will receive the TSource and TCollection as input and we can return a TResult as output. Ok so let's do that:
var courses = students.SelectMany(x => x.Courses,
(a, b) => new { Name = a.Name, Course = b });
So in the above line, a is the source which is our students list and b is the string yielded to us one by one from the first Func which we said will return x.Courses. The TResult we are returning is an anonymous type.
Hopefully this example will clarify what is going on in the short answer.
Or what you can do is, since your where expression is for changeSet table, you can filter it when you select from it within your linq
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
};

Why I'm still getting System.NotSupportedException

My code is:
using( var ctxA = new AEntities())
using( var ctxB = new BEntities())
{
var listOfA = (from A in AEntities.AEntity select A).ToList();
var listOfB = (from B in BEntities.BEntity
where listOfA.Select( A = A.Id)contains(B.Id)
select B).ToList();
}
I'm getting the error:
The specified LINQ expression contains references to queries that are associated with different contexts.
However, because of 'ToList' I already did a fetch in AEntity thats makes second query only about one of the contexts, did not?
How can a separate the two queries still using one list to query the other?
Try to store just IDs into listOfA
var listOfA = (from A in AEntities.AEntity select A.Id).ToList();
var listOfB = (from B in BEntities.BEntity
where listOfA.Contains(B.Id)
select B).ToList();
If anyone wants to know, the answer to my question was:
var listOfA = (from A in AEntities.AEntity select A.Id).ToList();
List<long> listOfIdsOfA = listOfA.Select( A=>A.id).ToList();
var listOfB = (from B in BEntities.BEntity
where listOfIdsOfA.Contains(B.Id)
select B).ToList();
The only difference to Selman's answer was the creation of Id's list after the first query. That's why I need the whole entity too.

LINQ using dictionary in where clause

er have the following query in linq...
Whenever I try to run it I get a No comparison operator for type System.Int[] exception.
It's got something to do with the dictionary I am sure, but I don't understand why this isn't valid and was wondering if someone could explain?
// As requested... not sure it will help though.
var per = (
from p in OtherContext.tblPeriod
where activeContractList.Select(c => c.DomainSetExtensionCode).Contains(p.DomainSetExtensionCode)
select p).ToArray();
var com = (
from c in MyContext.tblService
join sce in MyContext.tblServiceExtension
on c.ServiceExtensionCode equals sce.ServiceExtensionCode
join sc in MyContext.tblServiceContract
on sce.ServiceContractCode equals sc.ContractCode
group sc by c.Period into comG
select new
{
PeriodNumber = comG.Key,
Group = comG,
}).ToArray();
var code =
(from c in com
join p in per on c.PeriodNumber equals p.PeriodNumber
select new
{
p.Code,
c.Group
}).ToArray();
var payDictionary = new Dictionary<int, int[]>();
// This is another linq query that returns an anonymous type with
// two properties, and int and an array.
code.ForEach(c => payDictionary.Add(c.Code, c.Group.Select(g => g.Code).ToArray()));
// MyContext is a LINQ to SQL DataContext
var stuff = (
from
p in MyContext.tblPaySomething
join cae in MyContext.tblSomethingElse
on p.PaymentCode equals cae.PaymentCode
join ca in MyContext.tblAnotherThing
on cae.SomeCode equals ca.SomeCode
where
// ca.ContractCode.Value in an int?, that should always have a value.
payDictionary[p.Code].Contains(ca.ContractCode.Value)
select new
{
p.Code,
p.ExtensionCode,
p.IsFlagged,
p.Narrative,
p.PayCode,
ca.BookCode,
cae.Status
}).ToList();
You won't be able to do this with a dictionary. The alternative is to join the three linq queries into one. You can do this with minimal impact to your code by not materializing the queries with ToArray. This will leave com and code as IQueryable<T> and allow for you compose other queries with them.
You will also need to use a group rather than constructing a dictionary. Something like this should work:
var per = (
from p in OtherContext.tblPeriod
where activeContractList.Select(c => c.DomainSetExtensionCode).Contains(p.DomainSetExtensionCode)
select p.PeriodNumber).ToArray(); // Leave this ToArray because it's materialized from OtherContext
var com =
from c in MyContext.tblService
join sce in MyContext.tblServiceExtension on c.ServiceExtensionCode equals sce.ServiceExtensionCode
join sc in MyContext.tblServiceContract on sce.ServiceContractCode equals sc.ContractCode
group sc by c.Period into comG
select new
{
PeriodNumber = comG.Key,
Group = comG,
}; // no ToArray
var code =
from c in com
where per.Contains(c.PeriodNumber) // have to change this line because per comes from OtherContext
select new
{
Code = c.PeriodNumber,
c.Group
}; // no ToArray
var results =
(from p in MyContext.tblPaySomething
join cae in MyContext.tblSomethingElse on p.PaymentCode equals cae.PaymentCode
join ca in MyContext.tblAnothThing on cae.SomeCode equals ca.SomeCode
join cg in MyContext.Codes.GroupBy(c => c.Code, c => c.Code) on cg.Key equals p.Code
where cg.Contains(ca.ContractCode.Value)
select new
{
p.ContractPeriodCode,
p.DomainSetExtensionCode,
p.IsFlagged,
p.Narrative,
p.PaymentCode,
ca.BookingCode,
cae.Status
})
.ToList();
Side Note: I also suggest using navigation properties where possible instead of joins. It makes it much easier to read and understand how objects are related and create complex queries.

LinqProvider that gets data from from two places and merges them

I have a Linq query that looks something like the following
var query3 = from c in Session.CreateLinq<AccountTransaction>()
join a in Session.CreateLinq<Account>() on c.Account equals a
where c.DebitAmount >= 0
select new { a.Name, c.DebitAmount }
;
The Session object interacts with a datasource behind the scenes but it also has an internal cached state which may have changes. When I run a query I would like to query the both the internal cached state AND the datasource and then merge the results together, with the internal cached state taking precendence.
I am using re-linq for the generation of the query against the datasource which is working fine. What I am not sure about is how to also do the query against the internal state using the same Linq query.
There's a call GetAllCached() on Session that I can use instead of Session.CreateLinq if I just wanted to query the internal state. But I'm not sure at which point in my custom provider I can handle handing off to the datasource AND the internal state using GetAllCached().
Any suggestions appreciated from any Linq gurus.
// From Database
var query1 = from c in Session.CreateLinq<AcccountTransaction>()
join a in Session.CreateLinq<Account>()
on c.Account equals a
where c.DebitAmount >= 0
select new { Account = a, AccountTrans = c };
//select new { a.Name, c.DebitAmount };
// From Cache
var query2 = from c in Session.GetAllCached<AcccountTransaction>()
join a in Session.GetAllCached<Account>()
on c.Account equals a
where c.DebitAmount >= 0
select new { Account = a, AccountTrans = c };
//select new { a.Name, c.DebitAmount };
//var query3 = query2.Union(query1.Except(query2));
var query4 = query2.Union(query1);
Modified: 04:51 AM Singapore Time
If I understand correctly, you have a single custom LINQ provider for your datasource, and a (presumably type-safe) way of getting cached results as well.
In this case, I recommend just using LINQ to Objects to access your cached set. You can use AsEnumerable to "step out" of your custom LINQ provider into LINQ to Objects.
The join brings up a problem, though. Since either of these types may exist in the cache, it's not possible to push logic to the DB. For example, is it possible to have an AccountTransaction in the cache without its Account also being in the cache?
If you allow any situation in the cache (e.g., AccountTransaction without associated Account records), then you have to do the join in memory and not in the db:
var allDebitAccountTransactions = Session.GetAllCached<AccountTransaction>()
.Where(x => x.DebitAmount >= 0)
.Union(Session.CreateLinq<AccountTransaction>()
.Where(x => x.DebitAmount >= 0));
var allAccounts = Session.GetAllCached<Account>()
.Union(Session.CreateLinq<Account>());
var query3 = from c in allDebitAccountTransactions
join a in allAccounts where c.Account equals a
select new { a.Name, c.DebitAmount };
However, if you have more control over your cache, and only allow AccountTransaction objects to be present if their associated Account objects are present, then you can push the join operation to the datasource and do another one in memory, merging the results:
var datasourceResults = from c in Session.CreateLinq<AccountTransaction>()
join a in Session.CreateLinq<Account>() on c.Account equals a
where c.DebitAmount >= 0
select new { a.Name, c.DebitAmount, c.Id };
var cacheResults = from c in Session.GetAllCached<AccountTransaction>()
join a in Session.GetAllCached<Account>() on c.Account equals a
where c.DebitAmount >= 0
select new { a.Name, c.DebitAmount, c.Id };
var query3 = cacheResults.Union(datasourceResults)
.Select(x => new { x.Name, x.DebitAmount });
I think. I am not an expert in LINQ, so I'm curious to see other responses.

Categories