select a.ObjectFieldID,
a.FieldName,
b.RelationName
from tblMNG_Framework_ObjectField as a left join
tblMNG_Framework_ObjectRelation as b on a.ObjectID = b.RelatedObjectID
and a.ObjectFieldID = b.RelatedKeyFieldID
where a.ObjectID = 2
and a.Deleted = 0
It's my quick code, I have no data to test so feedback me with your data sample if left join is not exact.
tblMNG_Framework_ObjectField.GroupJoin(tblMNG_Framework_ObjectRelation,
a => new { JoinCol1 = a.ObjectID, JoinCol2 = a.ObjectFieldID },
b => new { JoinCol1 = b.RelatedObjectID, JoinCol2 = b.RelatedKeyFieldID },
(a, b) => new { a, b }).Where(x => x.a.ObjectID == 2 && x.a.Deleted == 0)
.SelectMany(b => b.b.DefaultIfEmpty(),
(a, b) => new { a.a.ObjectID, a.a.FieldName, b.RelationName });
Hope it helps
Related
var commodity = _appDbContext.ArchivesCCommodity.Where(lambda)
.GroupJoin(_appDbContext.ArchivesCCommoditySpecification, a => a.Code, b => b.Commodity, (a, b) => new { a, b })
.SelectMany(a => a.b.DefaultIfEmpty(), (a, b) => new { a.a, b })
.GroupJoin(_appDbContext.ArchivesCSpecificationDetail, a => a.a.a.b.SpecificationDetail, d => d.Code, (a, d) => new { a, d })
.SelectMany(a => a.d.DefaultIfEmpty(), (a, d) => new
{
Commodity = a.a.a.Code,
CommodityName = a.a.a.Name,
SpecificationDetailName = d.Name,
OrderSN = d.OrderSN
}).AsQueryable().OrderBy(a => a.OrderSN).GroupBy(a => new { a.Commodity, a.CommodityName })
.Select(a => new
{
Commodity = a.Key.Commodity,
CommodityName = a.Key.CommodityName,
SpecificationDetailName = string.Join(" - ", a.Select(a => a.SpecificationDetailName)),
SpecificationDetailTotal = string.Join(" - ", a.Select(a => a.SpecificationDetailName)) == "" ? 0 : a.Count()
});
Where .AsQueryable() will cause an error
.AsQueryable()
.OrderBy(a => a.OrderSN)
.GroupBy(a => new { a.Commodity, a.CommodityName })
No error will be reported when changing to AsEnumerable()
.ASEnumerable()
.OrderBy(a => a.OrderSN)
.GroupBy(a => new { a.Commodity, a.CommodityName })
But I don't want to send this code to the database for the time being, because it will be sent after paging query. I don't know how to deal with it?
//////////////I pasted my complete code and talked about my actual needs
Query the code and query the database page by page. For example, only one page and 10 rows of records are checked. Here is OK.
var AA= _appDbContext.ArchivesCCommodity.Where(lambda)
.GroupJoin(_appDbContext.ArchivesCCommoditySpecification, a => a.Code, b => b.Commodity, (a, b) => new { a, b })
.SelectMany(a => a.b.DefaultIfEmpty(), (a, b) => new { a.a, b })
.GroupJoin(_appDbContext.ArchivesCSpecificationDetail, a => a.a.b.SpecificationDetail, d => d.Code, (a, d) => new { a, d })
.SelectMany(a => a.d.DefaultIfEmpty(), (a, d) => new
{
Commodity = a.a.a.a.a.Code,
CommodityName = a.a.a.a.a.Name,
SpecificationDetailName = d.Name,
OrderSN = d.OrderSN
});
PageHealper<object> page = new PageHealper<object>();
page.Start(pageNum, pageSize);
page = await page.RestPage(AA);
At this time, I grouped and sorted again, and now I found that:
It is not to operate the paging query results, but to query all the AA databases.
Based on the previous pagination query, the number of rows and page numbers are obtained. Here, the number of rows is changed by grouping and merging.
That's why I want to put grouping and sorting together, and finally pagination.
var BB = AA.AsEnumerable().OrderBy(a => a.OrderSN).GroupBy(a => new { a.Commodity, a.CommodityName, a.Specification, a.SpecificationName })
.Select(a => new
{
Commodity = a.Key.Commodity,
CommodityName = a.Key.CommodityName,
SpecificationDetailName = string.Join(" - ", a.Select(a => a.SpecificationDetailName)),
SpecificationDetailTotal = string.Join(" - ", a.Select(a => a.SpecificationDetailName)) == "" ? 0 : a.Count()
}); ;
page.Data = BB.ToList<object>();
return page;
Checkout this article https://weblogs.asp.net/zeeshanhirani/using-asqueryable-with-linq-to-objects-and-linq-to-sql about what AsQueryable does.
I think you dont really need AsQueryable there... LINQ to SQL does not like something about that query.
It does not like the String.Join(...) because it cannot translate it.
So one thing you can do is put .AsEnumerable() after the GroupBy() this will do everything up to in SQL and everything after in memory.
Ex:
var commodity = _appDbContext.ArchivesCCommodity.Where(lambda)
.GroupJoin(_appDbContext.ArchivesCCommoditySpecification, a => a.Code, b => b.Commodity, (a, b) => new { a, b })
.SelectMany(a => a.b.DefaultIfEmpty(), (a, b) => new { a.a, b })
.GroupJoin(_appDbContext.ArchivesCSpecificationDetail, a => a.a.a.b.SpecificationDetail, d => d.Code, (a, d) => new { a, d })
.SelectMany(a => a.d.DefaultIfEmpty(), (a, d) => new
{
Commodity = a.a.a.Code,
CommodityName = a.a.a.Name,
SpecificationDetailName = d.Name,
OrderSN = d.OrderSN
}).OrderBy(a => a.OrderSN).GroupBy(a => new { a.Commodity, a.CommodityName })
.AnEnumerable()
.Select(a => new
{
Commodity = a.Key.Commodity,
CommodityName = a.Key.CommodityName,
SpecificationDetailName = string.Join(" - ", a.Select(a => a.SpecificationDetailName)),
SpecificationDetailTotal = string.Join(" - ", a.Select(a => a.SpecificationDetailName)) == "" ? 0 : a.Count()
});
This question is similar, but it does not apply to my case, since the user needed the merge observable streams from the same IGroupedObservable, while I want to combine streams from different groups.
I have the following structures and streams:
type A = {
Id: int
Value: int
}
type B = {
Id: int
Value: int
}
//subjects to test input, just any source of As and Bs
let subjectA: Subject<A> = Subject.broadcast
let subjectB: Subject<B> = Subject.broadcast
//grouped streams
let groupedA: IObservable<<IGroupedObservable<int, A>> = Observable.groupBy (fun a -> a.Id) subjectA
let groupedB: IObservable<<IGroupedObservable<int, B>> = Observable.groupBy (fun b -> b.Id) subjectB
I want to somehow merge the internal observables of A and B when groupedA.Key = groupedB.Key, and get an observable of (A, B) pairs where A.Id = B.Id
The signature I want is something like
IObservable<IGroupedObservable<int, A>> -> IObservable<IGroupedObservable<int, B>> -> IObservable<IGroupedObservable<int, (A, B)>> where for all (A, B), A.Id = B.Id
I tried a bunch of combineLatest, groupJoin, filters and maps variations, but with no success.
I'm using F# with Rx.Net and FSharp.Control.Reactive, but if you know the answer in C# (or any language, really) please post it
Here is a custom operator GroupJoin that you could use. It is based on the Select, Merge, GroupBy and Where operators:
/// <summary>
/// Groups and joins the elements of two observable sequences, based on common keys.
/// </summary>
public static IObservable<(TKey Key, IObservable<TLeft> Left, IObservable<TRight> Right)>
GroupJoin<TLeft, TRight, TKey>(
this IObservable<TLeft> left,
IObservable<TRight> right,
Func<TLeft, TKey> leftKeySelector,
Func<TRight, TKey> rightKeySelector,
IEqualityComparer<TKey> keyComparer = null)
{
// Arguments validation omitted
keyComparer ??= EqualityComparer<TKey>.Default;
return left
.Select(x => (x, (TRight)default, Type: 1, Key: leftKeySelector(x)))
.Merge(right.Select(x => ((TLeft)default, x, Type: 2, Key: rightKeySelector(x))))
.GroupBy(e => e.Key, keyComparer)
.Select(g => (
g.Key,
g.Where(e => e.Type == 1).Select(e => e.Item1),
g.Where(e => e.Type == 2).Select(e => e.Item2)
));
}
Usage example:
var subjectA = new Subject<A>();
var subjectB = new Subject<B>();
IObservable<IGroupedObservable<int, (A, B)>> query = subjectA
.GroupJoin(subjectB, a => a.Id, b => b.Id)
.SelectMany(g => g.Left.Zip(g.Right, (a, b) => (g.Key, a, b)))
.GroupBy(e => e.Key, e => (e.a, e.b));
I'm not clear if this is what you want. So it may be helpful to clarify first with runner code. Assuming the following runner code:
var aSubject = new Subject<A>();
var bSubject = new Subject<B>();
var groupedA = aSubject.GroupBy(a => a.Id);
var groupedB = bSubject.GroupBy(b => b.Id);
//Initiate solution
solution.Merge()
.Subscribe(t => Console.WriteLine($"(Id = {t.a.Id}, AValue = {t.a.Value}, BValue = {t.b.Value} )"));
aSubject.OnNext(new A() { Id = 1, Value = 1 });
aSubject.OnNext(new A() { Id = 1, Value = 2 });
bSubject.OnNext(new B() { Id = 1, Value = 10 });
bSubject.OnNext(new B() { Id = 1, Value = 20 });
bSubject.OnNext(new B() { Id = 1, Value = 30 });
Do you want to see the following output:
(Id = 1, AValue = 1, BValue = 10)
(Id = 1, AValue = 2, BValue = 10)
(Id = 1, AValue = 1, BValue = 20)
(Id = 1, AValue = 2, BValue = 20)
(Id = 1, AValue = 1, BValue = 30)
(Id = 1, AValue = 2, BValue = 30)
If that's the case, you can get to solution as follows:
var solution = groupedA.Merge()
.Join(groupedB.Merge(),
_ => Observable.Never<Unit>(),
_ => Observable.Never<Unit>(),
(a, b) => (a, b)
)
.Where(t => t.a.Id == t.b.Id)
.GroupBy(g => g.a.Id);
I'll caution that there are memory/performance impacts here if this is part of a long-running process. This keeps all A and B objects in memory indefinitely, waiting to see if they can be paired off. To shorten the amount of time they're kept in memory, change the Observable.Never() calls to appropriate windows for how long to keep each object in memory.
As a start, this has the signature you want:
let cartesian left right =
rxquery {
for a in left do
for b in right do
yield a, b
}
let mergeGroups left right =
rxquery {
for (leftGroup : IGroupedObservable<'key, 'a>) in left do
for (rightGroup : IGroupedObservable<'key, 'b>) in right do
if leftGroup.Key = rightGroup.Key then
let merged = cartesian leftGroup rightGroup
yield {
new IGroupedObservable<_, _> with
member __.Key = leftGroup.Key
member __.Subscribe(observer) = merged.Subscribe(observer)
}
}
However, in my testing, the groups are all empty. I don't have enough Rx experience to know why, but perhaps someone else does.
So I have the following data table:
Region Class
Reg100 A
Reg100 B
Reg200 A
Reg300 B
Where I want to determine a region that has two classes A and B. In this case it would be Reg100. How could I write this in using lambda expression?
I have tried something like below but not getting what I want.
dt.Where(x => x.Class.Contains(listOfAandB).GroupBy(x=>x.Region).FirstOrDefault()
Lets define the input:
class Data
{
public string Region;
public string Class;
}
var dt = new[]
{
new Data { Region = "Reg100", Class = "A" },
new Data { Region = "Reg100", Class = "B" },
new Data { Region = "Reg200", Class = "A" },
new Data { Region = "Reg300", Class = "B" },
};
Now, using GroupBy we can group the input by Region.
dt.GroupBy(x => x.Region)
This yields { Reg100 (A, B), Reg200 A, Reg 300 B }. Now we look if we can find both A and B:
dt.GroupBy(x => x.Region)
.Where(g => g.Any(x => x.Class == "A") && g.Any(x => x.Class == "B"))
And finally as we are only interested in the region, we project to it:
dt.GroupBy(x => x.Region)
.Where(g => g.Any(x => x.Class == "A") && g.Any(x => x.Class == "B"))
.Select(g => g.Key);
We have the following definition LINQ:
myList.Select(s=> new DtoTest()
{
TotalSamples = myList.Count(c=> c.UserId == s.UserId),
EvaluatedSamples = myList.Count(c=> c.UserId == s.UserId && c.Status == Status.OK)
PercentageRealized = (myList.Count(c=> c.UserId == s.UserId) / myList.Count(c=> c.UserId == s.UserId && c.Status == Status.OK)) * 100
});
Is there a way where you can assign the property value "PercentageRealized" without using the same functions previously used in: "TotalSamples" & "EvaluatedSamples"?
Something like that:
myList.Select(s=> new DtoTest()
{
TotalSamples = myList.Count(c=> c.UserId == s.UserId),
EvaluatedSamples = myList.Count(c=> c.UserId == s.UserId && c.Status == Status.OK)
PercentageRealized = (TotalSamples / EvaluatedSamples) * 100 //<-!Not possible!
});
Any other tips?
Change the function delegate to use the already calculated values
myList.Select(s => {
var result = new DtoTest() {
TotalSamples = myList.Count(c => c.UserId == s.UserId),
EvaluatedSamples = myList.Count(c => c.UserId == s.UserId && c.Status == Status.OK)
};
result.PercentageRealized = (result.TotalSamples / result.EvaluatedSamples) * 100;
return result;
});
If you were using an anonymous type this would be more complicated, but since DtoTest is a class, you could always move your math into the property.
public class DtoTest
{
public float PercentageRealized
{
get { return (TotalSamples / EvaluatedSamples) * 100; }
}
}
If PercentageRealized is that simple calculations, why not have the calculation in the property in the class, something like this inside the class DTOTest:
public float PercentageRealized => (TotalSamples / EvaluatedSamples) * 100;
What you are doing seems extremely questionable to me - you are making multiple passes over the source data to re-compute the same thing over and over again (per occurrence of UserId), when it seems like what you should want is to compute once per UserId, like so:
var ans2 = myList.GroupBy(s => s.UserId)
.Select(sg => {
var ts = sg.Count();
var es = sg.Count(c => c.Status == Status.OK);
return new DtoTest { UserId = sg.Key, TotalSamples = ts, EvaluatedSamples = es, PercentageRealized = (int)(100.0 * ts / es) };
});
Also, your percentage calculation will use C# integer division and not be close to correct, unless you convert to double first. You can cast back to int after the math.
If you really intended to return multiple results, and want to be efficient (and since I love extension methods), creating an extension method to count chained predicates for one pass:
public static class IEnumerableExt {
public static (int Cond1Count, int Cond2Count) Count2Chained<T>(this IEnumerable<T> src, Func<T, bool> cond1, Func<T, bool> cond2) {
int cond1Count = 0;
int cond2Count = 0;
foreach (var s in src) {
if (cond1(s)) {
++cond1Count;
if (cond2(s))
++cond2Count;
}
}
return (cond1Count, cond2Count);
}
}
Now you can count the sub-values in one pass and compute the third value:
var ans3 = myList.Select(s => {
var (ts, es) = myList.Count2Chained(c => c.UserId == s.UserId, c => c.Status == Status.OK);
return new DtoTest { UserId = s.UserId, TotalSamples = ts, EvaluatedSamples = es, PercentageRealized = (int)(100.0 * ts / es) };
});
Of course, depending on the size of myList, you may still be better off calculating each answer once and then repeating them for the final answer:
var ansd = myList.GroupBy(s => s.UserId)
.Select(sg => {
var ts = sg.Count();
var es = sg.Count(c => c.Status == Status.OK);
return new { sg.Key, ts, es };
})
.ToDictionary(ste => ste.Key, ste => new DtoTest {
UserId = ste.Key,
TotalSamples = ste.ts,
EvaluatedSamples = ste.es,
PercentageRealized = (int)(100.0 * ste.ts / ste.es) });
var ans4 = myList.Select(s => ansd[s.UserId]);
first project to a tuple then project to your custom object:
myList.Select(s => (tSample: myList.Count(c=> c.UserId == s.UserId),
eSample : myList.Count(c=> c.UserId == s.UserId && c.Status == Status.OK)))
.Select(x => new DtoTest
{
TotalSamples = x.tSample,
EvaluatedSamples = x.eSample,
PercentageRealized = (x.tSample / x.eSample) * 100
});
or use an anonymous type:
myList.Select(s => new
{
tSample = myList.Count(c=> c.UserId == s.UserId),
eSample = myList.Count(c=> c.UserId == s.UserId && c.Status == Status.OK)
})
.Select(x => new DtoTest
{
TotalSamples = x.tSample,
EvaluatedSamples = x.eSample,
PercentageRealized = (x.tSample / x.eSample) * 100
});
i'm trying rewrite query from native sql to linq lambda syntax (not linq query syntax)
LINQ (not work)
var result = _uow.Repository<TableA>().Get().AsNoTracking()
.GroupJoin(
_uow.Repository<TableB>().Get().AsNoTracking(),
a => new { a.TabNotesCodeId, a.TabLabelCodeId },
b => b.ElementNameId
(b, a) => new SubSection
{
SubSectionName = b.CustomValue ?? a.TabLabelCodeId,
SubSectionNote = b.CustomValue ?? a.TabLabelCodeId,
})
.Where(a => a.ResourceId == 1);
SQL
SELECT [SubSectionName] = ISNULL(B.CUSTOMVALUE,A.TABLABELCODEID),
[SubSectionNote] = ISNULL(B.CUSTOMVALUE,A.TABNOTESCODEID)
FROM TableA as A LEFT JOIN
(SELECT CUSTOMVALUE, ELEMENTNAMEID FROM TableB WHERE DISPLAYSETTINGID = 1) as B
ON B.ELEMENTNAMEID IN ( A.TABNOTESCODEID, A.TABLABELCODEID)
WHERE A.RESOURCEID = 1
Q How to rewrite sql ON B.ELEMENTNAMEID IN ( A.TABNOTESCODEID, A.TABLABELCODEID) to lambda syntax
...
a => new { a.TabNotesCodeId, a.TabLabelCodeId },
b => b.ElementNameId
....
(doesn't work)
Normally I would suggest following my SQL conversion rules, but this is sufficiently complex I don't think it would help.
To use query comprehension syntax on a non-equijoin that is a left join, it seems easiest to use lambda syntax to express the join conditions, so I just combined the sub-query with the join conditions:
var ans = from A in TableA
where A.ResourceID == 1
from B in TableB.Where(b => b.DisplaySettingID == 1).Where(b => b.ElementNameID == A.TabNotesCodeID || b.ElementNameID == A.TabLabelCodeID).DefaultIfEmpty()
select new {
SubSectionName = (B.CustomValue ?? A.TabLabelCodeID),
SubSectionNote = (B.CustomValue ?? A.TabNotesCodeID)
};
The lambda equivalent of multiple from clauses to generate a cross join is SelectMany, so converting into lambda syntax:
var ans2 = TableA.Where(a => a.ResourceID == 1)
.SelectMany(a => TableB.Where(b => b.DisplaySettingID == 1).Where(b => b.ElementNameID == a.TabNotesCodeID || b.ElementNameID == a.TabLabelCodeID)
.DefaultIfEmpty(),
(a, b) => new {
SubSectionName = (b.CustomValue ?? a.TabLabelCodeID),
SubSectionNote = (b.CustomValue ?? a.TabNotesCodeID)
}
);
After countless experiments i've found out solution:
_uow.Repository<TableA>().Get().AsNoTracking()
.GroupJoin(
_uow.Repository<TableB>().Get().AsNoTracking().Where(b => b.DisplaySettingId == 1),
a => new { note = a.TabNotesCodeId, label = a.TabLabelCodeId },
b => new { note = b.ElementNameId, label = b.ElementNameId },
(a, b) => new { a,b })
.Where(joinTables => joinTables.a.ResourceId == 1)
.SelectMany(
joinTables => joinTables.b.DefaultIfEmpty(),
(joinTables, b) => new SubSection()
{
LayoutTab = joinTables.a.LayoutTab,
SubSectionName = b.CustomValue ?? joinTables.a.TabLabelCodeId,
SubSectionNote = b.CustomValue ?? joinTables.a.TabNotesCodeId
});