Detect duplicate set of IDs in grouped collection - c#

I have the following classes:
public class ExistingProposal
{
public Guid ProposalIdPK { get; set; }
public int StatusCode { get; set; }
public List<ExistingProposalElement> GoldenRecordElements { get; } = new List<ExistingProposalElement>();
}
public class ExistingProposalElement
{
public Guid ProposalElementIdPK { get; set; }
public Guid ProposalIdFK { get; set; } // foreignkey referencing ExistingProposal.ProposalIdPK
}
This classes are initialized with existing proposals and its elements. The Proposal will have various StatusCode's like "Accepted", "Declined", etc.
While creating new proposals and elements I need to check if there is already a proposal containing the same elements like the new ones
so lets assume the folowing situation:
ExistingProposal ExistingProposalElement
1 1
2
3
2 5
6
7
Now a new proposal needs to be created with elements for example with ID's 5,6 and 7. My check now needs to detect
that there is already a proposal with these ID's.
The new ProposalElement-ID's are contained in a
List<Guid> newElements
How can I detect that there is already a proposal containing the same ProposalElement-IDs like contained in my List "newElements" and a particular "StatusCode"?
I assume that it will be related to the Linq "All" method or similar, but I'm really stuck with it since I am not really advanced with Linq.
Any help will be very appreciated.

I advice to use SelectMany as I understand you correctly.. It is easier than other solutions.
var existingItemsWithStatusCode = existingProposals.SelectMany(s => s.GoldenRecordElements,
(s, p) => new { s.ProposalIdPK, s.StatusCode, p.ProposalElementIdPK })
.Where(w => newProposals.SelectMany(s => s.GoldenRecordElements.Select(t => t.ProposalElementIdPK))
.Contains(w.ProposalElementIdPK)).Select(s=>new
{
s.ProposalElementIdPK,s.StatusCode
}).ToList();
var newItems = existingProposals.SelectMany(s => s.GoldenRecordElements,
(s, p) => new {s.ProposalIdPK, s.StatusCode, p.ProposalElementIdPK})
.Where(w => !newProposals.SelectMany(s => s.GoldenRecordElements.Select(t => t.ProposalElementIdPK))
.Contains(w.ProposalElementIdPK)).ToList();

Here are the dummy classes I setup to test this. I set up the two examples you provided and then a new proposal to check that it already exists.
var existingProposal1 = new ExistingProposal
{
Id = 1,
GoldenRecordElements = new List<ExistingProposalElement>
{
new ExistingProposalElement{ Id = 1 },
new ExistingProposalElement{ Id = 2 },
new ExistingProposalElement{ Id = 3 },
}
};
var existingProposal2 = new ExistingProposal
{
Id = 2,
GoldenRecordElements = new List<ExistingProposalElement>
{
new ExistingProposalElement{ Id = 4 },
new ExistingProposalElement{ Id = 5 },
new ExistingProposalElement{ Id = 6 },
}
};
List<ExistingProposal> existingProposals = new List<ExistingProposal>
{
existingProposal1,
existingProposal2
};
var newElements = new List<int> { 1, 2, 3 };
This code should let you know if it already exists.
var exists = existingProposals.Any(x => x.GoldenRecordElements
.Select(y => y.Id)
.Intersect(newElements).Count() == x.GoldenRecordElements.Count());

Related

Add element to a List when delcaring new object

