Union two list by property - c#

I would like to merge two list without duplicates. It should distinct only by one property.
I have a class:
public class Test
{
public int Id { get; set; }
public string Prop { get; set; }
public string Type { get; set; }
}
I have two lists which I would like to merge without duplicates by Type. So, firstly I want to take everything from list 1 and then from list 2 when Type doesn't exist in list 1.
I've tried union.

You've to use IEqualityComparer. See at : https://msdn.microsoft.com/en-us/library/ms132151%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
class Compare : IEqualityComparer<Test>
{
public bool Equals(Test x, Test y)
{
return x.Id == y.Id;
}
public int GetHashCode(Test codeh)
{
return codeh.Id.GetHashCode();
}
}
Then use
var union = list1.Union(list2).Distinct(new Compare()).ToList();

Using .NET 6 or higher, you can use the UnionBy() method which will merge 2 lists excluding duplicates by a property :
var mergedLists = firstList.UnionBy(secondList, u => u.property).ToList();
For more informations : Microsoft doc

Related

Trying to get a list of properties dynamically where count > 0

I have a model like so
public class UserModel
{
List<UserModel> users
}
public class UserModel
{
public List<UserSomeObj> userSomeObj { get; set; }
public List<UserSomeOtherObj> userSomeOtherObj { get; set; }
}
public class UserSomeObj
{
public int someIntProperty { get; set; }
public string someStringProperty { get; set; }
}
public class UserSomeOtherObj
{
public int someIntProperty { get; set; }
public string someStringProperty { get; set; }
}
Each UserModel class List is comprised of several other class Lists.
I am referencing them dynamically like so by looping over a list of targeted properties.
to get a list of properties matching the 'prop' variable;
var props = MethodToGetTargetedProperties();
// props example content would be a list of strings like so "UserSomeObj", "UserSomeOtherObj"
foreach (var prop in props)
{
var results = users.Select(x => x.GetPropertyValue(prop)).ToList();
//results contain lists of prop where count == 0 and i dont want them
}
what I am trying to do is reduce the results where count of the lists targeted is greater than 0 .... problem is that I can't find the correct order/syntax to get it to work.
Thanks
As List<T> implements the non-generic ICollection interface, you can cast to that:
var results = users.Select(x => x.GetPropertyValue(prop))
.Cast<ICollection>()
.Where(list => list.Count > 0)
.ToList();
You could do the cast within the Where if you want, although I prefer the above:
var results = users.Select(x => x.GetPropertyValue(prop))
.Where(list => ((ICollection) list).Count > 0)
.ToList();
Thanks ..... in reviewing your reply I was able to do the following
var results = users.Select(x => x.GetPropertyValue(prop))
.Cast<IEnumerable<object>>().Where(y => y.Count() > 0).ToList();
Then after looking at your answer more and trying to understand all its facets, I wondered if I could do it all in one line. The two List classes in UserModel have 2 common properties (idSomething and idSomeOtherThing) and since I will be combining them and doing a Distinct, i thought, hmm, one liner might be possible.

Group By is not aggregating

I am aggregating data that I retrieve from multiple identical web services. The same row count and data points are returned with only a variance in the Value. The GroupBy clause I am using is not condensing any of the rows. I have the same row count before and after the GroupBy.
MyWebServiceUrls
.AsParallel()
.SelectMany(url => GetMetricItemData(url))
.GroupBy(item => new { item.DateTime, item.Group, item.Metric }, item => item.Value)
.Select(grp => new MetricItem()
{
DateTime = grp.Key.DateTime,
Group = grp.Key.Group,
Metric = grp.Key.Metric,
Value = // type = decimal?
grp.Any(mi => mi.HasValue)
? grp.Key.Metric.AggregationType == Metric.MetricAggregationTypes.Sum
? grp.Sum(mi => mi.Value)
: grp.Average(mi => mi)
: null
})
.AsEnumerable();
The syntax looks correct based on other examples I have found.
I send this data back to my database and can aggregate with the statement GROUP BY [DateTime], [Group], [Metric] and everything works great. While I can use the database to solve this issue, I would like to know how to correctly use LINQ in this instance.
What am I missing to get this LINQ expression to work?
UPDATE:
This is the relevant MetricItem and Metric class definition:
public class MetricItem
{
public string Group { get; set; }
public DateTime DateTime { get; set; }
public Metric Metric { get; set; }
public Decimal? Value { get; set; }
}
public class Metric
{
public string Code { get; set; }
public string Label { get; set; }
private List<string> SumMetrics = new List<string>(new string[] { "TPI", "TPO", "TPIO" });
public enum MetricAggregationTypes { Sum, Average };
public MetricAggregationTypes AggregationType
{
get
{
if (SumMetrics.IndexOf(this.Code) >= 0)
return MetricAggregationTypes.Sum;
else
return MetricAggregationTypes.Average;
}
}
}
You need to override Equals and GetHashCode on the Metric class. Most Linq methods use hash codes for comparison operations, so for most objects you define yourself, you need to override this class if you plan to use something like GroupBy, Union, etc.

Can two Lists be joined if types are different like List<T> and List<G>?

