Unable to translate set operation after client projection has been applied - c#

I have a method within my repository which queries two separate join tables. I am trying to combine the two IQueryables before converting them into Lists but I am receiving the above error message in the console.
Here is the method within the repository:
public async Task<PagedList<EventDto>> GetUserOrganisedEvents(EventParams eventParams)
{
var user = await _userRepository.GetUserByUsernameAsync(eventParams.username);
var organisedEventsquery = _context.UserEvents.AsQueryable();
var organisedEvents = organisedEventsquery.Where(events => events.OrganiserId == user.Id);
var AttendedEventsquery = _context.EventUsers.AsQueryable();
var AttendedEvents = AttendedEventsquery.Where(events => events.AttendeeId == user.Id);
var organisedEventsList = organisedEvents.Select(organisedEvent => new EventDto
{
Name = organisedEvent.Event.Name,
Id = organisedEvent.EventId,
Date = organisedEvent.Event.Date,
Location = organisedEvent.Event.Location,
MainPhotoUrl = organisedEvent.Event.MainPhotoUrl,
Creator = _mapper.Map<MemberDto>(organisedEvent.Event.Creator),
Organisers = _mapper.Map<ICollection<MemberDto>>(organisedEvent.Event.Organisers),
Attendees = _mapper.Map<ICollection<MemberDto>>(organisedEvent.Event.Attendees)
});
var AttendedEventsList = AttendedEvents.Select(AttendedEvent => new EventDto
{
Name = AttendedEvent.AttendingEvent.Name,
Id = AttendedEvent.AttendingEventId,
Date = AttendedEvent.AttendingEvent.Date,
Location = AttendedEvent.AttendingEvent.Location,
MainPhotoUrl = AttendedEvent.AttendingEvent.MainPhotoUrl,
Creator = _mapper.Map<MemberDto>(AttendedEvent.AttendingEvent.Creator),
Organisers = _mapper.Map<ICollection<MemberDto>>(AttendedEvent.AttendingEvent.Organisers),
Attendees = _mapper.Map<ICollection<MemberDto>>(AttendedEvent.AttendingEvent.Attendees)
});
var events = organisedEventsList.Concat(AttendedEventsList);
return await PagedList<EventDto>.CreateAsync(events, eventParams.PageNumber, eventParams.PageSize);
}
Here is the CreateAsync method:
public static async Task<PagedList<T>> CreateAsync(IQueryable<T> source, int pageNumber, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
return new PagedList<T>(items, count, pageNumber, pageSize);
}