Is there a way to add elements to a List when doing this:
var Foo = new MyClass() {
PropertyList = MyList,
Id = Id,
}
I would like to add elements to PropertyList. For example would be the same as: MyList.Add()
The problem is that i do not have a list called MyList but i rather have elements that i want to append to PropertyList
Updating code based on comments:
var result1 = await query
.GroupBy(c => new {
c.CommissionId, c.ActivityId
})
.Select(grp => new RegistrationStatisticViewModel() {
CommissionId = grp.Key.CommissionId,
CommissionCode = grp.First().Commission.Code,
CommissionDescription = grp.First().Commission.Description,
MinuteWorked = grp.Sum(c => c.MinuteWorked),
ActivityId = grp.Key.ActivityId,
ActivityCode = grp.First().Activity.Code,
ActivityDescription = grp.First().Activity.Description,
})
.ToListAsync();
var grps = from d in result1
group d by d.CommissionId
into grp
select new RegistrationStatisticViewModel() {
CommissionId = grp.Key,
ActivityList = new List < Activity > {
new Activity {
//ActivityId = grp.Select(d => d.ActivityId),
//Code = grp.Select(d => d.ActivityCode),
//Description = grp.Select(d => d.ActivityDescription),
}
},
CommissionCode = grp.First().CommissionCode,
CommissionDescription = grp.First().CommissionDescription,
MinuteWorked = grp.First().MinuteWorked
};
return grps;
To give context:
forget the result1 is just some data i retrieve from my database
Commission is one class and contains:
CommissionId
Code
Description
Activity is one class and contains:
ActivityId ==> type GUID
Code ==> type string
Description ==> type string
Now the var = grps is a LINQ that gets the data and then instatiates a new object (class) new RegistrationStatisticViewModel()
So the tricky part we were discussing before is when i populate ActivityList with multiple activities.
When populating the list if i use .First() or .Select() i would only get one instance and therfore the list would only have one activity.
It worked when using .ToArray() for example if i replace ActivityList with just the ActivityId of type string (so a new property on RegistrationStatisticViewModel that is not a list anymore):
I can do this ActivityId = grp.Select(d2 => d2.ActivityId).ToArray()
And it will give me an array of all the ActivityId linked to that commissionId
I am sorry if this is confusing but it is for me as well. I would thank you if you could help me. No worries if you can't you have already give me very helpful answers, so i thank you for that!
Based on your remarks, I believe this is what you are trying to achieve:
public class PersonContainer
{
public IList<Person> Persons { get; set; }
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
var personContainer = new PersonContainer
{
Persons = new List<Person>
{
new Person
{
Name = "John Doe",
Age = 28,
},
new Person
{
Name = "Jane Doe",
Age = 27,
},
}
};
Here, the Persons property of PersonContainer is initialized and populated with Person elements during instantiation.

Select on empty list's group item gives non-empty list

This is my sample code:
var users = GetUsers();
var userGroups = users.GroupBy(x => x.Id);
foreach (var userGroup in userGroups)
{
var user = userGroup.First();
var carriedItems = userGroup.Select(x => x.CarriedItemId).ToList();
Console.WriteLine($"UserId={user.Id}, CarriedItemsCount={carriedItems.Count}");
}
static IEnumerable<User> GetUsers()
{
return new List<User>()
{
new User() { Id = 1, CarriedItemId = new List<int> { 6 }},
new User() { Id = 1, CarriedItemId = new List<int> { 12 }},
new User() { Id = 2, CarriedItemId = new List<int> { 2 }},
new User() { Id = 3, CarriedItemId = new List<int>() }
};
}
class User
{
public int Id { get; set; }
public List<int> CarriedItemId { get; set; }
}
Output of this is:
UserId=1, CarriedItemsCount=2
UserId=2, CarriedItemsCount=1
UserId=3, CarriedItemsCount=1
Why UserId=3 has 1 carried item, but not 0? After debugging I can see, that his carriedItems list contains default int32 value, which is 0. Why? I expected his carriedItems list to be empty. Can someone spot a bug?
You've messed up types and ranges. Your code as it is does not Count the things you think it does. Right now, it counts Collections. Not Items. See comments below:
class User
{
public int Id { get; set; }
// this 'CarriedItemId' is a LIST, so it's rather a "CarriedItemIds"
public List<int> CarriedItemId { get; set; }
}
var users = GetUsers();
var userGroups = users.GroupBy(x => x.Id);
foreach (var userGroup in userGroups)
{
var user = userGroup.First();
// watch carefully what you select here
// CarriedItemId is a LIST, not a single item
// 'carriedItems' is a List<List<int>>
var carriedItems = userGroup.Select(x => x.CarriedItemId).ToList();
Console.WriteLine($"UserId={user.Id}, CarriedItemsCount={carriedItems.Count}");
}
Therefore, for a userId=3, there's one record, that record has an item list, which, although empty, is still a list, and record exists, so you get a 1.
To get a merged set of all itemIds, use SelectMany instead:
var carriedItems = userGroup.SelectMany(x => x.CarriedItemId).ToList();
This will merge all CarriedItemId from all User records, and you will end up with expected 2/1/0 counts.
var carriedItems = userGroup.Select(x => x.CarriedItemId).ToList();
Console.WriteLine($"UserId={user.Id}, CarriedItemsCount={carriedItems.Count}");
carriedItems is not the count of CarriedItemId, its a list of each entry of the usergroup with that ID. The one for id = 3 has 1 entry that contains 0.

list compare and replace on matching condition linq C#

public class UserValues
{
public string UserId { get; set; }
public int FieldId { get; set; }
public string FieldValue { get; set; }
}
public class LookupMeta
{
public int FieldId { get; set; }
public int Id { get; set; }
public int FieldValueId { get; set; }
public string Title { get; set; }
}
I have kept this in 2 different lists after reading it from DB.
Now I want to compare both the list with
FieldId == FieldId
and FieldValue equals Id
then replace FieldValue from uservalues to FieldValueId from lookupMeta
UserValues
.Where(x => LookupMeta.Any(y =>
y.FieldId == x.FieldId &&
y.FieldValueId.Equals(x.FieldValue)))
.Select(x => x.FieldValue.Replace(x.FieldValue, ???))
I am looking at this link as well. I am struck C# LINQ code for two list compare and replace
Is it good to have in List and doing like this or is there any other optimized way?
Based on the comment that has been left on pwilcox's answer it seems like the OP is look for a solution where the unmatched rows are also included. That means instead of using inner join we are looking for a left outer join.
In the world of Linq this could be achieved via a combination of GroupJoin, SelectMany and Select operators.
In order to be able to join on two different columns we have to introduce an intermediate class to be able to tell the types of the GroupJoin. So, I have created the following class:
internal class IntermediateKey
{
public int Id { get; set; }
public string Value { get; set; }
}
We also have to define a comparer for this class to be able to find matching data:
internal class IntermediateKeyComparer : IEqualityComparer<IntermediateKey>
{
public bool Equals(IntermediateKey x, IntermediateKey y)
{
return x.Id == y.Id && x.Value == y.Value;
}
public int GetHashCode(IntermediateKey obj)
{
return obj.Id.GetHashCode() + obj.Value.GetHashCode();
}
}
Please bear in mind that this implementation is quite simplified. The correct way to implement it is shown in this thread.
Now can define our query as this:
var comparer = new IntermediateKeyComparer();
var result = userValues
.GroupJoin(
lookupMetas,
uv => new IntermediateKey { Id = uv.FieldId, Value = uv.FieldValue },
lm => new IntermediateKey { Id = lm.FieldId, Value = lm.Id.ToString() },
(uv, lm) => new { Value = uv, Lookups = lm},
comparer)
.SelectMany(
pair => pair.Lookups.DefaultIfEmpty(),
(paired, meta) => new { Value = paired.Value, Lookup = meta})
.Select(res =>
{
res.Value.FieldValue = res.Lookup?.FieldValueId.ToString() ?? res.Value.FieldValue;
return res.Value;
});
We defined that userValues should be left outer joined on lookupMetas
if uv's FieldId is matches to lm's FieldId
and if uv's FieldValue is matches to lm's Id's string representation
With the SelectMany we choose either the matching LookupMeta entity or null
With the Select we update the UserValue's FieldValue property only if there is a related LookupMeta otherwise we use its original value.
Now let's see how this works with some sample data:
static void Main(string[] args)
{
var userValues = new List<UserValue>
{
new UserValue { FieldId = 1, FieldValue = "2"},
new UserValue { FieldId = 2, FieldValue = "3"},
new UserValue { FieldId = 4, FieldValue = "5"}
};
var lookupMetas = new List<LookupMeta>
{
new LookupMeta { FieldId = 1, Id = 2, FieldValueId = 20 },
new LookupMeta { FieldId = 2, Id = 3, FieldValueId = 30 },
new LookupMeta { FieldId = 3, Id = 4, FieldValueId = 40 },
};
var comparer = new IntermediateKeyComparer();
var result = userValues
.GroupJoin(
lookupMetas,
uv => new IntermediateKey { Id = uv.FieldId, Value = uv.FieldValue },
lm => new IntermediateKey { Id = lm.FieldId, Value = lm.Id.ToString() },
(uv, lm) => new { Value = uv, Lookups = lm},
comparer)
.SelectMany(
pair => pair.Lookups.DefaultIfEmpty(),
(x, meta) => new { Value = x.Value, Lookup = meta})
.Select(res =>
{
res.Value.FieldValue = res.Lookup?.FieldValueId.ToString() ?? res.Value.FieldValue;
return res.Value;
});
foreach (var maybeUpdatedUserValue in result)
{
Console.WriteLine($"{maybeUpdatedUserValue.FieldId}: {maybeUpdatedUserValue.FieldValue}");
}
}
The output will be:
1: 20
2: 30
4: 5
So, as you can see there is no matching LookupMeta for the last UserValue that's why its FieldValue remained intact.
If I follow you correctly, then the .Join() method in LINQ may be of use to you. Here I use it to accomplish what I think you're after.
UserValues
.Join(
LookupMeta,
uv => new { uv.FieldId, uv.FieldValue },
lm => new { lm.FieldId, lm.FieldValueId },
(uv,lm) => {
uv.FieldValue = lm.FieldValueId;
return uv;
}
);
The second and third lines in the method build anonymous objects from the source tables. The values of these are matched to make a link.
The last line takes the joined entries as inputs and then gives your output. In your case, I just return the UserValues entry. But before I do I change its "FieldValue" property to the "FieldValueId" property of the LookupMeta entry.
You have some inconsistencies. For instance, you talk about matching FieldValue to Id in the paragraph, but in the code you match FieldValue to FieldValueId. Also, you use == in one comparison and .Equals() in the other. No wrong answer here. I just don't know your underlying objects. So you may have to modify my code a bit to get what you want. But it shows the general strategy that I hope will work for you.

Flatten collection of key-collection items Linq

How would I go about flattening below structure using Linq?
I'm looking at following result:
Date Ids
1/1/2011 1
1/1/2011 2
1/1/2011 3
1/1/2012 3
etc..
I tried SelectMany, but I'm doing it wrong. Some snippets for this example:
public class DTO
{
public DateTime Date { get; set; }
public List<int> Ids { get; set; }
}
as
var dataSet = new List<DTO>{
new DTO {Date = new DateTime(2011,1,1), Ids = new List<int>{1,2,3} },
new DTO {Date = new DateTime(2012,1,1), Ids = new List<int>{3,4,5} },
new DTO {Date = new DateTime(2013,1,1), Ids = new List<int>{5,6,7} }
};
You're looking to SelectMany
var result = dataSet.SelectMany(x => x.Ids, (dto,id) => {
return new { date = dto.Date,id};
});
Live example: http://rextester.com/GHY67873
The selectmany should be on the lists inside the DTO but while keeping a reference to the date. That's probably the main issue. By using query syntax those constructions are a lot easier:
var res = (from s in dataSet
from id in s.Ids
select new {Date=s.Date, Id = id}).ToList();
And yet another SelectMany variant with an inner Select
var result = dataSet.SelectMany(x => x.Ids.Select(id => new { date = x.Date,id}));
If you still want Ids as list you can do
var newDataSet = from dto in dataSet
from id in dto.Ids
select new DTO { Date = dto.Date, Ids = new List<int> { id }};
If you want a single id property like
public class NewDTO
{
public DateTime Date { get; set; }
public int Id { get; set; }
}
You can do
var newDataSet = from dto in dataSet
from id in dto.Ids
select new NewDTO { Date = dto.Date, Id = id };
Here another solution using SelectMany
var results = dataSet.Select(x => new
{
dto = x.Ids.Select(y => new
{
date = x.Date,
id = y
})
}).SelectMany(x => x.dto).ToList();

Correlate Three Streams of Events for Multiple Groups With CombineLatest

I have 3 observable streams of events - in date order - (main events, events about sales, and events about customers) - each stream consists of a type of event relating to various vehicles, each event has a vehicleID and various other properties. Events can appear for one vehicle and then another vehicle etc. So basically I am trying to correlate three independent event streams together based on the VehicleID - this sounds like something that should be straightforward. I'm new to any form of complex observable programming so this proving rather difficult.
I want to call a function anytime I see a new event for a vehicle on any of the streams (I guess basically combineLatest). I can do it if I filter each stream only to contains events for one vehicle, so Where, but I can't figure out how to GroupBy and then get the latest of each Group. I guess I am looking to merge the streams but combineLatest on each group of vehicles.
The below would print all the objects I wish to create put only for VehcileID=1. I wish to do the below but for all vehicles. If I for looped over this with every VehcileID this would give me the output I wanted - however that doesn't seem like the hip observable - everything is a stream - state of zen that I should be aiming for.
Observable.CombineLatest(mainEvents.Where(a=>a.VehcileID==1),saleEventsGroup.Where(a=>a.VehcileID==1),customerEventsGroup.Where(a=>a.VehcileID==1),(main, sale, customer)=>{
//Basically flattening various properties from latest state of the 3 streams for current vehicle with some mapping
return ComplexObject(){};})
.Subscribe(Console.WriteLine);
How can I get combine the latest event for each stream for each vehicle.
Any advice would be appreciated
How about this? I'm only doing two streams here but the idea could easily be expanded to three
[TestMethod]
public void GroupByWithMultipleStreams()
{
Subject<Notification> producer = new Subject<Notification>();
Subject<RelatedToNotification> otherThingProducer = new Subject<RelatedToNotification>();
Observable.Merge(
producer.Select(n => new { Id = n.Id, notification = n, relatedNotification = (RelatedToNotification)null }),
otherThingProducer.Select(rn => new { Id = rn.NotificationId, notification = (Notification)null, relatedNotification = rn }))
.GroupBy(x => x.Id)
.SelectMany(obs =>
{
return obs.Scan(new ComplexObject() { Id = obs.Key }, (acc, input) =>
{
acc.Notification = input.notification ?? acc.Notification;
acc.Related = input.relatedNotification ?? acc.Related;
return acc;
});
})
.Where(result => result.Notification != null && result.Related != null) // if you only want it to fire when everything has a value
.Subscribe(result =>
{
//do something with the results here
}
);
producer.OnNext(new Notification() { Id = 1, Version = 1 });
producer.OnNext(new Notification() { Id = 1, Version = 2 });
producer.OnNext(new Notification() { Id = 2, Version = 17 });
producer.OnNext(new Notification() { Id = 1, Version = 3 });
producer.OnNext(new Notification() { Id = 9, Version = 0 });
producer.OnNext(new Notification() { Id = 9, Version = 1 });
otherThingProducer.OnNext(new RelatedToNotification() { NotificationId = 2, SomeData = "2data" });
otherThingProducer.OnNext(new RelatedToNotification() { NotificationId = 2, SomeData = "2data1" });
otherThingProducer.OnNext(new RelatedToNotification() { NotificationId = 9, SomeData = "9Data" });
producer.OnNext(new Notification() { Id = 2, Version = 1 });
}
class ComplexObject
{
public int Id { get; set; }
public Notification Notification { get; set; }
public RelatedToNotification Related { get; set; }
}
class Notification
{
public int Id { get; set; }
public int Version { get; set; }
public string Name { get; set; }
}
public class RelatedToNotification
{
public int NotificationId { get; set; }
public string SomeData { get; set; }
}

Categories