How can I write a linq query to match two condition on same column in the table?
Here one person can be assigned to multiple types of works and it is store in PersonWorkTypes table containing the details of persons and their worktypes.
So I need to get the list of persons who have both fulltime and freelance works.
I have tried
people.where(w => w.worktype == "freelance" && w.worktype == "fulltime")
But it returns an empty result.
You can try this
public class Person {
public string Name {get;set;}
public List<PersonWorkType> PersonWorkTypes {get;set;}
}
public class PersonWorkType {
public string Type {get;set;}
}
public static void Main()
{
var people = new List<Person>();
var person = new Person { Name = "Toño", PersonWorkTypes = new List<PersonWorkType>() { new PersonWorkType { Type = "freelance" } } };
var person2 = new Person { Name = "Aldo", PersonWorkTypes = new List<PersonWorkType>() { new PersonWorkType { Type = "freelance" }, new PersonWorkType { Type = "fulltime" } } };
var person3 = new Person { Name = "John", PersonWorkTypes = new List<PersonWorkType>() { new PersonWorkType { Type = "freelance" }, new PersonWorkType { Type = "fulltime" } } };
people.Add(person);
people.Add(person2);
people.Add(person3);
var filter = people.Where(p => p.PersonWorkTypes.Any(t => t.Type == "freelance") && p.PersonWorkTypes.Any(t => t.Type == "fulltime"));
foreach(var item in filter) {
Console.WriteLine(item.Name);
}
}
This returns person that contains both types in PersonWorkTypes
AS already said, && operator means, that BOTH conditions has to be met. So in your condition it means that you want worktype type to by freelanceand fulltime at the same time, which is not possible :)
Most probably you want employees that have work type freelance OR fulltime, thus your condition should be:
people.Where(w=>w.worktype=="freelance" || w.worktype =="fulltime")
Or, if person can be set more than once in this table, then you could do:
people
.Where(w=>w.worktype=="freelance" || w.worktype =="fulltime")
// here I assume that you have name of a person,
// Basically, here I group by person
.GroupBy(p => p.Name)
// Here we check if any person has two entries,
// but you have to be careful here, as if person has two entries
// with worktype freelance or two entries with fulltime, it
// will pass condition as well.
.Where(grp => grp.Count() == 2)
.Select(grp => grp.FirstOrDefault());
w.worktype=="freelance"
w.worktype=="fulltime"
These are mutually exclusive to each other, and therefore cannot both be true to ever satisfy your AND(&&) operator.
I am inferring that you have two (or more) different rows in your table per person, one for each type of work they do. If so, the Where() method is going to check your list line-by-line individually and won't be able to check two different elements of a list to see if Alice (for example) both has en entry for "freelance" and an entry for "fulltime" as two different elements in the list. Unfortuantely, I can't think of an easy way to do this in a single query, but something like this might work:
var fulltimeWorkers = people.Where(w=>w.worktype=="fulltime");
var freelanceWorkers = people.Where(w=>w.worktype=="freelance");
List<Person> peopleWhoDoBoth = new List<Person>();
foreach (var worker in fulltimeWorkers)
{
if (freelanceWorkers.Contains(worker)
peopleWhoDoBoth.Add(worker);
}
This is probably not the most efficient way possible of doing it, but for small data sets, it shouldn't matter.
Related
I have two objects in a list and want to ensure that all properties of these objects are having the same value.
For example:
List<Person> persons = new List<Person>
{
new Person { Id = "1", Name = "peter" },
new Person { Id = "1", Name = "peter" }
};
Now I want to get true as both objects properties are same. I have tried with the following lambda expression.
var areEqual = persons.All(o => o == persons.First());
but I'm getting false in areEqual. I'm unable to understand why this is so and want to know how to do it correctly.
You can find out if all elements are the same by using:
persons.Distinct().Count() == 1
If it's zero there were no entries in the first place, if it's greater 1, you had entries that were not the same.
Now... how do you make sure the .Distinct() call knows when two objects are the same?
Option 1: Person is already a record. Great. Inbuilt funtionality. Done.
Option 2: Person implements IEquatable<Person> and it does the check you want.
Option 3: Person overrides Object.Equals and Object.GetHashCode on it's own, in a way you need.
Option 4: Person is neither of the above and you don't want to change it to check one of those boxes. Then you can still implement your own IEqualityComparer<Person> and pass an instance of it to the distinct method like this:
persons.Distinct(new MyCustomPersonEqualityComparer()).Count() == 1
This query would be meaningless if you have less than 2 items. So, take first element to compare to the rest
var allAreSame = persons
.All(p => p.Id == persons[0].Id && p.Name == persons[0].Name);
Or (faster way)
var allAreSame = !persons
.Any(p => p.Id != persons[0].Id || p.Name != persons[0].Name);
Person is a reference type, the default == is performing memory's location equality check. In order to perform your own equality check you must implement IComparable<Person>.
You can also use record instead of class, this implements behind the hoods the equality checks on the record's public properties.
public record Person
{
public string Id { get; set; }
public string Name { get; set; }
}
I have a given linq-sql like this:
var erg = from p in m_session.Query<LovTestData>()
select new
{
SomeString = p.SomeString,
SomeOtherString = p.SomeOtherString
};
This should be the "base"-query for a Lov-Dialog. So this is the query which defines the content of the Lov.
But there are fields in the LOV to search. So this is the query I have to use to fill the Lov at runtime:
var erg = from p in m_session.Query<LovTestData>()
where ((string.IsNullOrEmpty(someStringValueFilter) || p.SomeString.ToLower().Contains(someStringValueFilter.ToLower())) &&
(string.IsNullOrEmpty(someOtherStringFilter) || p.SomeOtherString.ToLower().Contains(someOtherStringFilter.ToLower())))
select new
{
SomeString = p.SomeString,
SomeOtherString = p.SomeOtherString
};
So I wonder how its possible to "inject" the where clause afterwards into the given query? This is how I think it should look like:
var erg = from p in m_session.Query<LovTestData>()
select new
{
SomeString = p.SomeString,
SomeOtherString = p.SomeOtherString
};
var additionalWhere = ... //Some way to define this part: ((string.IsNullOrEmpty(someStringValueFilter) || p.SomeString.ToLower().Contains(someStringValueFilter.ToLower())) && (string.IsNullOrEmpty(someOtherStringFilter) || p.SomeOtherString.ToLower().Contains(someOtherStringFilter.ToLower())))
erg = InjectWhere(erg, additionalWhere); //In this function the where is inserted into the linq so the result is the second query.
Updated:
The additionalWhere should be constructed out of the original query. So its not possible for me to write "p.SomeString" because the construction of the additionalWhere is universal. This is the way I get the fields
Type elementType = erg.ElementType;
foreach (PropertyInfo pi in elementType.GetProperties())
{
//pi.name...
}
If Query returns IQueryable then there is no problem at all.
List<LovTestData> GetTestData(Expression<Func<T, bool>> where)
{
var erg = from p in m_session.Query<LovTestData>()
select new
{
...
}
IQueryable result = erg.Where(where);
return result.ToList();
}
Now. IQueryable will NOT EXECUTE unitl you really use it. So you can do select, where, union and so on, but until you use the IQueryable it won't do anything. Here the real SQL will run in: result.ToList(). That's why you can build all there conditions earlier. Of course assuming that m_session.Query returns IQueryable but as far as I remember - it does.
So you can even do this without result variable that I have created. Just operate on erg.
Waayd's comment will also work.
OK, now about creating filter dynamically.
Let's take a simple class:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Now, let's create a list of records - database.
List<Person> list = new List<Person>
{
new Person {Name = "Adam Abc", Age = 15},
new Person {Name = "John Abc", Age = 23},
new Person {Name = "Steven Abc", Age = 26},
new Person {Name = "Adam Bca", Age = 21},
new Person {Name = "Adam Xyz", Age = 26},
};
Now, let's prepare a filter. You have to get filter data from a view, now let's just simulate this:
string nameIs = "Adam";
bool createAgeFilter = true;
int ageFilterMin = 20;
int ageFilterMax = 25;
So we need all Adam's that are in age between 20 and 25. Let's create this condition:
First condition on name:
Func<Person, bool> whereName = new Func<Person, bool>((p) =>
{
if (!string.IsNullOrWhiteSpace(nameIs))
return p.Name.Contains(nameIs);
else
return true;
}
);
Next condition on age:
Func<Person, bool> whereAge = new Func<Person, bool>((p) =>
{
if (createAgeFilter)
return p.Age >= ageFilterMin && p.Age <= ageFilterMax;
else
return true;
}
);
Next, let's have our IQueryable:
IQueryable<Person> q = list.AsQueryable();
And finally let's add where clauses:
List<Person> filteredList = q.Where(whereName)
.Where(whereAge)
.ToList();
That's it. The idea behind this is that you have to create several partial where clauses. Each for one thing you want to filter. But what I've done at the end will make "AND" between the filters. If you would like to "OR" them, you should do it in one other filter type - like in age filter.
I've just came up with this idea. So there may be a better solution. Maybe even some one liner.
Edit
If you can't use linq like that, there is another way. But not so simple.
There MUST be a point somewhere in your application that you build filter in LINQ style. For example in your view. So take this expression and call ToString(). You will get string representation of the linq query.
The next thing you have to do is to install Roslyn package.
Finally you can change string representation of LINQ expression to LINQ expression using some Roslyn magic:
public async static Task<Expression<Func<T, bool>>> ExpressionFromStr<T>(string expressionStr)
{
var options = ScriptOptions.Default.AddReferences(typeof(T).Assembly);
return await CSharpScript.EvaluateAsync<Expression<Func<T, bool>>>(expressionStr, options);
}
Usings:
using Microsoft.CodeAnalysis.CSharp.Scripting; //roslyn
using Microsoft.CodeAnalysis.Scripting; //roslyn
using System;
using System.Linq.Expressions;
using System.Threading.Tasks; //for async and Task.
In the link https://msdn.microsoft.com/en-us/library/bb534631(v=vs.110).aspx, the third signature is
// M<S> -> (S -> M<C>) -> (S -> M<C> -> R) -> E<R>
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TCollection>> collectionSelector,
Func<TSource, TCollection, TResult> resultSelector
)
Shouldn't the first one which has the signature of the typical Monad bind function M<A> -> (A -> M<B>) -> M<B> be enough? Isn't it easy to merge code in the resultSelector into collectionSelector?
The MSDN document gave an example to show the usage of the method.
class Program
{
static void Main(string[] args)
{
SelectManyEx3();
}
public static void SelectManyEx3()
{
PetOwner[] petOwners =
{ new PetOwner { Name="Higa",
Pets = new List<string>{ "Scruffy", "Sam" } },
new PetOwner { Name="Ashkenazi",
Pets = new List<string>{ "Walker", "Sugar" } },
new PetOwner { Name="Price",
Pets = new List<string>{ "Scratches", "Diesel" } },
new PetOwner { Name="Hines",
Pets = new List<string>{ "Dusty" } } };
// Project the pet owner's name and the pet's name.
var query =
petOwners
.SelectMany(petOwner => petOwner.Pets, (petOwner, petName) => new { petOwner, petName })
.Where(ownerAndPet => ownerAndPet.petName.StartsWith("S"))
.Select(ownerAndPet =>
new
{
Owner = ownerAndPet.petOwner.Name,
Pet = ownerAndPet.petName
}
);
// Print the results.
foreach (var obj in query1)
{
Console.WriteLine(obj);
}
}
}
class PetOwner
{
public string Name { get; set; }
public List<string> Pets { get; set; }
}
However, the var query = ... can be rewritten using the SelectMany of the first signature as following?
var query = petOwners.SelectMany(o => o.Pets.Select(p => new { petOwner = o, petName =p}))
When will the SelectMany with the third signature be really useful?
It would have been more helpful if they used the example like below. See how I added a Where clause in the SelectMany. This overload gives you the flexibility to run a query and then do a projection on the result of that query:
// Project the pet owner's name and the pet's name.
var query =
petOwners
.SelectMany(petOwner => petOwner.Pets.Where(x => x.StartsWith("S")), (petOwner, petName) => new { petOwner, petName })
//.Where(ownerAndPet => ownerAndPet.petName.StartsWith("S"))
.Select(ownerAndPet =>
new
{
Owner = ownerAndPet.petOwner.Name,
Pet = ownerAndPet.petName
}
);
The 3rd and 4th overloads of the SelectMany( ) method with the additional “result selector” parameter take a bit more explanation.
The extra selector parameter is trying to help you know and understand the relationship between the parent and child collections. The code is more concise concerned the relationship.
A result selector is an intermediate object available within the scope of the query to give you the information you need, and it’s up to you to decide what data you need in the result selector to help you.
Say you need a result collection that will not only have the full list of Teams that match some criteria in all leagues but need to know what League the teams are in.
For example:
var teamsAndTheirLeagues = from helper in leagues
.SelectMany( l => l.Teams, ( league, team )
=> new { league, team } )
where helper.team.Players.Count > 2
&& helper.league.Teams.Count < 10
select new { LeagueID = helper.league.ID, Team = helper.team };
This syntax gives you extra ability to query both the team and the league in the where clause and you are not "losing" backward connection of the parent-child relationship.
More detailed explanation about selectMany projections can be found here and here
I have two lists of 'Table' 1 ----N 'Columns.
The first list holds the default schema that must be achieved.
The second list holds the schema defined by the user.
I need to compare the second list against the first one, retrieving the tables where the schema mismatches, also the list of columns missing/unknown.
Consider the following example:
public class Table
{
public string Name {get;set;}
public IList<Column> Columns {get;set;}
public Table()
{
Columns = new List<Column>();
}
}
public class Column
{
public string Name {get;set;}
}
//...
var Default1 = new Table() { Name = "Table1" };
Default1.Columns.Add(new Column() { Name = "X1" });
Default1.Columns.Add(new Column() { Name = "X2" });
var Default2 = new Table() { Name = "Table2" };
Default2.Columns.Add(new Column() { Name = "Y1" });
Default2.Columns.Add(new Column() { Name = "Y2" });
var DefaultSchema = new List<Table>() { Default1, Default2 };
var T1 = new Table() { Name = "Table1" };
T1.Columns.Add(new Column() { Name = "X1" });
var T2 = new Table() { Name = "Table2" };
T2.Columns.Add(new Column() { Name = "Y2" });
var MyTables = new List<Table>() { T1, T2};
/*
var DiffTables = DefaultSchema.Join(??).Select(x => x.Columns).Except(?? MyTables.Select(y => y.Columns) ...
*/
Expected result:
var DiffTables =
{
{
Name = "Table1",
Columns =
{
Name = "X2" //Missing from DefaultSchema.Table1
}
},
{
Name = "Table2",
Columns =
{
Name = "Y1" //Missing from DefaultSchema.Table2
}
}
}
Is there any way of doing this with a lamdba expression, or just by a master+nested foreach?
Thanks!
For comparing just two tables, it would be:
Default1.Columns
.Select(x => x.Name)
.Except(T1.Columns.Select(x => x.Name));
For comparing two schemas, it would be:
DefaultSchema
.Zip(MyTables, (x, y) => new
{ Name = x.Name,
MissingColumns = x.Columns.Select(x1 => x1.Name)
.Except(y.Columns.Select(y1 => y1.Name)) });
Zip combines any two sequences, so that item 1 gets matched with item 2, item 2 gets matched with item 2, etc. (in other words, like a zipper).
Except removes all items of one sequence from another sequence.
As #MetroSmurf pointed out, my original version had an error that was causing Except to fail. The reason is that the it was comparing the columns based on whether they are referring to the same object. I added the inner Select statements to allow the columns to be compared by Name instead.
Note also that this answer assumes the two schema being compared have the same tables in the same order.
Another way to go (inspired by #MetroSmurf's use of IEquatable) is to create a custom IEqualityComparer, like this:
public class ColumnComparer : IEqualityComparer<Column>
{
public bool Equals(Column x, Column y)
{
if (Object.ReferenceEquals(x, y)) return true;
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) return false;
return x.Name == y.Name;
}
public int GetHashCode(Column column)
{
if (Object.ReferenceEquals(column, null)) return 0;
return column.Name.GetHashCode();
}
}
Then the LINQ query reduces down to just this:
DefaultSchema.Zip(MyTables, (x, y) => new
{
Name = x.Name,
MissingColumns = x.Columns.Except(y.Columns, new ColumnComparer())
});
Again, the same assumption that the tables are equivalent for the two schemas applies.
If this assumption doesn't apply (i.e., MyTables are not in order or may be missing tables), you can use a "left outer join" instead:
var result =
from table in DefaultSchema
join myTable in MyTables on table.Name equals myTable.Name into matchingTables
from matchingTable in matchingTables.DefaultIfEmpty()
select new
{
Name = table.Name,
MissingColumns = matchingTable == null
? null
: table.Columns.Except(matchingTable.Columns, new ColumnComparer())
};
With this query, a result is generated for every table in DefaultSchema. If one or more of the MyTables has the same name, the missing columns get reported. If the table is missing from MyTables, the value of MissingColumns is null. Note that this will not report on any extra tables in MyTables that don't exist in DefaultSchema.
Here is how you can do it:
var result =
DefaultSchema
.Select(
table =>
new
{
Table = table,
UserTable = MyTables.FirstOrDefault(utable => utable.Name == table.Name)
})
.Select(item => new
{
Name = item.Table.Name,
MissingColumns =
item.UserTable == null
? item.Table.Columns.Select(x => x.Name).ToArray()
: item.Table.Columns.Select(x => x.Name)
.Except(item.UserTable.Columns.Select(x => x.Name))
.ToArray()
}).ToList();
This code handles the case where the two lists are not guaranteed to have the same number of tables or to have the tables in the correct order.
It starts by selecting the default schema table with its corresponding user table (or null of the user table is not found).
Then, for each such object, it creates a new object that contains the default schema table name, and the list of missing columns.
The list of missing columns are all the columns in the default schema table if a corresponding user table is not found.
If a corresponding user table is found, Except is used to subtract the list of columns defined in the user table from the columns defined in the default schema table.
Using a complex Linq query is going to over-complicate what would be an otherwise easier to understand and maintainable loop. An alternative to devuxer's suggestion (which works great assuming everything in both lists can be zipped):
First, I'd implement IEquatable for an easy comparison:
public class Column : IEquatable<Column>
{
public string Name { get; set; }
public bool Equals( Column other )
{
// consider case insensitive comparison if needed.
return Name == other.Name;
}
}
The loop then becomes:
var diffs = new List<Table>();
foreach( Table table in MyTables )
{
Table schema = DefaultSchema
// consider case insensitive comparison if needed.
.FirstOrDefault( x => x.Name == table.Name );
if( schema == null )
{
// no matching schema, everything should be evaluated.
diffs.Add( table );
continue;
}
// use IEquatable to pull out the differences
List<Column> columns = table.Columns.Except( schema.Columns ).ToList();
if( columns.Any() )
{
diffs.Add( new Table { Name = table.Name, Columns = columns } );
}
}
Building a bunch of reports, have to do the same thing over and over with different fields
public List<ReportSummary> ListProducer()
{
return (from p in Context.stdReports
group p by new { p.txt_company, p.int_agencyId }
into g
select new ReportSummary
{
PKi = g.Key.int_agencyId,
Name = g.Key.txt_company,
Sum = g.Sum(foo => foo.lng_premium),
Count = g.Count()
}).OrderBy(q => q.Name).ToList();
}
public List<ReportSummary> ListCarrier()
{
return (from p in Context.stdReports
group p by new { p.txt_carrier, p.int_carrierId }
into g
select new ReportSummary
{
PKi = g.Key.int_carrierId,
Name = g.Key.txt_carrier,
Sum = g.Sum(foo => foo.lng_premium),
Count = g.Count()
}).OrderBy(q => q.Name).ToList();
}
My Mind is drawing a blank on how i might be able to bring these two together.
It looks like the only thing that changes are the names of the grouping parameters. Could you write a wrapper function that accepts lambdas specifying the grouping parameters? Or even a wrapper function that accepts two strings and then builds raw T-SQL, instead of using LINQ?
Or, and I don't know if this would compile, can you alias the fields in the group statement so that the grouping construct can always be referenced the same way, such as g.Key.id1 and g.Key.id2? You could then pass the grouping construct into the ReportSummary constructor and do the left-hand/right-hand assignment in one place. (You'd need to pass it as dynamic though, since its an anonymous object at the call site)
You could do something like this:
public List<ReportSummary> GetList(Func<Record, Tuple<string, int>> fieldSelector)
{
return (from p in Context.stdReports
group p by fieldSelector(p)
into g
select new ReportSummary
{
PKi = g.Key.Item2
Name = g.Key.Item1,
Sum = g.Sum(foo => foo.lng_premium),
Count = g.Count()
}).OrderBy(q => q.Name).ToList();
}
And then you could call it like this:
var summary = GetList(rec => Tuple.Create(rec.txt_company, rec.int_agencyId));
or:
var summary = GetList(rec => Tuple.Create(rec.txt_carrier, rec.int_carrierId));
Of course, you'll want to replace Record with whatever type Context.stdReports is actually returning.
I haven't checked to see if that will compile, but you get the idea.
Since all that changes between the two queries is the group key, parameterize it. Since it's a composite key (has more than one value within), you'll need to create a simple class which can hold those values (with generic names).
In this case, to parameterize it, make the key selector a parameter to your function. It would have to be an expression and the method syntax to get this to work. You could then generalize it into a function:
public class GroupKey
{
public int Id { get; set; }
public string Name { get; set; }
}
private IQueryable<ReportSummary> GetReport(
Expression<Func<stdReport, GroupKey>> groupKeySelector)
{
return Context.stdReports
.GroupBy(groupKeySelector)
.Select(g => new ReportSummary
{
PKi = g.Key.Id,
Name = g.Key.Name,
Sum = g.Sum(report => report.lng_premium),
Count = g.Count(),
})
.OrderBy(summary => summary.Name);
}
Then just make use of this function in your queries using the appropriate key selectors.
public List<ReportSummary> ListProducer()
{
return GetReport(r =>
new GroupKey
{
Id = r.int_agencyId,
Name = r.txt_company,
})
.ToList();
}
public List<ReportSummary> ListCarrier()
{
return GetReport(r =>
new GroupKey
{
Id = r.int_carrierId,
Name = r.txt_carrier,
})
.ToList();
}
I don't know what types you have mapped for your entities so I made some assumptions. Use whatever is appropriate in your case.