I am trying to apply a bit of groupby/crosstabbing logic to an IEnumerable list of a user defined object and was wondering if anyone could help me out. I'm stuck with an existing (rather annoying) object model to work with but anyway here goes...
consider the following class which I will condense to only relevant properties so you get the jist...
public class Holding
{
private void Institution;
private string Designation;
private Owner Owner;
private Event Event;
private Shares Shares;
}
I want to convert this into a list that satifys the following...
The object is grouped by Institution.
This parent list of institutions contains a list of a new object with a unique combination of Designation and Owner.
Now for each of this combination of Designation and Owner we get another child list of unique Events.
So it basically 3 lists deep.
I'm not sure if this is possible with an IEnumerable List or not, I have toyed around quite a bit with the GroupBy extension method to no avail thus far. I'd like most to do it this way, but I'm using linq-to-sql to get the initial list of holdings which is as follows and might be the better place to do the business...
public static List<Holding> GetHoldingsByEvents(
int eventId1,
int eventId2)
{
DataClassesDataContext db = new DataClassesDataContext();
var q = from h in db.Holdings
where
h.EventId == eventId1 ||
h.EventId == eventId2
select h;
return q.Distinct().ToList();
}
Any help/guidance would be much appreciated...
Thanks in advance.
I'm using ToLookup method, which is kind of a grouping, it takes two parameters, first one a function used for defining the group keys and the next one is a function used as a selector (what to take from the match).
items.ToLookup(c=>c.Institution.InstitutionId, c => new {c.Designation, c.Owner, c.Event})
.Select(c => new {
// find the institution using the original Holding list
Institution = items.First(i=>i.Institution.InstitutionId == c.Key).Institution,
// create a new property which will hold the groupings by Designation and Onwner
DesignationOwner =
// group (Designation, Owner, Event) of each Institution by Designation and Owner; Select Event as the grouping result
c.ToLookup(_do => new {_do.Designation, _do.Owner.OwnerId}, _do => _do.Event)
.Select(e => new {
// create a new Property Designation, from e.Key
Designation = e.Key.Designation,
// find the Owner from the upper group ( you can use items as well, just be carreful this is about object and will get the first match in list)
Owner = c.First(o => o.Owner.OwnerId == e.Key.OwnerId).Owner,
// select the grouped events // want Distinct? call Distinct
Events = e.Select(ev=>ev)//.Distinct()
})
})
I assumed your classes look like these
public class Holding
{
public Institution Institution {get; set;}
public string Designation {get; set;}
public Owner Owner {get; set;}
public Event Event {get; set;}
}
public class Owner
{
public int OwnerId {get; set;}
}
public class Event
{
public int EventId {get; set;}
}
public class Institution
{
public int InstitutionId {get; set;}
}
Related
Let's say I have a an interface, which is basically a combination of two sub-interfaces. The idea behind this is, that I have two different API's. One which provides public information on a person. And once which provides the 'secret' information. It could look something like this:
public interface IPublicPersonData
{
// The ID is the key
int PersonId { get; set; }
// This property is specific to this part
string Name {get; set; }
}
public interface ISecretPersonData
{
// The ID is the key
int PersonId { get; set; }
// This property is specific to this part
decimal AnnualSalary{ get; set; }
}
public interface IPerson: IPublicPersonData, ISecretPersonData
{
// No new stuff, this is merely a combination of the two.
}
So basically I get two lists. One List<IPublicPersonData> and one List<ISecretPersonData>. I would like to join these into a single List<IPerson>, ideally using LINQ.
I cannot really find anything on how control the type of output from LINQ, based on the type of input, even if the logic is there (in the means of interfaces implementing interfaces).
public List<IPerson> JoinPersonData(
List<IPublicPersonData> publicData,
List<ISecretPersonData> secretData)
{
// What the heck goes here?
}
Say you wrote a method such as:
public ISomething CombinePersonWithSecret(
IPublicPersonData publicPerson,
ISecretPersonData secret)
{
if(publicPerson.PersonId != secret.PersonId)
{
throw ...;
}
//join 2 params into a single entity
return something;
}
Now you might...
IEnumerable<ISomething> secretivePeople = PublicPeople.Join(
SecretPersonData,
publicPerson => publicPerson.PersonId,
secret => secret.PersonId,
(publicPerson, secret) => CombinePersonWithSecret(publicPerson, secret))
The problem is not in the Join, it is in the IPerson you want to return. One of the parameters of the Join methods is used what to do with joined result.
You want to join them into a new object that implements IPerson. If you already have such an object: great, use that one, if you don't have it, here is an easy one:
public PersonData : IPerson // and thus also IPublicPersonData and ISecretPersonData
{
// this PersonData contains both public and secret data:
public IPublicPersonData PublicPersonData {get; set;}
public ISecretPersnData SecretPersonData {get; set;}
// implementation of IPerson / IPublicPersonData / ISecretPersonData
int PersonId
{
get {return this.PublicPersonData.Id; }
set
{ // update both Ids
this.PublicPersonData.Id = value;
this.SecreatPersonData.Id = value;
}
}
public string Name
{
get { return this.PublicPersonData.Name; },
set {this.PublicPersonData.Name = value;}
}
public decimal AnnualSalary
{
get {return this.SecretPersonData.AnnualSalary;},
set {this.SecretPersnData.AnnualSalary = value;
}
}
This object requires no copying of the values of the puclic and secret person data. Keep in mind however, if you change values, the original data is changed. If you don't want this, you'll need to copy the data when creating the object
IEnumerable<IPublicPersonData> publicData = ...
IEnumerable<ISecretPersonData> secretData = ...
// Join these two sequences on same Id. Return as an IPerson
IEnumerable<IPerson> joinedPerson = publicData // take the public data
.Join(secretData, // inner join with secret data
publicPerson => publicPerson.Id, // from every public data take the Id
secretPerson => secretPerson.Id, // from every secret data take the Id
(publicPerson, secretPerson) => new PersonData() // when they match make a new PersonData
{
PublicPersonData = publicPerson,
SecretPersnData = secretPerson,
});
LINQ's Join method does the job for you. Assuming there is a Person : IPerson class, here is two ways to implement your JoinPersonData method:
public static IEnumerable<IPerson> LiteralJoinPersonData(List<IPublicPersonData> publics, List<ISecretPersonData> secrets)
{
return from p in publics
join s in secrets on p.PersonId equals s.PersonId
select new Person(p.PersonId, p.Name, s.AnnualSalary);
}
public static IEnumerable<IPerson> FunctionalJoinPersonData(List<IPublicPersonData> publics, List<ISecretPersonData> secrets)
{
return publics
.Join<IPublicPersonData, ISecretPersonData, int, IPerson>(
secrets,
p => p.PersonId,
s => s.PersonId,
(p, s) => new Person(p.PersonId, p.Name, s.AnnualSalary));
}
I want to sort properties based off an attribute parameter Order that is given.
Attribute:
public class Items: Attribute
{
public int Order { get; set; }
public Items(int order)
{
this.Order = order;
}
}
Implementation:
public class client
{
[Items(1)]
public string fName {get; set;}
[Items(3)]
public string lName {get; set;}
[Items(2)]
public string mName {get; set;}
}
Get all Properties:
var properties = typeof(client).GetProperties().Where(
prop => prop.IsDefined(typeof(Items), false));
Sort by Order#?
This is what I tried but it does not work
Array.Sort(properties, delegate (Items x, Items y)
{ return x.Order.CompareTo(y.Order); });
How do I sort the properties based off the Order?
This has been solved, but would like to extend the question.
Is there a way to sort properties without having to put an "Order" on them. I am wanting to just have an attribute "EndOfList" Or "Last" that would state be sure to sort these last. So that I would not have to clutter up the code with Orders.
You can use Linq and OrderBy
var sorted = properties
.OrderBy(p => ((Items)p.GetCustomAttributes(true)
.FirstOrDefault(a => a is Items)).Order);
This results in following order: fName, mName, lName.
So what happens inside the OrderBy: access custom properties. Find a first that is of type Items and use the Order property as a sort parameter.
To sort in reversed order just use OrderByDescending.
You can try this
var properties = typeof(client).GetProperties().Where(prop => prop.IsDefined(typeof(Items), false));
var sortedProperties = properties.OrderBy(x => ((Items)x.GetCustomAttributes(typeof(Items), false).FirstOrDefault()).Order);
Not really sure how to go about this and was hoping the brainiacs here could point me in the right direction. I have a biz object that is a collection and of course each item in the top level collection has some properties but one of the properties is a collection of some related child records....and it is these child records that have the values I am looking for.
So that was the high level overview not moving closer...
The parent object is a collection of events (in essence school classes) and the related child collection is the instructors...such a primary, secondary, aide, observer..etc.
Now the idea is to create an instructor calendar that shows what events Bob..etc has for the month.
So the instructors would be on the left (Y axis) of the calendar while the days of the month would be across the top (X Axis)....however as I mentioned the instructors have to be mined out of the biz obj.
Now I am at a stage in development that I can rework this biz object if there is a better way that I should implement.
Now in my limited knowledge base the best I could come up with was to write a foreach loop to extract the instructors querying one at a time...adding the results to another collection and then taking the new collection and removing dupes or dupe check and don't insert during loop.
Something like List.ForEach(x => x.TeamMember....???)
The parent object:
public class CalendarEvent
{
#region Internal Fields
private readonly string _id;
private DateTime _start;
private DateTime _end;
private string _eventname;
private TeamMembers _eventteam;
#endregion
#region Const
public CalendarEvent(string id, DateTime start, DateTime end, string evtname)
{
this._id = id;
this._start = start;
this._end = end;
this._eventname = evtname;
}
#endregion
#region Props
public string ID { get { return _id; } }
public DateTime Start { get { return _start; } }
public DateTime End { get { return _end; } }
public string EventName { get { return _eventname; } }
public TeamMembers EventTeamMembers { get; set; }
#endregion
}
The child object TeamMembers:
public class TeamMember
{
#region Internal Fields
private readonly string _firstname;
private readonly string _fullname;
private readonly string _userid;
private TeamRoles.TeamMemberRole _memberrole;
//private string _resid;
#endregion
#region Const
public TeamMember(string fullname, string firstname, string userid)
{
this._fullname = fullname;
this._firstname = firstname;
this._userid = userid;
//this._resid = resid;
}
#endregion
#region Props
public string FirstName { get { return _firstname; } }
public string FullName { get { return _fullname; } }
public string UserId { get { return _userid; } }
//public string SpeakerID { get { return _resid; } }
public TeamRoles.TeamMemberRole TeamMemberRole { get; set; }
#endregion
}
So the object would look like this:
CalendarEvent.Count = 25
CalendarEvent[0]
EventId = GUID
Start = May 1st
End = May 12th
EventName = Pencil Sharpening
TeamMembers(.count = 3)
So I need to extract all three team members but..if I have already added them to the Y axis collection (ie the collection that will be bound to the Y axis) then skip or remove later with List.Distinct() or similiar.
Any ideas, suggestions or best practices would be very much appreciated.
My apologies but I am struggling with one aspect of what has been shown to me. I'm sure # Ben Aaronson has already answered this but it is going over my head.
I understand the comment that Event know about TeamMembers but not the reverse. I also understand this statement:
"You need to store all the events somewhere. You can't start from just the team members and get events without also having access to the events at the same time..."
agreed but I do have such a collection to begin with in the proposed solution below called _allevents...so I was hoping to just pull out a distinct collection of TeamMembers.
For example I wrote this but my lack of knowledge means this is just a trip around the mulberry bush....I get what I already had???
List<CalendarEvent> asdf = _allevents.Where(evt => evt.TeamMembers.Any(tm => tm.UserId != string.Empty)).Distinct().ToList();
Your solution would look something like this:
public IEnumerable<CalendarEvent> EventsForTeamMember(string userId)
{
return _allEvents.Where(e => e.EventTeamMembers.Any(tm => tm.UserId == userId));
}
In this example, it's assumed you have a collection with all your possible events in scope called _allEvents. I'm also assuming that TeamMembers is some sort of collection of TeamMembers that you can perform LINQ queries on. And if you want to look up team members by something other than user ID, the change should hopefully be relatively straightforward to see.
EDIT
To pull out a distinct list of team members, as in the updated part of your question, you're close, but you need to remember that you want to select the team member out of the event. So this would look like:
public IEnumberable<TeamMember> AllTeamMembersForEvents()
{
return _allEvents.SelectMany(e => e.EventTeamMembers).Distinct();
}
The key bit here is the SelectMany, which flattens collections.
I would iterate over the object collecting a new anonymous object with team member and date using SelectMany will "flatten" and concatenate these elements-- then sort them by team member and then by date. Once you have the list ordered by team member and date you can just iterate over it to build you calendar as the items will be in the needed order.
Like this:
var byMemberDate = events
.SelectMany(e => e.EventTeamMembers
.Select(m => new { evnt = e, member = m}))
.OrderBy(item => item.member.FullName)
.ThenBy(item => item.evnt.Start);
foreach(var item in byMemberDate)
Console.WriteLine("Member = "+item.member.FullName+" Date = "+item.evnt.Start);
output:
Member = gi joe Date = 1/1/2014 1:01:01 AM
Member = hogan long Date = 1/1/2014 1:01:01 AM
Member = sam spade Date = 1/1/2014 1:01:01 AM
Member = sam spade Date = 2/2/2014 2:02:02 AM
Member = three three Date = 3/3/2014 3:03:03 AM
Full working code here
https://gist.github.com/hoganlong/1bad5892febf3f48b55f
A side note occurred to me. If you want a list of team members each with a list of dates that is easy too. Just use GroupBy after your SelectMany
Like this:
var byMembers = events
.SelectMany(e => e.EventTeamMembers
.Select(m => new { start = e.Start, name = m.FullName}))
.GroupBy(item => item.name)
.OrderBy(g => g.Key);
I have two lists, one which is a list of Equipment and one which is a list of WorkflowItems with an Equipment property. The Equipment property within the List<WorkflowItem> list only has one of it's values hydrated, ProcessId. The List<Equipment> has two properties hydrated, ProcessId and Name. I want to hydrate the List<WorkflowItem>.Equipment.Name with the value from the single Equipment record in the List<Equipment>
This LINQ query below will select a generic item out doing basically what I'm looking for, but I would rather just fill in the original list.
var list = from item in workflowItems
join equipment in barcodeEquipmentList on
item.Equipment.ProcessId equals equipment.ProcessId
select new
{
ProcessId = item.Equipment.ProcessId,
EquipmentName = equipment.Name
};
Edit
The list is going to be relatively small, even doing something like this would be fine (aside from the fact that this does not work)
workflowItems.ForEach(x => x.Equipment = from e in barcodeEquipmentList
where e.Process.Id == x.Equipment.Process.Id
select e
);
...final edit
but this does work:
workflowItems.ForEach(x => x.Equipment = barcodeEquipmentList
.Where(e => e.Process.Id == x.Equipment.Process.Id)
.FirstOrDefault());
This piece of code should match your needs:
public class Equipment {
public int ProcessId { get; set; }
public string Name { get; set; }
}
public class WorkflowItem {
public Equipment { get; set; }
public void LoadEquipmentFrom(IEnumerable<Equipment> cache){
var equipment = cache.FirstOrDefault(e => e.ProcessId == Equipment.ProcessId);
if(equipment != null)
Equipment.Name = equipment.Name;
}
}
You could also assign the instance from cache to the existing one, it wouldn't matter since both must have the same Identifier Equipment = equipment;. That would be easier if you have more properties to set. To optimize further use an IDictionary<int, Equipment> instead of that IEnumerable<Equipment>, because you'll be reading that collection very often.
I'm guessing you are implementing a kind of ORM, in this case I can give you a good advice: "There is already something out there that'll fit your needs.".
Since the dataset was not very big (less than 20 records), I was able to this as below without a hit to performance
workflowItems.ForEach(x => x.Equipment = barcodeEquipmentList
.Where(e => e.Process.Id == x.Equipment.Process.Id)
.FirstOrDefault());
Using SauceDB, how do I turn a one-to-many relationship, between say table "A" and table "B" respectively, into a list property (containing B objects) in the class that corresponds to table A? The relationship is represented by a foreign key in table B referring to table A (so that many B records can belong to one A record).
Sauce does not support Linq2SQL style navigation properties. However, there are two supported ways to work around this depending on your requirements.
1) Just do the join in your code
IDataStore dstore = .GetDataStore();
var query = from i in dstore.Query<MyTable>()
join x in dstore.Query<MyTable>() on i.Name equals x.Name
select new { };
2) Another way to do it is as follows, and gives a more Navigation Property Style use. Modify your object definition to contain a list and use an [AdditionalInit]
public class Foo
{
public int ID { get; set; }
public string Name { get; set; }
[IgnoredField]
public List<Bar> Bars { get; set; }
[AdditionalInit]
private void LoadBars(IDataStore dstore)
{
Bars = dstore.Query<Bar>().Where(r=> r.Foo = this.ID).ToList();
}
}
That should do what you seek, if you have any more questions let me know.
I found that I could use the AdditionalInit attribute in order to define a hook which gets called as a database object gets initialized. Since this hook can accept a data store, I can deduce the one-to-many relationship right there.
Here's the relevant excerpt of class A:
public class A
{
...
public List<B> Bs { get; private set; }
[AdditionalInit]
public void OnInit(IDataStore dstore)
{
Bs = dstore.Query<B>().Where(b => b.A.Id == Id).ToList();
}
}
Bear in mind I haven't been able to test this code yet.