I have two different classes like below:
public class ProductDto : IDto
{
public int Parentproductid { get; set; }
public string Parentproductnumber { get; set; }
public int Supplierid { get; set; }
//More properties
}
public class OrderItemsDto : IDto
{
public int Poid { get; set; }
public System.DateTime Createddate { get; set; }
public int Parentproductid { get; set; }
public int Variationid { get; set; }
public System.DateTime Deliverydate { get; set; }
//More properties
}
What I need to do is basically to join List<OrderItemsDto> and List<ProductDto> on parentproductid (like if they were database tables) and produce another list.
I have tried using Union like below:
List<ProductDto> productParents = productManager.getList();
List<OrderItemsDto> orderItemsList = ordermanager.getList();
gvPurchaseOrderItems.DataSource = orderItemsList.Select(o => o.Parentproductid).Union(productParents.Select(pp => pp.parentproductid));
But this only gives me a result of the List<int> parentproductids found in both of the lists where I need something that has properties ( columns in above case ) from both classes. And I couldn't find how to select multiple properties with the Select extension method ( I am kind of a beginner )
I know I can create a new class and map the properties manually, but I really am curious about how to do this with lambda expressions or linq. Is this possible, I would really appreciate if you could point me a direction. Thanks.
You can use Select to create an anonymous type for your query:
orderItemsList.Select(o => new { o.Parentproductid, o.Variationid, /* etc */ })
So, in your case:
gvPurchaseOrderItems.DataSource = orderItemsList
.Select(o => new { o.Parentproductid, o.Variationid, /* etc */ })
.Union(productParents
.Select(pp => new { pp.Parentproductid, pp.Variationid, /* etc */ }));
As both ProductDto and OrderItemsDto implements IDto interface, no need for anonymous types as you tried, this should work:
gvPurchaseOrderItems.DataSource = orderItemsList.Cast<IDto>().Union(productParents);
All the common properties (i.e. defined in the interface I assume) will be there, along with the type-specific ones using another casting.
According to your comment (and the fact that it's DTOs, silly me), you should definitely go for Dave Bish's answer.
If you want to join two lists on Parentproductid into one list, try:
var result = from pm in productManager
join om in ordermanager on pm.Parentproductid equals om.Parentproductid
select new { pm.Parentproductid, pm.Parentproductnumber,
pm.Supplierid, om.Poid /* more properties */ };

Can iEqualityComparer be used to keep one object over another

I am using linq-to-entities to populate a list of objects, I am then using .Distinct(new objectComparer()) to get distinct records. I want to ensure that certain objects are retained in the list over other objects.
Object:
public class Student
{
public int NSN { get; set; }
//removed properties not relevant
public int EnrolmentStatus { get; set; }
}
Comparer:
public class StudentComparer : IEqualityComparer<Student>
{
public bool Equals(Student x, Student y)
{
//Check whether the properties are equal.
return x.NSN == y.NSN;
}
public int GetHashCode(Student obj)
{
return obj.NSN.GetHashCode();
}
}
Query:
var students = (from t1 in Entities.Table1
join t2 in Entities.Table2
on t1.someID equals t2.someID
where (new int?[] { 3, 6, 10 }).Contains(t2.EnrolmentStatus)
select new Student
{
NSN = t1.NSN,
EnrolmentStatus = t2.EnrolmentStatus
}).ToList().Distinct(new StudentComparer());
This code returns distinct NSN values, however I would like to ensure that objects with EnrolmentStatus of 3 or 6 are preferred over EnrolmentStatus 10. Is there any way to do this with an iEqualityComparer or should I use a different approach?
https://code.google.com/p/morelinq/source/browse/MoreLinq/DistinctBy.cs
.DistinctBy(x => Student.NSN)

Remove duplication's from my DropDownList

In my controller i am returning list of my object with specific property:
public ActionResult Index()
{
List<MyObject> list = db.MyObjects.Where(x => x.family == "Web").ToList();
ViewBag.Files = new SelectList(list, "Id", "protocol");
return View();
}
This is my object:
public class MyObject
{
public int id { get; set; }
public string fileName { get; set; }
public string browser { get; set; }
public string protocol { get; set; }
public string family { get; set; }
}
Index.cshtml:
#Html.DropDownList("File", new SelectList(ViewBag.Files, "Id", "protocol_site"), "Select webmail site", new { style = "vertical-align:middle;" })
And i try to made 2 changes with no succeed:
Remove all the duplication protocol from my DropDownListfor exapmle if i have 10 objects : 9 is doc protocol and 1 pdf i wand to see in my DropDownList only 2 items: DOC and PDF and not all the 10 items.
Sort this DropDownList in alphabet order
As #Dreamcatcher mentioned in his answer, you should pass already prepared collection to SelectList constructor and use linq for these tasks. For Distinct linq method you will need to create custom comparer, which will compare objects by protocol field:
public sealed class MyObjectByProtocolComparer: IEqualityComparer<MyObject>
{
public bool Equals(MyObject x, MyObject y)
{
return x.protocol.Equals(y.protocol);
}
public int GetHashCode(MyObject obj)
{
return obj.protocol.GetHashCode();
}
}
This is rather simple implementation, you might need to check for null values. After that use it in your linq query:
var list = db.MyObjects.Where(x => x.family == "Web").ToArray();
list = list.Distinct(new MyObjectByProtocolComparer())
.OrderBy(x => x.fileName)
.ToArray();
You should add second line in your code. However i am not sure correct spelling, i did not use VS. Also if Disctinct does not work correctly, you should write Comparer.
List<MyObject> list = db.MyObjects.Where(x => x.family == "Web").ToList();
list= list.OrderBy(x => x.fileName).Distinct().ToList();
Follow the guidelines in this page http://msdn.microsoft.com/en-us/library/ms173147(v=vs.80).aspx so that you can call linq Distinct

Categories

Resources