Domanda

I have a Address class.

public class Address : RootEntityBase
{
    virtual public string Province { set; get; }
    virtual public string City { set; get; }        
    virtual public string PostalCode { set; get; }
}

By this default values:

var myList = new List<Address>
             {
               new Address {Province = "P1", City = "C1", PostalCode = "A"},
               new Address {Province = "P1", City = "C1", PostalCode = "B"},
               new Address {Province = "P1", City = "C1", PostalCode = "C"},

               new Address {Province = "P1", City = "C2", PostalCode = "D"},
               new Address {Province = "P1", City = "C2", PostalCode = "E"},

               new Address {Province = "P2", City = "C3", PostalCode = "F"},
               new Address {Province = "P2", City = "C3", PostalCode = "G"},
               new Address {Province = "P2", City = "C3", PostalCode = "H"},

               new Address {Province = "P2", City = "C4", PostalCode = "I"}
             };

I need to extraction distinct of this myList by two columns : Province & City

Namely similar to myExpertResult :

var myExpertResult = new List<Address>
                        {
                           new Address {Province = "P1", City = "C1"},
                           new Address {Province = "P1", City = "C2"},
                           new Address {Province = "P2", City = "C3"},
                           new Address {Province = "P2", City = "C4"}
                        }; 

So I use this code :

var list = myList.Select(x => new Address {City = x.City, Province = x.Province}).Distinct().ToList();

but my result is not valid because count of result is 9 namely all addresses.

Quivalent query in SQL is : select distinct Province , City from tblAddress

also I tested this query by linq to NHibernate.

var q = SessionInstance.Query<Address>();
        .Select(x => new Address { Province = x.Province, City = x.City }).Distinct().ToList();

But it is not support this query. Message of exception is : Expression type 'NhDistinctExpression' is not supported by this SelectClauseVisitor.

How can I do it?

È stato utile?

Soluzione

You can use GroupBy:

var result = myList.GroupBy(a => new { a.Province, a.City })
      .Select(g => new Address { 
                  Province = g.Key.Province, 
                  City = g.Key.City 
              });

Or use anonymous type:

 myList.Select(a => new { 
            Province = a.Province,
            City = a.City
        })
      .Distinct();

By default, anonymous type uses value quality to compare, it is equivalent when all properties are equivalent.

Another way is to customer EqualityComparer which uses Province and City for Equal and GetHashCode method with another overload Distinct in here

Altri suggerimenti

Maybe this can help:

A simple relay comparer

public class RelayComparer<T> : IEqualityComparer<T>
{
private Func<T, T, bool> equals;
private Func<T, int> getHashCode;  

public RelayComparer(Func<T, T, bool> equals, Func<T,int> getHashCode)
{
  this.equals = equals;
  this.getHashCode = getHashCode;
}

public bool Equals(T x, T y)
{
  return equals(x, y);
}

public int GetHashCode(T obj)
{
  return getHashCode(obj);
}
}


var comparer = new RelayComparer<Address>(
(lhs, rhs) => lhs.Province == rhs.Province && lhs.City == rhs.City, 
      t => t.Province.GetHashCode() + t.City.GetHashCode());

myList.Distinct(comparer);

