Cast after Join - c#

I need to add some information to an entity before returning it via GET.
I created a class, kind of like a ViewModel, with the original entity plus one int field, to return the complete data to a client, but was enable to make it work. I also don't want to return the Depth field, but need it in the Where.
How do I go from this Join to correctly returning the data to a client?
Is iterating and copying item by item the only way?
public class FluxoHierarchyOutput
{
public FluxoHierarchy FluxoHierarchy { get; set; }
public int ParentId { get; set; }
}
public IEnumerable<FluxoHierarchyOutput> GetFluxoHierarchyByFluxo([FromRoute] int idFluxo)
{
return _context.FluxoHierarchy
.Join(
_context.FluxoClosure,
h => h.NodeId,
c => c.ChildId,
(h, c) => new { FluxoHierarchy = h, c.ParentId, c.Depth }
)
.Where(x => x.FluxoHierarchy.FluxoId == idFluxo && x.Depth == 1)
.ToList(); // Cannot implicitly convert type...
}

Try projecting to your concrete type, rather than an anonymous type.
(h, c) => new FluxoHierarchyOutput { FluxoHierarchy = h, c.ParentId, c.Depth }
You're going to need a Select in there somewhere, probably just before the ToList() call. Otherwise, your result will be the entire join.

The fluent API is not the prettiest with joins. Here is the same thing using query syntax. You completely avoid having to specify an intermediate result prior to the select.
return (
from h in _context.FluxoHierarchy
join c in _context.FluxoClosure on h.NodeId equals c.ChildId
where h.FluxoId == idFluxo && c.Depth == 1
select new FluxoHierarchyOutput {
FluxoHierarchy = h,
ParentId = c.ParentId
}).ToList();

Without a Select, your query returns an anonymous object, which then will have to be converted to your actual return type. This conversion cannot be implicit as the error message is telling you. Add a Select to the end of your query and it will work.
return _context.FluxoHierarchy
.Join(
_context.FluxoClosure,
h => h.NodeId,
c => c.ChildId,
(h, c) => new { FluxoHierarchy = h, c.ParentId, c.Depth }
)
.Where(x => x.FluxoHierarchy.FluxoId == idFluxo && x.Depth == 1)
.Select(s => new FluxoHierarchyOutput {
FluxoHierarchy = s.FluxoHierarchy,
ParentId = s.ParentId,
//Add all the fields you want.
})
.ToList();
You have the same issue in the Join as Robert Harvey explained.

Related

Converting SQL with LEFT JOIN to Linq (Method Syntax)

It's my first post here, so if I get anything wrong let me know and I'll fix it.
I'm struggling to convert a simple SQL statement with a left join, to a LINQ statement (Method syntax). I cannot use Linquer since this is a .Net Core 5.0 MVC project.
Consider that I have two tables:
dbo.OrganisationChannel (Id, OrganisationId, ChannelId)
dbo.Channel (Id, ChannelName, ChannelUrl)
I want to show all channels that an organisation DOESN'T currently have.
Here is the correct SQL query
SELECT c.Id, c.ChannelName, c.ChannelUrl
FROM dbo.Channel c
LEFT JOIN dbo.OrganisationChannel oc ON c.Id = oc.ChannelId
WHERE oc.ChannelId IS NULL OR oc.OrganisationId <> 1
However, the corresponding .GroupJoin and .SelectMany is perplexing me.. I can't find the right place to add the WHERE clauses:
var groupItems = db.Channel
.GroupJoin(
db.OrganisationChannel,
c => c.Id,
oc => oc.ChannelId,
(c, oc) => new { c, oc })
.SelectMany(
x => x.oc.DefaultIfEmpty(),
(chan, orgChan) => new
{
Id = chan.c.Id,
ChannelName = chan.c.ChannelName,
ChannelUrl = chan.c.ChannelUrl,
IsActive = chan.c.IsActive,
}
);
I'd be grateful for any help here, thanks!
Si
Method syntax with LEFT JOIN is a nightmare. If you really want method syntax install Reshaper and click "convert to method chain". But I do not recommend to do that - query become unmaintainable.
Your query is simple with query syntax
var query =
from c in db.Channel
join oc in db.OrganisationChannel on c.Id equals oc.ChannelId into gj
from oc in gj.DefaultIfempty()
where (int?)oc.ChannelId == null || oc.OrganisationId != 1
select new
{
c.Id,
c.ChannelName,
c.ChannelUrl
};
You can use the LeftJoin extension method :
public static IQueryable<TResult> LeftJoin<TResult, Ta, Tb, TKey>(this IQueryable<Ta> TableA, IEnumerable<Tb> TableB, Expression<Func<Ta, TKey>> outerKeySelector, Expression<Func<Tb, TKey>> innerKeySelector, Expression<Func<JoinIntermediate<Ta, Tb>, Tb, TResult>> resultSelector)
{
return TableA.GroupJoin(TableB, outerKeySelector, innerKeySelector, (a, b) => new JoinIntermediate<Ta, Tb> { Value = a, ManyB = b }).SelectMany(intermediate => intermediate.ManyB.DefaultIfEmpty(), resultSelector);
}
public class JoinIntermediate<Ta, Tb>
{
public Ta Value { get; set; }
internal IEnumerable<Tb> ManyB { get; set; }
}
It's usage is similar to the Join extension method but will perform a left join instead of a regular join. Then you can add your call to the Where method right after the call to LeftJoin.
Use the following query instead of lambda expressions
from left in lefts
join right in rights on left equals right.Left into leftRights
from leftRight in leftRights.DefaultIfEmpty()
select new { }
check this url https://dotnettutorials.net/lesson/left-outer-join-in-linq/
also my working code example:
UserApiKeys
.Where(w => w.AppID == AppID && w.IsActive)
.Join(
UserApiApplications,
keys => keys.AppID,
apps => apps.AppID,
(keys, apps) => new { UserApiKeys = keys, UserApiApplications = apps}
)
.OrderByDescending(d => (d.UserApiKeys.ExpirationDate ?? DateTime.MaxValue))
.Select(s => new {
ApiKey = s.UserApiKeys.ApiKey,
IsActive = s.UserApiKeys.IsActive,
SystemName = s.UserApiKeys.SystemName,
ExpirationDate = (s.UserApiKeys.ExpirationDate == null)
? "Newer Expires"
: s.UserApiKeys.ExpirationDate.ToString(),
s.UserApiApplications
})
.ToList()
in addition, to refer #nalka post about extension method usage:
NotificationEvents
.Where(w => w.ID == 123)
.LeftJoin(
Events,
events => events.EventID, ev => ev.EventID,
(events, ev) => new { NotificationEvents = events, Events = ev }
);

