c# distinct doesn't work. what i'm doing wrong? - c#

I'm developing an asp.net core application.
I have a code for getting property values.
var properties = _context.Properties.Select(p => new {
p.Name,
Values = p.Values.Distinct()
}).Distinct();
But Distinct() doesn't work. What I'm doing wrong?

The problem is that the second Distinct does not know how to compare the items. Probably you mean distinct by name, instead it does distinct by all the properties.
Instead of creating an anonymous type, create a named one (i.e. Property). Then declare a IEqualityComparer<T> for this type:
class PropertyNameComparer : IEqualityComparer<Property>
{
public bool Equals(Property x, Property y) => x.Name.Equals(y.Name);
public int GetHashCode(Property p) => p.Name.GetHashCode();
}
(for sake of simplicity, I'm not handling nulls here.)
var properties = _context.Properties.Select(p => new Property {
Name = p.Name,
Values = p.Values.Distinct()
})
.AsEnumerable()
.Distinct(new PropertyNameComparer());
Note the .AsEnumerable() to separate the query from the second Distinct to make it LINQ-to-Objects, because EF cannot convert the IEqualityComparer<T> to SQL.
But the real question is, why are you getting duplicate properties in the first place? And if you do, what do you want to happen to the values of the duplicates? Will they contain the same values or different values? My implementation just takes the first property with its values and ignores the values of the duplicates. Instead, you might want to group by name and to union the values. But that's not clear from your question.

Related

How to filter a List<T> if it contains specific class data?

I need help with filtering list data in c#.
I got 3 class named Product.cs, Storage.cs and Inventory.cs.
public class Storage{
string StorageId;
string Name;
}
public class Inventory{
string InventoryId;
string StorageId;
string ProductId;
}
I got the filled List<Storage> mStorages, List<Product> mProduct and List<Inventory> mInventories.
I have trouble to print mStorages that contain with specific productId that only can be obtained from mInventories.
So, I tried this:
List<Storage> mFilteredStorage;
for(int i=0;i<mStorages.Count;i++){
if(mStorages[i] contain (productId from inventories)){
mFilteredStorage.add(mstorages[i]);
}
So I can get mFilteredStorage that contains specific product from inventories. (in inventories there are lot of product id).
What should I do to get that filteredStorage? I tried to use list.contains() but it only return true and at last there are duplicated storage at mFilteredStorage.
Really need your help guys. Thanks in advance.
I suggest you to read about lambda-expressions, that is what you are looking for.
mFilteredStorage.AddRange(mStorages.Where(storage => inventories.Any(inventory => inventory.productId == storage.productId)).ToList());
This returns you a list with your filtered conditions. So right after Where you iterate over each item in your list, I called this item storage. (you can name those what ever you want to) Then we iterate over your object inventories with another lambda expression. This, the second lambda expression, returns either true if any of inventories's productIds match the productId of the current iterating object of mStorages or false if they don't match.
So you once the productIds match you can imagine the code like the following:
mStorages.Where(storage => true);
And once the result of the second lambda expression is true, storage will be added to the IEnumerable you will get as a result of the Where method.
Since we get an IEnumerable as return, but we want to add those Storage objects to mFilteredStorage, I convert the IEnumerable to a list, by:
/*(the return object we get from the `Where` method)*/.ToList();
You can use LINQ to accomplish your goal. Since Storage has no ProductId, the query will match by StorageId.
var filteredStoragesQry =
from storage in mStorages
where inventories.Any(inventory => inventory.StorageId == storage.StorageId)
select storage;
mFilteredStorages = filteredStoragesQry.ToList();
This query is for LINQ to objects, but it will also work in Entity Framework, when you replace mStorages and inventories by the respective DbSet objects from the context.
mStorages.Join(mInventories, x => x.StorageId, y => y.StorageId, (x, y) => new { Storage = x, ProductId = y.ProductId})
.Where(z => z.ProductId == "specificProductId").Select(z => z.Storage).ToList()
I ended with this code.
mFilteredStorage = tempStorage.GroupBy(s => s.Id).Select(group => group.First()).ToList()
This code is what I want to show.

How to build () => new { x.prop} lambda expression dynamically?

How to dynamically create the below linq expression.
IQueryable abc = QueryData.Select(a => new { a, TempData = a.customer.Select(b => b.OtherAddress).ToList()[0] }).OrderBy(a => a.TempData).Select(a => a.a);
public class Orders
{
public long OrderID { get; set; }
public string CustomerID { get; set; }
public int EmployeeID { get; set; }
public double Freight { get; set; }
public string ShipCountry { get; set; }
public string ShipCity { get; set; }
public Customer[] customer {get; set;}
}
public class Customer
{
public string OtherAddress { get; set; }
public int CustNum { get; set; }
}
Actual data:
List<Orders> order = new List<Orders>();
Customer[] cs = { new Customer { CustNum = 5, OtherAddress = "Hello" }, new
Customer { CustNum = 986, OtherAddress = "Other" } };
Customer[] cso = { new Customer { OtherAddress = "T", CustNum = 5 }, new
Customer { CustNum = 777, OtherAddress = "other" } };
order.Add(new Orders(code + 1, "ALFKI", i + 0, 2.3 * i, "Mumbari", "Berlin", cs));
order.Add(new Orders(code + 2, "ANATR", i + 2, 3.3 * i, "Sydney", "Madrid", cso));
order.Add(new Orders(code + 3, "ANTON", i + 1, 4.3 * i, "NY", "Cholchester", cs));
order.Add(new Orders(code + 4, "BLONP", i + 3, 5.3 * i, "LA", "Marseille", cso));
order.Add(new Orders(code + 5, "BOLID", i + 4, 6.3 * i, "Cochin", "Tsawassen", cs));
public Orders(long OrderId, string CustomerId, int EmployeeId, double Freight, string ShipCountry, string ShipCity, Customer[] Customer = null)
{
this.OrderID = OrderId;
this.CustomerID = CustomerId;
this.EmployeeID = EmployeeId;
this.Freight = Freight;
this.ShipCountry = ShipCountry;
this.ShipCity = ShipCity;
this.customer = Customer;
}
If i sort the OtherAddress field 0th index means Customer field only sorted. I need to sort the whole order data based on OtherAddress field.
I have tried the below way:
private static IQueryable PerformComplexDataOperation<T>(this IQueryable<T> dataSource, string select)
{
string[] selectArr = select.Split('.');
ParameterExpression param = Expression.Parameter(typeof(T), "a");
Expression property = param;
for (int i = 0; i < selectArr.Length; i++)
{
int n;
if (int.TryParse(selectArr[i + 1], out n))
{
int index = Convert.ToInt16(selectArr[i + 1]);
property = Expression.PropertyOrField(Expression.ArrayIndex(Expression.PropertyOrField(property, selectArr[i]), Expression.Constant(index)), selectArr[i + 2]);
i = i + 2;
}
else property = Expression.PropertyOrField(property, selectArr[i]);
}
var TempData = dataSource.Select(Expression.Lambda<Func<T, object>>(property, param));
IQueryable<object> data = dataSource.Select(a => new { a, TempData = property});// Expression.Lambda<Func<T, object>>(property, param) });
return data;
}
Method call : PerformComplexDataOperation(datasource, "customer.0.OtherAddress")
I can get the value from this line : var TempData = dataSource.Select(Expression.Lambda>(property, param));
But i can't get the values in dataSource.Select(a => new { a, TempData = property});
It is working when we use the below code :
var TempData = dataSource.Select(Expression.Lambda<Func<T, object>>(property, param)).ToList();
IQueryable<object> data = dataSource.Select((a, i) => new { a, TempData = TempData[i] });
Is it proper solution ?
XY problem?
This feels like it's a case of the XY problem. Your solution is contrived (no offense intended), and the problem you're trying to solve is not apparent by observing your proposed solution.
However, I do think there is technical merit to your question when I read the intention of your code as opposed to your described intention.
Redundant steps
IQueryable abc = QueryData
.Select(a => new {
a,
TempData = a.customer.Select(b => b.OtherAddress).ToList()[0] })
.OrderBy(a => a.TempData)
.Select(a => a.a);
First of all, when you inline this into a single chained command, TempData becomes a redundant step. You could simply shift the first TempData logic (from the first Select) directly into the OrderBy lambda:
IQueryable abc = QueryData
.OrderBy(a => a.customer.Select(b => b.OtherAddress).ToList()[0])
.AsQueryable();
As you can see, this also means that you no longer need the second Select (since it existed only to undo the earlier Select)
Parametrization and method abstraction
You mentioned you're looking for a usage similar to:
PerformComplexDataOperation(datasource, "customer.0.OtherAddress")
However, this doesn't quite make sense, since you've defined an extension method:
private static IQueryable PerformComplexDataOperation<T>(this IQueryable<T> dataSource, string select)
I think you need to reconsider your intended usage, and also the method as it is currently defined.
Minor note, the return type of the method should be IQueryable<T> instead of IQueryable. Otherwise, you lose the generic type definition that LINQ tends to rely on.
Based on the method signature, your expected usage should be myData = myData.PerformComplexDataOperation("customer.0.OtherAddress").
Strings are easy hacks to allow you to circumvent an otherwise strongly typed system. While your strign usage is technically functional, it is non-idiomatic and it opens the door to unreadable and/or bad code.
Using strings leads to a contrived string parsing logic. Look at your method definition, and count how many lines are there simply to parse the string and translate that into actual code again.
Strings also mean that you get no Intellisense, which can cause unseen bugs further down the line.
So let's not use strings. Let's look back at how I initially rewrote the `OrderBy:
.OrderBy(a => a.customer.Select(b => b.OtherAddress).ToList()[0])
When you consider OrderBy as an ordinary method, no different from any custom method you and I can develop, then you should understand that a => a.customer.Select(b => b.OtherAddress).ToList()[0] is nothing more than a parameter that's being passed.
The type of this parameter is Func<A,B>, where:
A equals the type of your entity. So in this case, A is the same as T in your existing method.
B equals the type of your sorting value.
OrderBy(x => x.MyIntProp) means that B is of type int.
OrderBy(x => x.MyStringProp) means that B is of type string.
OrderBy(x => x.Customer) means that B is of type Customer.
Generally speaking, the type of B doesn't matter for you (since it will only be used by LINQ's internal ordering method).
Let's look at a very simple extension method that uses a parameter for its OrderBy:
public static IQueryable<A> OrderData<A, B>(this IQueryable<A> data, Func<A, B> orderbyClause)
{
return data
.OrderBy(orderbyClause)
.AsQueryable();
}
Using the method looks like:
IQueryable<MyEntity> myData = GetData(); //assume this returns a correct value
myData = myData.OrderData(x => x.MyIntProperty);
Notice how I did not need to specify either of the generic type arguments when calling the method.
A is already known to be MyEntity, because we're calling the method on an object of type IQueryable<MyEntity>.
B is already known to be an int, since the used lambda method returns a value of type int (from MyIntProperty)
As it stands, my example method is just a boring wrapper that does nothing different from the existing OrderBy method. But you can change the method's logic to suit your needs, and actually make it meaningfully different from the existing OrderBy method.
Your expectations
Your description of your goals makes me think that you're expecting too much.
I need to sort "customer.0.OtherAddress" nested file compared to whole base data. But it sorted only for that field. For this case, I find that field value and stored it to TempData. Then Sorting the TempData field.
i need to sort the parent nodes not an sibling alone. QueryData.Select(a => new { a, TempData = a.customer.Select(b => b.OtherAddress).ToList()[0] }).OrderBy(a => a.TempData).Select(a => a.a); I sorting a original data based on temp data. Then i split the original data alone.
It's not possible to sort an entire nested data structure based on a single OrderBy call. OrderBy only sorts the collection on which you call Orderby, nothing else.
If you have a list of Customer entities, who each have a list of Adress entities, then you are working with many lists (a list of customer and several lists of adresses). OrderBy will only sort the list that you ask it to sort, it will not look for any nested lists.
You mention that your TempData solution works. I actually wrote an entire answer contesting that notion (it should be functionally similar to my suggested alternatives, and it should always order the original list, not any nested list), until I noticed that you've made it work for a very insidious and non-obvious reason:
.Select(a => new {
a,
TempData = a.customer.Select(b => b.OtherAddress).ToList()[0]
})
You are calling .ToList(), which changes how the code behaves. You started off with an IQueryable<>, which means that LINQ was preparing an SQL command to retrieve the data when you enumerate it.
This is the goal of an IQueryable<>. Instead of pulling all the data into memory and then filtering it according to your specifications, it instead constructs a complex SQL query, and will then only need to execute a single (constructed) query.
The execution of that constructed query occurs when you try to access the data (obviously, the data needs to be fetched if you want to access it). A common method of doing so is by enumerating the IQueryable<> into an IEnumerable<>.
This is what you've done in the Select lambda. Instead of asking LINQ to enumerate your list of orders, you've asked it to enumerate every list of addresses from every customer from every order in the list of orders.
But in order to know which adresses need to be enumerated, LINQ must first know which customers it's supposed to get the adresses from. And to find out which customers it needs, it must first figure out which orders you're working with. The only way it can figure all of that out is by enumerating everything.
My initial suggestion, that you should avoid using the TempData solution, is still valid. It's a redundant step that serves no functional purpose. However, the enumeration that also takes place may actually be of use to you here, because it changes LINQ's behavior slightly. You claim that it fixes your problem, so I'm going to take your statement at face value and assume that the slightly different behavior between LINQ-to-SQL and LINQ-to-Entities solves your problem.
You can keep the enumeration and still omit the TempData workaround:
IQueryable abc = QueryData
.OrderBy(a => a.customer.Select(b => b.OtherAddress).ToList()[0])
.AsEnumerable()
.AsQueryable();
Some footnotes:
You can use ToList() instead of AsEnumerable(), the result is the same.
When you use First() or Single(), enumeration will inherently take place, so you don't need to call AsEnumerable() beforehand.
Notice that I cast the result to an IEnumerable<>, but then I immediately re-cast it to IQueryable<>. Once a collection has been enumerated, any further operation on it will occur in-memory. Casting it back to an IQueryable<> does not change the fact that the collection has already been enumerated.
But does it work?
Now, I think that this still doesn't sort all of your nested lists with a single call. However, you claim it does. If you still believe that it does, then you don't need to read on (because your problem is solved). Otherwise, the following may be useful to you.
SQL, and by extension LINQ, has made it possible to sort a list based on information that is not found in the list. This is essentially what you're doing, you're asking LINQ to sort a list of orders based on a related address (regardless of whether you want the adresses to be retrieved from the database or not!) You're not asking it to sort the customers, or the addresses. You're only asking it to sort the orders.
Your sort logic feels a bit dirty to me. You are supplying an Address entity to your OrderBy method, without specifiying any of its (value type) properties. But how are you expecting your addresses to be sorted? By alphabetical street name? By database id? ...
I would expect you to be more explicit about what you want, e.g. OrderBy(x => x.Address.Street).ThenBy(x => x.Address.HouseNumber) (this is a simplified example).
After enumeration, since all the (relevant) data is in-memory, you can start ordering all the nested lists. For example:
foreach(var order in myOrders)
{
order.Customer.Addresses = order.Customer.Addresses.OrderBy(x => x.Street).ToList();
}
This orders all the lists of addresses. It does not change the order of the list of orders.
Do keep in mind that if you want to order data in-memory, that you do in fact need the data to be present in-memory. If you never loaded the customer's addresses, you can't use addresses as a sorting argument.
Ordering the list of orders should be done before enumeration. It's generally faster to have it handled by your SQL database, which is what happens when you're working with LINQ-to-SQL.
Ordering nested lists should be done after enumeration, because the order of these lists is unrelated to the original IQueryable<Order>, which only focused on sorting the orders, not its nested related entities (during enumeration, the included entities such as Customer and Address are retrieved without ordering them).
You can transform your OrderBy so you don't need an anonymous type (though I like the Perl/Lisp Schwartzian Transform) and then it is straightforward to create dynamically (though I am not sure how dynamically you mean).
Using the new expression:
var abc = QueryData.OrderBy(a => a.customer[0].OtherAddress);
Not being sure what you mean by dynamic, you can create the lambda
x => x.OrderBy(a => a.customer[0].Otheraddress)
using Expression as follows:
var parmx = Expression.Parameter(QueryData.GetType(), "x");
var parma = Expression.Parameter(QueryData[0].GetType(), "a");
var abc2 = Expression.Lambda(Expression.Call(MyExtensions.GetMethodInfo((IEnumerable<Orders> x)=>x.OrderBy(a => a.customer[0].OtherAddress)),
new Expression[] { parmx,
Expression.Lambda(Expression.Property(Expression.ArrayIndex(Expression.Property(parma, "customer"), Expression.Constant(0)), "OtherAddress"), parma) }),
parmx);

Group by Linq setting properties

I'm working on a groupby query using Linq, but I want to set the value for a new property in combination with another list. This is my code:
var result = list1.GroupBy(f => f.Name)
.ToList()
.Select(b => new Obj
{
ClientName = b.Name,
Status = (AnotherClass.List().Where(a=>a.state_id=b.????).First()).Status
})
I know I'm using a group by, but I'm not sure of how to access the value inside my bcollection to compare it with a.state_id.
This snippet:
Status = (AnotherClass.List().Where(a=>a.state_id=b.????).First()).Status
I've done that before but months ago I don't remember the syntax, when I put a dot behind b I have acces only to Key and the Linq Methods... What should be the syntax?`
Issue in your code is happening here:
a=>a.state_id=b.????
Why ?
Check type of b here, it would be IGrouping<TKey,TValue>, which is because, post GroupBy on an IEnumerable, you get result as IEnumerable<IGrouping<TKey,TValue>>
What does that mean?
Think of Grouping operation in the database, where when you GroupBy on a given Key, then remaining columns that are selected need an aggregation operation,since there could be more than one record per key and that needs to be represented
How it is represented in your code
Let's assume list1 has Type T objects
You grouped the data by Name property, which is part of Type T
There's no data projection so for a given key, it will aggregate the remaining data as IEnumerable<T>, as grouped values
Result is in the format IEnumerable<IGrouping<TK, TV>>, where TK is Name and TV represent IEnumerable<T>
Let's check out some code, break your original code in following parts
var result = list1.GroupBy(f => f.Name) - result will be of type IEnumerable<IGrouping<string,T>>, where list1 is IEnumerable<T>
On doing result.Select(b => ...), b is of type IGrouping<string,T>
Further you can run Linq queries on b, as follows:
b.Key, will give access to Name Key, there's no b.Value, for that your options could be following or any other relevant Linq operations:
a=>b.Any(x => a.state_id == x.state_id) or // Suuggests if an Id match in the Collection
a=>a.state_id == b.FirstOrDefault(x => x.state_id) //Selects First or default Value
Thus you can create a final result, from the IGrouping<string,T>, as per the logical requirement / use case

Lambda expression to return one result for each distinct value in list

I currently have a large list of a class object and I am currently using the following lambda function to return elements that meet the condition.
var call = callList.Where(i => i.ApplicationID == 001).ToList();
This will return a list of objects that all have an id of 001.
I am now curious as to what different ApplicationIDs there are. So I would like a lambda function that will look into this list and return a list where all the element have a different ApplicationID but only fetches one of those.
If i understand your question you can try:
var list = callList.GroupBy(x => x.ApplicationID).Select(x => x.First()).ToList();
So if you have a list like:
AppID:1, AppID:1, AppID:2, AppID:2, AppID:3, AppID:3
Will return:
AppID:1 AppID:2 AppID:3
You can use either First or FirstOrDefault to get back one result
var call = callList.First(i => i.ApplicationID == 001);
If no call exisrs with an ApplicationID of 001 this will throw an exception. If this may be expected consider using:
var call = callList.FirstOrDefault(i => i.ApplicationID == 001);
Here null will be returned if no such call exists and you can handle accordingly in you code.
To find out what other ApplicationId's exist you can query:
var Ids = callList.Where(i => i.ApplicationID != 001).Select(i => i.ApplicationID).Distinct();
You are saying
I am now curious as to what different ApplicationIDs there are. So I
would like a lambda function that will look into this list and return
a list where all the element have a different ApplicationID but only
fetches one of those.
I would suggest that is never something you'd actually want. You either don't care about the elements, you care about all of them, or you care about a specific one. There are few (none?) situations where you care about a random one from the list.
Without knowing about which specific one you care, I can't give you a solution for that version. Allesandro has given you a solution for the random one.
When you only care about the distinct ID's you would end up with
callList.Select(c => c.ApplicationID).Distinct()
which just gives you all ApplicationIDs.
if you care about all of them, you'd end up with
callList.GroupBy(c => c.ApplicationID)
this will give you an IEnumerable<IGrouping<String, Thingy>> (where Thingy is the type of whatever the type of elements of callList is.)
This means you now have a collection of ApplicationID -> collection of Thingy's. For each distinct ApplicationID you'll have a "List" (actually IEnumerable) of every element that has that ApplicationID
If you care for the Thingy of that - for example - has the lowest value of property Foo you would want
callList.GroupBy(c => c.ApplicationID)
.Select(group => group.OrderBy(thingy => thingy.Foo).First()))
here you first Group them by ApplicationID, and then for each list of thingies with the sample ApplicationID you Select the first one of them if you Order them by Foo
There is a way to use the Distinct in the query, but it makes you take care about the values equality. Let's assume your type is called CallClass and try:
class CallClass : IEqualityComparer<CallClass>
{
public int ApplicationId { get; set; }
//other properties etc.
public bool Equals(CallClass x, CallClass y)
{
return x.ApplicationId == y.ApplicationId;
}
public int GetHashCode(CallClass obj)
{
return obj.GetHashCode();
}
}
Now you're able to query values distinctly:
var call = callList.Distinct().ToList();

LINQ to SQL find average of field?

I have a ViewModel called EntityRating, one of whose properties is AverageRating.
When I instantiate a new object of my ViewModel (called EntityRating) type, how do I set the EntityRating.AverageRating based on the Rating field (in SQL Server) of the item in question?
I want to do something like this (which obviously doesn't work):
var er = new EntityRating()
{
AverageRating = _db.All<Ratings>(X => X.RatingID = rating.RatingID).Average(RatingField);
};
Can I average the properties of an object in the database and assign it to the property of an object in my code?
(Pretty new, so let me know if any terminology is off, or if you need more info)
Thanks.
LINQ has the .Average extension method, however, that only works on integers. So what you need to do is get an IEnumerable of the RatingField property on all your Rating objects in the database. This can be accomplished by using the .Select extension method of LINQ which Projects each element of a sequence into a new form.
int average = _db.Ratings
.Where(x => x.RatingID == rating.RatingID)
.Select(x => x.RatingField)
.Average();
There's a LINQ function Average() seen here:
http://msdn.microsoft.com/en-us/library/bb399409.aspx
var er = new EntityRating()
{
AverageRating = _db.Where(X => X.RatingID == rating.RatingID)
.Select( x => x.RatingField).Average();
};

Categories

Resources