I came across this error too.
Much simpler query.
var query1 = _context.Set<TableA>()
.Select(x => NewMethod(x));
var query2 = _context.Set<TableB>()
.Select(x => NewMethod1(x));
private static ViewModel NewMethod1(TableA x)
{
return new ViewModel()
{
Number = x.Number,
Date = x.Date
};
}
private static ViewModel NewMethod(TableB x)
{
return new SalesLedgerRecord()
{
Number = x.Number,
Date = x.Date
};
}
The shown code above will fail with the error message in the subject if I union the results:
var query = query1.Union(query2)
.OrderBy(OrderBy).AsNoTracking();
However, if I remove the two private static functions to have:
var query = _context.Set<TableA>()
.Select(x =>
new ViewModel()
{
Number = x.Number,
Date = x.Date
};
All seems to be working ok.
Also, all works if I leave 'NewMethodX' but remove the union.
Not sure what change extracting to private static changes but it does changes something.

Related

How to make an List of Objects a parameter in a Lamba using C# Expression API

I'm trying to dynamically create an predicate to pass into a linq where clause. This is for a generic method that takes two list of the same Type and and list of property names to compare.
void SomeMethod<T>(List<T> oldRecords, List<T> newRecords, List<string> propertiesOfT)
{
// dynamically build predicate for this
var notMatch = oldRecords.Where(o => !newRecords.Any(n => n.Prop1 == o.Prop1 && n.Prop2 == o.Prop2)).ToList();
// do somethind with notMatch
}
I would like to convert this:
var notMatch = oldRecords.Where(o => !newRecords.Any(n => n.Prop1 == o.Prop1 && n.Prop2 == o.Prop2)).ToList();
To achieve this:
var predicate = "n => n.Prop1 == o.Prop1 && n.Prop2 == o.Prop2"; // sudo code
var notMatch = oldRecords.Where(o => !newRecords.Any(predicate));
or this
var predicate = "o => !newRecords.Any(n => n.Prop1 == o.Prop1 && n.Prop2 == o.Prop2)" // sudo code
var notMatch = oldRecords.Where(predicate);
How do I populate newRecords when dynamically creating the Expression?
And how would I reference parameter o and parameter n in the Expresssion.
I've gotten this far:
//construct the two parameters
var o = Expression.Parameter(typeof(T), "o");
var n = Expression.Parameter(typeof(T), "n");
// How to I go about populating o with values and n with values
// from oldRecords and newRecords? or is that no neccessary
var property = Expression.Property(o, typeof(T).GetProperty("Id").Name);
var value = Expression.Constant(Convert.ChangeType("12345", typeof(T).GetProperty("Id").PropertyType), typeof(T).GetProperty("Id").PropertyType);
BinaryExpression binaryExpression = Expression.MakeBinary(ExpressionType.Equal, property, value);
Any sudo code or clue where to look to achieve this?
With reflection it's quite easy. You just to have to think about it. Here's the working version.
void SomeMethod<T>(List<T> oldRecords, List<T> newRecords, List<string> propertiesOfT)
{
// get the list of property to match
var properties = propertiesOfT.Select(prop => typeof(T).GetProperty(prop)).ToList();
// Get all old record where we don't find any matching new record where all the property equal that old record
var notMatch = oldRecords.Where(o => !newRecords.Any(n => properties.All(prop => prop.GetValue(o).Equals(prop.GetValue(n))))).ToList();
}
And here's a sample set i tried and it works
public class test
{
public int id { get; set; } = 0;
public string desc { get; set; } = "";
public test(string s, int i)
{
desc = s;id = i;
}
}
private void Main()
{
var oldRecords = new List<test>()
{
new test("test",1),
new test("test",2)
};
var newRecords = new List<test>()
{
new test("test1",1),
new test("test",2)
};
SomeMethod(oldRecords, newRecords, new List<string>() { "id", "desc" });
}

Unable to get a distinct list that contains summed values

I have posted this earlier but the objective of what I am trying to achieve seems to have lost hence re-posting it to get explain myself better.
I have a collection that has duplicate productnames with different values. My aim is to get a list that would sum these productnames so that the list contains single record of these duplicates.
For e.g
If the list contains
Product A 100
Product A 200
The result object should contain
Product A 300
So as you can see in my code below, I am passing IEnumerable allocationsGrouped to the method. I am grouping by productname and summing the Emv fields and then looping it so that I created a new list of the type List and pass it to the caller method. The problem what I seeing here is on the following line of code Items = group. Items now contains original list without the sum. Hence the inner foreach loop runs more than ones because there are duplicates which defeats my purpose. I finally need to return result object that has non duplicate values which are summed based on the above criteria. Could you please tell me where I am going wrong.
private static List<FirmWideAllocationsViewModel> CreateHierarchy(string manStratName, IEnumerable<FIRMWIDE_MANAGER_ALLOCATION> allocationsGrouped, List<FirmWideAllocationsViewModel> result)
{
var a = allocationsGrouped
.Where(product => !string.IsNullOrEmpty(product.PRODUCT_NAME))
.GroupBy(product => product.PRODUCT_NAME)
.Select(group => new
{
ProductName = group.Key, // this is the value you grouped on - the ProductName
EmvSum = group.Sum(x => x.EMV),
Items = group
});
var b = a;
var item = new FirmWideAllocationsViewModel();
item.Hierarchy = new List<string>();
item.Hierarchy.Add(manStratName);
result.Add(item);
foreach (var ac in b)
{
var productName = ac.ProductName;
var emvSum = ac.EmvSum;
foreach (var elem in ac.Items)
{
var item2 = new FirmWideAllocationsViewModel();
item2.Hierarchy = new List<string>();
item2.Hierarchy.Add(manStratName);
item2.Hierarchy.Add(elem.PRODUCT_NAME);
item2.FirmID = elem.FIRM_ID;
item2.FirmName = elem.FIRM_NAME;
item2.ManagerStrategyID = elem.MANAGER_STRATEGY_ID;
item2.ManagerStrategyName = elem.MANAGER_STRATEGY_NAME;
item2.ManagerAccountClassID = elem.MANAGER_ACCOUNTING_CLASS_ID;
item2.ManagerAccountingClassName = elem.MANAGER_ACCOUNTING_CLASS_NAME;
item2.ManagerFundID = elem.MANAGER_FUND_ID;
item2.ManagerFundName = elem.MANAGER_FUND_NAME;
item2.Nav = elem.NAV;
item2.EvalDate = elem.EVAL_DATE.HasValue ? elem.EVAL_DATE.Value.ToString("MMM dd, yyyy") : string.Empty;
item2.ProductID = elem.PRODUCT_ID;
item2.ProductName = elem.PRODUCT_NAME;
item2.UsdEmv = Math.Round((decimal)elem.UsdEmv);
item2.GroupPercent = elem.GroupPercent;
item2.WeightWithEq = elem.WEIGHT_WITH_EQ;
result.Add(item2);
}
}
return result;
}
change it to:
var result = allocationsGrouped
.Where(product => !string.IsNullOrEmpty(product.PRODUCT_NAME))
.GroupBy(product => product.PRODUCT_NAME)
.Select(group => {
var product = group.First();
return new FirmWideAllocationsViewModel()
{
Hierarchy = new List<string>() { manStratName, product.PRODUCT_NAME },
FirmID = product.FIRM_ID,
FirmName = product.Item.FIRM_NAME,
ManagerStrategyID = product.MANAGER_STRATEGY_ID,
ManagerStrategyName = product.MANAGER_STRATEGY_NAME,
ManagerAccountClassID = product.MANAGER_ACCOUNTING_CLASS_ID,
ManagerAccountingClassName = product.MANAGER_ACCOUNTING_CLASS_NAME,
ManagerFundID = product.MANAGER_FUND_ID,
ManagerFundName = product.MANAGER_FUND_NAME,
Nav = product.NAV,
EvalDate = product.EVAL_DATE.HasValue ? product.EVAL_DATE.Value.ToString("MMM dd, yyyy") : string.Empty,
ProductID = product.PRODUCT_ID,
ProductName = product.PRODUCT_NAME,
UsdEmv = Math.Round((decimal)product.UsdEmv),
GroupPercent = product.GroupPercent,
WeightWithEq = product.WEIGHT_WITH_EQ,
//assign aggregate Sum here
EmvSum = group.Sum(x => x.EMV),
};
});

How can I select the last digit of an integer in a LINQ .select?

I have this LINQ select:
var extendedPhrases = phrases
.Select(x => new ExtendedPhrase()
{
Ajlpt = x.Ajlpt,
Bjlpt = x.Bjlpt,
Created = x.Created // an int?
});
If I define:
public int? CreatedLast { get; set; }
Then how can I populate that with the last digit of x.Created?
If you are looking for the last digit of the Created property, the use the % operator like this:
var extendedPhrases = phrases
.Select(x => new ExtendedPhrase()
{
Ajlpt = x.Ajlpt,
Bjlpt = x.Bjlpt,
Created = x.Created,
CreatedLast = x.Created % 10
});
The first way to come to mind is to call .ToString().Last():
var extendedPhrases = phrases
.Select(x => new ExtendedPhrase()
{
Ajlpt = x.Ajlpt,
Bjlpt = x.Bjlpt,
Created = x.Created,
CreatedLast = x.Created?.ToString().Last()
});
If you aren't using the latest shiny C#, then null protection can be done with:
var extendedPhrases = phrases
.Select(x => new ExtendedPhrase()
{
Ajlpt = x.Ajlpt,
Bjlpt = x.Bjlpt,
Created = x.Created,
CreatedLast = x.Created.HasValue ? x.Created.ToString().Last() : null
});
And some conversion back to an int? left as an exercise to the reader.

Linq Lookup to parse a CSV text line

Issue
I had asked this question a while back and the requirements has changed a bit since then.
Now, there is a possibility to have a file with lines as follow:
Bryar22053;ADDPWN;Bryar.Suarez#company.com;ACTIVE
Nicole49927;ADDPWN;Nicole.Acosta#company.com;ACTIVE
Rashad58323;ADDPWN;Rashad.Everett#company.com;ACTIVE
Take first line. The first value Bryar22053 is skipped and the same lookup is used:
var columnCount = dataRow.Skip(1).Count();
var modular = 0;
// Simple Enum
var rightsFileType = new RightsFileType();
if (columnCount % 2 == 0)
{
rightsFileType = RightsFileType.WithoutStatus;
modular = 2;
}
else if (columnCount % 3 == 0)
{
rightsFileType = RightsFileType.WithStatus;
modular = 3;
}
var lookup = dataRow.Skip(1).Select((data, index) => new
{
lookup = index % modular,
index,
data
}).ToLookup(d => d.lookup);
The lookup object now has three groups:
> ? lookup[0].ToList() Count = 1
> [0]: { lookup = 0, index = 0, data = "ADDPWN" } ? lookup[1].ToList() Count = 1
> [0]: { lookup = 1, index = 1, data = "Bryar.Suarez#company.com" } ? lookup[2].ToList() Count = 1
> [0]: { lookup = 2, index = 2, data = "ACTIVE" }
If it was the original case where it would be just System1,User1,System2,User2... the lookup would have two groups and following code would work:
List<RightObjectRetrieved> rights;
rights = lookup[0].Join(lookup[1], system => system.index + 1, username => username.index, (system, username) => new
{
system = system.data,
useraname = username.data
}).Where(d => !string.IsNullOrEmpty(d.system)).Select(d => new RightObjectRetrieved {UserIdentifier = userIdentifier, SystemIdentifer = d.system, Username = d.useraname, RightType = rightsFileType}).ToList();
// rights => Key = System Identifier, Value = Username
But with the third 'status' as System1,User1,Status1,System2,User2,Status2..., I'm having issue trying to Join and get all three. Please help.
Edit
Here is what I have for raw data:
// Method has parameter localReadLine (string) that has this:
// Bryar22053;ADDPWN;Bryar.Suarez#company.com;ACTIVE
// Data line
var dataRow = localReadLine.Split(new[] { ToolSettings.RightsSeperator }, StringSplitOptions.None);
// Trim each element
Array.ForEach(dataRow, x => dataRow[Array.IndexOf(dataRow, x)] = x.Trim());
Tried (failed) so far
rights = lookup[0].Join(lookup[1], system => system.index + 1, username => username.index, status => status.index, (system, username, status) => new
{
system = system.data,
useraname = username.data,
status = status.data
}).Where(d => !string.IsNullOrEmpty(d.system)).Select(d => new RightObjectRetrieved {UserIdentifier = userIdentifier, SystemIdentifer = d.system, Username = d.useraname, RightType = rightsFileType}).ToList();
And
rights = lookup[0].Join(lookup[1], system => system.index + 1, username => username.index, (system, username) => new
{
system = system.data,
useraname = username.data
}).Join(lookup[2], status => status.index, (status) => new
{
status = status.data
}).Where(d => !string.IsNullOrEmpty(d.system)).Select(d => new RightObjectRetrieved {UserIdentifier = userIdentifier, SystemIdentifer = d.system, Username = d.useraname, RightType = rightsFileType, Status = ParseStatus(status)}).ToList();
I think you need to split up a little bit your implementation.
Let's declare a class that will hold the data:
class Data
{
public string System { get; set; }
public string Username { get; set; }
public string Status { get; set; }
}
Now, let's define a couple of parsing functions to parse a line.
The first one will parse a line which includes status:
var withStatus = (IEnumerable<string> line) => line
.Select((token, index) => new { Value = token, Index = index })
.Aggregate(
new List<Data>(),
(list, token) =>
{
if( token.Index % 3 == 0 )
{
list.Add(new Data { System = token.Value });
return list;
}
var data = list.Last();
if( token.Index % 3 == 1 )
data.Username = token.Value;
else
data.Status = token.Value;
return list;
});
The second one will parse a line which doesn't include status:
var withoutStatus = (IEnumerable<string> line) => line
.Select((token, index) => new { Value = token, Index = index })
.Aggregate(new List<Data>(),
(list, token) =>
{
if( token.Index % 2 == 0)
list.Add(new Data { System = token.Value });
else
list.Last().Username = token.Value;
return list;
});
With all that in place, you'll need the following:
Determine the modulus
Iterate the lines of the file and parse each line
Group and aggregate the results
The remaining code would look like this:
var lines = streamReader.ReadAllLines(); // mind the system resources here!
var parser = lines.First().Split(';').Length % 2 == 0 ? withoutStatus : withStatus;
var data = lines.Skip(1) // skip the header
.Select(line =>
{
var parts = line.Split(';');
return new
{
UserId = parts.First(),
Data = parser(parts.Skip(1))
};
})
.GroupBy(x => x.UserId)
.ToDictionary(g => g.Key, g => g.SelectMany(x => x.Data));
Now you have a Dictionary<string, Data> which holds the user id and its info.
Of course, a more elegant solution would be to separate each parsing function into its own class and join those classes under a common interface in case there would be more info to add in the future but the code above should work and give you an idea of what you should do.
If you want to use joins:
var result = lookup[0]
.Join(lookup[1],
system => system.index,
username => username.index - 1,
(system, username) => new {system = system.data, username = username.data, system.index})
.Join(lookup[2],
d => d.index,
status => status.index - 2,
(d, status) => new {d.system, d.username, status = status.data})
.ToList();
Another option to group by records and just select data from it (looks more readable from my point of view):
var result = dataRow
.Skip(1)
.Select((data, index) => new {data, record = index / 3})
.GroupBy(r => r.record)
.Select(r =>
{
var tokens = r.ToArray();
return new
{
system = tokens[0].data,
username = tokens[1].data,
status = tokens[2].data
};
})
.ToList();

AggregationContainer vs. AggregationDescriptor

I am trying to send an array of ranges to an Aggregation Descriptor, but the Lambda expression expects a comma delimited expresions
.Aggregations(agg =>
{
AggregationDescriptor ag = agg.Terms("objectTypes", ot => ot.Field("doc.objectType"));
if (!parameters.ContainsKey("userID"))
ag = ag.Terms("users", ot => ot.Field("doc.entryUserID"));//.Field("doc.sourceUserID")))
ag.Terms("commentTypes", ot => ot.Field("doc.commentType"));
if (!parameters.ContainsKey("dateRange"))
{
Dictionary<string, SearchDateRange> dateMap = GetDateRangeMap();
ag.DateRange("dates", dr => dr.Field("doc.date").Format("yyyy-MM-dd")
.Ranges(r1 => r1.Key("Today").From(dateMap["Today"].startDate.Value.ToString("yyyy-MM-dd")).To("now"),
r2 => r2.Key("SinceWednesday").From(dateMap["Today"].startDate.Value.ToString("yyyy-MM-dd")).To("now"),
r3 => r3.Key("ThisYear").From(dateMap["ThisYear"].startDate.Value.ToString("yyyy-MM-dd")).To("now"),
r3 => r3.Key("Last2Years").From(dateMap["Last2Years"].startDate.Value.ToString("yyyy-MM-dd")).To("now"),
r4 => r4.Key("Last3Years").From(dateMap["Last3Years"].startDate.Value.ToString("yyyy-MM-dd")).To("now")
));
}
The above code works.
Below I would like to use the Range[] array and pass it to the aggregate discriptor but I can't, but I can create an AggregationContainer with the range array. How do I marry these two pieces together?
if (!parameters.ContainsKey("revenueRange") && docTypes.Contains(CouchbaseDocumentType.Company))
{
Dictionary<string, SearchNumberRange> numMap = GetMoneyRangeMap();
Range<double>[] ranges = numMap.Select(m =>
{
var r = new Range<double>().Key(m.Key);
if (m.Value.low.HasValue) r.From(m.Value.low.Value);
if (m.Value.high.HasValue) r.To(m.Value.high.Value);
return r;
}).ToArray();
AggregationContainer agr = new AggregationContainer
{
Range = new RangeAggregator { Field = "doc.lastFinancial.revenueUSD", Ranges = ranges }
};
}
return ag;
}
)
I created simple example to show you how you can achieve this.
var funcs = new List<Func<Range<double>, Range<double>>>();
funcs.Add(range => new Range<double>().From(1).To(2));
funcs.Add(range => new Range<double>().From(3).To(4));
var searchResponse = client.Search<Document>(
s => s.Aggregations(agg => agg.Range("range", descriptor => descriptor.Field(f => f.Number).Ranges(funcs.ToArray()))));
Document class:
public class Document
{
public int Id { get; set; }
public double Number { get; set; }
}
Hope you won't have problem with putting it into your context.

Categories