If you want a generic way to make a distinct by a single specific property you can use this:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;

  /// <summary> Gets property information.</summary>
  /// <exception cref="ArgumentException">
  ///   Thrown when one or more arguments have unsupported or illegal values.
  /// </exception>
  /// <typeparam name="TSource">   Type of the source. </typeparam>
  /// <typeparam name="TProperty"> Type of the property. </typeparam>
  /// <param name="source">         Source for the. </param>
  /// <param name="propertyLambda"> The property lambda. </param>
  /// <returns> The property information.</returns>
  public static PropertyInfo GetPropertyInfo<TSource, TProperty>(
      TSource source,
      Expression<Func<TSource, TProperty>> propertyLambda)
  {
     Type type = typeof(TSource);

     MemberExpression member = propertyLambda.Body as MemberExpression;
     if (member == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a method, not a property.",
            propertyLambda.ToString()));
 PropertyInfo propInfo = member.Member as PropertyInfo;
 if (propInfo == null)
    throw new ArgumentException(string.Format(
        "Expression '{0}' refers to a field, not a property.",
        propertyLambda.ToString()));

 if (propInfo.ReflectedType != null && (type != propInfo.ReflectedType &&
                                        !type.IsSubclassOf(propInfo.ReflectedType)))
    throw new ArgumentException(string.Format(
        "Expresion '{0}' refers to a property that is not from type {1}.",
        propertyLambda.ToString(),
        type));

 return propInfo;
  }

      /// <summary> An IQueryable&lt;T&gt; extension method that distinct by a certain property.</summary>
      /// <exception cref="NullReferenceException"> Thrown when the underlying Session value was unexpectedly null. </exception>
      /// <typeparam name="T">  The result type of the IQueryable. </typeparam>
      /// <typeparam name="T1"> The property type. </typeparam>
      /// <param name="query">      The query to act on. </param>
      /// <param name="expression"> The expression, that references the property. </param>
      /// <returns> An IQueryable&lt;T&gt;</returns>
      public static IQueryable<T> DistinctBy<T, T1>(this IQueryable<T> query, Expression<Func<T, T1>> expression)
      {
         var distinctSelection = query.Select(expression);
         var info = GetPropertyInfo(default(T), expression);
         var propertyInfo = query.Provider.GetType().GetProperty("Session", BindingFlags.NonPublic | BindingFlags.Instance);
         if (propertyInfo == null)
         {
            throw new NullReferenceException("The underliying Session is not defined!");
         }
         ISession session = propertyInfo.GetValue(query.Provider, null) as ISession;
         var result = session.Query<T>().Where("x => @0.Contains( x." + info.Name + ")", distinctSelection);
         return result;
      }
   }

      /// <summary> An IQueryable&lt;T&gt; extension method that distinct by two properties (composite key).</summary>
      /// <exception cref="ArgumentNullException">  Thrown when one or more required arguments are null. </exception>
      /// <exception cref="NullReferenceException"> Thrown when a value was unexpectedly null. </exception>
      /// <typeparam name="T">  The resulting type. </typeparam>
      /// <typeparam name="T1"> The type of the first property. </typeparam>
      /// <typeparam name="T2"> The type of the second property. </typeparam>
      /// <param name="query">          The query to act on. </param>
      /// <param name="expressionKey1"> The first expression key (property 1 or key 1). </param>
      /// <param name="expressionKey2"> The second expression key (property 2 or key 2). </param>
      /// <returns> An IQueryable&lt;T&gt;</returns>
      public static IQueryable<T> DistinctBy<T, T1, T2>(this IQueryable<T> query, Expression<Func<T, T1>> expressionKey1, Expression<Func<T, T2>> expressionKey2)
      {
         if (expressionKey1 == null)
         {
            throw new ArgumentNullException("expressionKey1");
         }
         if (expressionKey2 == null)
         {
            return query.DistinctBy(expressionKey1);
         }
         var propertyInfo = query.Provider.GetType().GetProperty("Session", BindingFlags.NonPublic | BindingFlags.Instance);
         if (propertyInfo == null)
         {
            throw new NullReferenceException("The underliying Session is not defined!");
         }
         ISession session = propertyInfo.GetValue(query.Provider, null) as ISession;
         var info1 = GetPropertyInfo(default(T), expressionKey1);
         var info2 = GetPropertyInfo(default(T), expressionKey2);

         var result = session.Query<T>().Where("k => @0.Any(k1 => k1." + info1.Name + " == k." + info1.Name + " && k1." + info2.Name + " == k." + info2.Name + ")", query);

         return result;
      }

You can call it like this:

var query = Session.Query<Person>().DistinctBy(p => p.FirstName);
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top