Turn SQL into Lambda/Linq

I've been trying to turn a fairly basic piece of SQL code into Lamda or Linq but I'm getting nowhere. Here is the SQL query:
SELECT * FROM Form a
INNER JOIN FormItem b ON a.FormId = b.FormId
INNER JOIN FormFee c ON a.FormId = c.FormId
INNER JOIN FeeType d ON c.FeeTypeId = d.FeeTypeId
WHERE b.StatusId = 7
I tried this but it isn't doing what I want.
public Form GetFormWithNoTracking(int id)
{
return ObjectSet
.Where(x => x.FormId == id &&
(x.FormItem.Any(di => di.StatusId == (short)Status.Paid)))
.AsNoTracking()
.FirstOrDefault();
}
I'm trying to return only the rows from FormItem whose StatusId is Paid. However, the above returns all. I know that .Any() will check if there are any matches and if there are return all, so in this case my data, for this form, does have items who have a StatusId of Paid and some items whose StatusId is not paid so it brings back them all.
var query = (from a in ObjectSet.FormA
join b in ObjectSet.FormB on a.field equals b.field
where b.StatusId = 7
select new { a, b})
You can join rest with same logic.
This should be what you are asking for:
Get the Form with FormId = id
Of that form, return all FormItems that have StatusId = Paid
public IEnumerable<FormItem> GetFormWithNoTracking(int id)
{
return ObjectSet
.SingleOrDefault(x => x.FormId == id)
.Select(f => f.FormItem
.Where(di => di.StatusId == (short)Status.Paid))
.AsNoTracking();
}
If you need the Form itself too, you might want to create a custom type (edit: see #Burk's answer) or return a Tuple<Form,IEnumerable<FormItem>>, a IEnumerable<Tuple<Form,FormItem>> or whatever suits your needs best instead.
Alternatively you could remove all non-paid items of the form.
public Form GetFormWithNoTracking(int id)
{
var form = ObjectSet
.SingleOrDefault(x => x.FormId == id)
.AsNoTracking();
var nonPaid = form.Select(f => f.FormItem
.Where(di => di.StatusId != (short)Status.Paid)).ToList();
foreach(FormItem item in nonPaid)
form.FormItem.Remove(item);
return form;
}

The type of the arguments cannot be inferred usage Linq GroupJoin

I'm trying to make a linq GroupJoin, and I receive the fore mentioned error. This is the code
public Dictionary<string, List<QuoteOrderline>> GetOrderlines(List<string> quoteNrs)
{
var quoteHeadersIds = portalDb.nquote_orderheaders
.Where(f => quoteNrs.Contains(f.QuoteOrderNumber))
.Select(f => f.ID).ToList();
List<nquote_orderlines> orderlines = portalDb.nquote_orderlines
.Where(f => quoteHeadersIds.Contains(f.QuoteHeaderID))
.ToList();
var toRet = quoteNrs
.GroupJoin(orderlines, q => q, o => o.QuoteHeaderID, (q => o) => new
{
quoteId = q,
orderlines = o.Select(g => new QuoteOrderline()
{
Description = g.Description,
ExtPrice = g.UnitPrice * g.Qty,
IsInOrder = g.IsInOrder,
PartNumber = g.PartNo,
Price = g.UnitPrice,
ProgramId = g.ProgramId,
Quantity = (int)g.Qty,
SKU = g.SKU
}).ToList()
});
}
I suspect this is the immediate problem:
(q => o) => new { ... }
I suspect you meant:
(q, o) => new { ... }
In other words, "here's a function taking a query and an order, and returning an anonymous type". The first syntax simply doesn't make sense - even thinking about higher ordered functions, you'd normally have q => o => ... rather than (q => o) => ....
Now that won't be enough on its own... because GroupJoin doesn't return a dictionary. (Indeed, you don't even have a return statement yet.) You'll need a ToDictionary call after that. Alternatively, it may well be more appropriate to return an ILookup<string, QuoteOrderLine> via ToLookup.

Group by, Count and Lambda Expression

I am trying to translate the following query:
SELECT STATE, COUNT(*)
FROM MYTABLE
GROUP BY STATE;
Into a lambda expression. I am using C# and EntityFramework, however it doesnt seem I can make it work. Here is what I have on my respository so far:
public IEnumerable<object> PorcentajeState(Guid id)
{
return _context.Sates.Where(a => a.Id == id)
.GroupBy(a => a.State)
.Select(n => new { n.StateId , n.Count() });
}
Of course it doesnt compile and I am lost after googling for 2 hours . Could you please help me?
thanks in advance
There are two issues here:
The result of GroupBy will will be an enumerable of type IEnumerable<IGrouping<TKey, TSource>>. The IGrouping interface only has one property you can access, Key which is the key you specified in the GroupBy expression, and implements IEnumerable<T> so you can do other Linq operations on the result.
You need to specify a property name for the anonymous type if it cannot be inferred from a property or field expression. In this case, you're calling Count on the IGrouping, so you need to specify a name for that property.
Try this:
public IEnumerable<object> PorcentajeState(Guid id)
{
return _context.Sates.Where(a => a.Id == id)
.GroupBy(a => a.StateId)
.Select(g => new { g.Key, Count = g.Count() });
}
The equivalent in query syntax would be
public IEnumerable<object> PorcentajeState(Guid id)
{
return from a in _context.Sates
where a.Id == id
group a by a.StateId into g
select new { a.Key, Count = g.Count() };
}
In either case, if you want the first property to be named StateId instead of Key, just change that to
new { StateId = g.Key, Count = g.Count() }
This one is good
public IEnumerable<object> PorcentajeState(Guid id)
{
return _context.Sates.Where(a => a.Id == id)
.GroupBy(a => a.StateId)
.Select(g => new { g.Key, Count = g.Count() });
}
But try this.
public IEnumerable<object> PorcentajeState(Guid id)
{
return _context.Sates.Where(a => a.Id == id)
.GroupBy(a => a.StateId)
.Select(g => new { g.Key.StateId, Count = g.Count() });
}

Is it possible to use Select(l=> new{}) with SelectMany in EntityFramework

I am trying something that i not really sure but i want to ask here if it s possible.
Is it able to be done ?
public IQueryable<Info> GetInfo(int count, byte languageId)
{
return db.Info.SelectMany(i => i.LanguageInfo)
.Where(l => l.Language.id == languageId)
.Select(l => new Info { AddDate = l.Info.AddDate,
Description = l.Description,
EntityKey = l.Info.EntityKey,
id = l.Info.id,
Title = l.Title,
ViewCount = l.Info.ViewCount }
)
.OrderByDescending(i => i.id)
.Take(count);
}
When this method is executed i got an error
The entity or complex type
'GuideModel.Info' cannot be
constructed in a LINQ to Entities
query.
Does it mean "not possible" ?
Thank you
The error essentially indicates that the Entity Framework doesn't know how to create an Info object, since it is not bound to a table object. (Put another way, the Select call on the IQueryable cannot be translated into equivalent SQL.) You could perform the Select projection on the client via:
public IQueryable<Info> GetInfo(int count, byte languageId)
{
return db.Info.SelectMany(i => i.LanguageInfo)
.Where(l => l.Language.id == languageId)
.Take(count)
.AsEnumerable()
.Select(l => new Info { AddDate = l.Info.AddDate,
Description = l.Description,
EntityKey = l.Info.EntityKey,
id = l.Info.id,
Title = l.Title,
ViewCount = l.Info.ViewCount }
)
.OrderByDescending(i => i.id);
}
It is possible to use Select(l => new ...), but not with an Entity type. You need to use an anonymous type or a POCO type with a parameterless constructor. Entity types are "special" because of the way they interact with the ObjectContext. You can select them, but not new them up in a query.
The code below worked for me. Here "SearchTerm" is a complex type. Thanks Jason :)
var lstSynonym = TechContext.TermSynonyms
.Where(p => p.Name.StartsWith(startLetter))
.AsEnumerable()
.Select(u => new SearchTerm
{
ContentId = u.ContentId,
Title = u.Name,
Url = u.Url
});

Categories