Lambda not equal on join - c#

Table 1 called Category contains 70 records
Table 2 called FilterCategorys contains 0 records (currently).
my lambda join, I want to pull only records that don't match, so in this case I expect to get 70 records back. Here's my incorrect Lambda:
var filteredList = categorys
.Join(filterCategorys,
x => x.Id,
y => y.CategoryId,
(x, y) => new { catgeory = x, filter = y })
.Where(xy => xy.catgeory.Id != xy.filter.CategoryId)
.Select(xy => new Category()
{
Name = xy.catgeory.Name,
Id = xy.catgeory.Id,
ParentCategoryId = xy.catgeory.ParentCategoryId
})
.ToList();
Whats the correct syntax I need here?

Not sure if you have a requirement of using lambdas (rather than query syntax), but I prefer query syntax for statements that have outer joins.
This should be equivalent:
var filteredList = (
from c in Categorys
join fc in FilterCategorys on c.Id equals fc.CategoryId into outer
from o in outer.DefaultIfEmpty()
select new
{
Category = new Category
{
Name = c.Name,
Id = c.Id,
ParentCategoryId = c.ParentCategoryId
},
Exists = (o != null)
})
.Where(c => !c.Exists)
.Select(c => c.Category);

If you want to do it in purely lambda:
var match = categorys.Join(filterCategorys, x => x.Id, y => y.CategoryId, (x, y) => new { Id = x.Id });
var filteredList = categorys.Where(x => !match.Contains(new {Id = x.Id}));
I haven't measured the performance of this, but for 70 records, optimization is not an issue.

Well I came up with a solution that takes away the need for the join.
var currentIds = filterCategorys.Select(x => x.Id).ToList();
var filteredList = categorys.Where(x => !currentIds.Contains(x.Id));
very similar to #Zoff Dino answer, not sure about performance, maybe someone would like to check.

Try this:
var categories= ...
var filteredCategories=...
var allExceptFiltered = categories.Except(filteredCategories, new CategoryComparer()).ToList();
If you don't provide a custom Comparer that framework has no way of knowing that 2 Category objects are the same(even if they have the same ID),it just thinks that they are different objects (it checks for reference equality )
so you must add this class to your project:
public class CategoryComparer: IEqualityComparer<Category>
{
public bool Equals(Category x, Category y)
{
if (x == null && y == null)
return true;
if (x == null)
return false;
if (y == null)
return false;
return x.CategoryId.GetHashCode() == y.CategoryId.GetHashCode();
}
public int GetHashCode(Category obj)
{
return obj.CategoryId.GetHashCode();
}
}
update
Also check out Wyatt Earp's answer,it is very useful to know how to do an outer join
update 2
Your problem is the Join method.
The Where clause is "called" after the join.so after you have joined the listed based on the ID you select those which have different IDs,that's why you get no resuts

Could you draw bracket and it should work.
....Where(xy => (xy.catgeory.Id != xy.filter.CategoryId))

Related

Map Linq groupby count to C# entity

I have this sql query that performs a groupby on a single field. It then counts the groupby's. So far so good.
select type, count(*)
from myTable
group by type
//Result
//TypeA = 5
//TypeB = 3
However, I am having trouble performing this query with Linq as I need to map the outcome of Count() to a specific entity.
The entity I want to map the count to:
public class MyEtity(){
public int TypeACount {get; set;}
public int TypeBCount {get; set;}
}
The linq query I currently use which
MyEntity test = data
.GroupBy(c => c.type)
.Select(g => new MyEntity (){
TypeACount = g.Where(d => d.type == "A").Count(),
TypeBCount = g.Where(d => d.type == "B").Count()
});
Extra info
Based on some answers, a little extra info. My original plan was to use following.
var firstResults = session.Query<MyEntity>()
.Where(//several date filter conditions)
.ToList();
return new MyEntity() {
TypeACount = firstResults.Where(s => s.type == "A").Count(),
TypeBCount = firstResults.Where(s => s.type == "B").Count()
};
This works, but table queried is rather large and the query took quite some time. Based on a colleagues feedback I was asked if the query couldn't be made in to 1 part instead of separating it. The idea being that the query counting logic would remain in SQL rather than in C#. I don't know if that would actually be faster, but that is what I am trying to figure out.
You should map after you get the information
var results = data
.Where(c => c.TypeOfUsage == "A" || c.TypeOfUsage == "B")
.GroupBy(c => c.TypeOfUsage)
.Select(g => new
{
Type = g.Key,
Count = g.Count()
}).ToList();
MyEntity test = new MyEntity
{
TypeACount = results.FirstOrDefault(d => d.Type == "A")?.Count ?? 0,
TypeBCount = results.FirstOrDefault(d => d.Type == "B")?.Count ?? 0
}
Or if you don't have C# 6
var a = results.FirstOrDefault(d => d.Type == "A");
var b = results.FirstOrDefault(d => d.Type == "B");
MyEntity test = new MyEntity
{
TypeACount = a == null ? 0 : a.Count,
TypeBCount = b == null ? 0 : b.Count
}
Another option would be to use a constant group by.
MyEntity test= data
.Where(c => c.TypeOfUsage == "A" || c.TypeOfUsage == "B")
.GroupBy(c => 1)
.Select(g => new MyEntity
{
TypeACount = g.Where(d => d.TypeOfUsage == "A").Count(),
TypeBCount = g.Where(d => d.TypeOfUsage == "B").Count()
}).Single();
This would be more like the following SQL
select
sum(case when typeOfUseage = 'A' then 1 else 0 end) AS TypeACount
, sum(case when typeOfUseage = 'B' then 1 else 0 end) AS TypeBCount
from myTable
why not the clasic way?, I do not see in your query the reason for group by or Select;
var entity=new MyEntity()
entity.TypeACount = data.Count(a => a.TypeOfUsage == "A"),
entity.TypeBCount =data.Count(b => b.TypeOfUsage == "B")

Entity Framework building Where clause on the fly using Expression

Using Entity Framework C# and have this query, I need the part where it says:
where x.Login_Status == "Submitted"
to be dynamic. There are different cases it could be "Submitted" or null or something else and instead of writing multiple if statement with different queries in it, want to have a Predicate in a where clause.
status = (from x in ctx.table
where x.Login_Status == "Submitted"
orderby x.SUB_DATE descending
select new Model_Table()
{
Id = x.ID,
Name = x.NAME,
Code = x.Code,
DateSubmitted = x.SUB_DATE
}).ToList<Model_Table>();
Is that possible?
Solution:
Inside the if statement when more parameters encountered use this
where_expression = x => x.Login_Status == "Submitted" || x.Login_Status == null;
Here is a complete code that worked for me, anything between square brackets replace to suit your code:
Expression<Func<[Replace with your Entity], bool>> where_submitted = x => x.Login_Status == "Submitted";
// Check if all selected
if (CheckBox_Show_All_Submitted.Checked)
{
where_submitted = x => x.Login_Status == "Submitted" || x.Login_Status == null;
}
status =
ctx.[Replace with your Entity Table]
.Where(where_submitted)
.OrderByDescending(x => x.SUB_DATE)
.Select(x => new Model_Table
{
Id = x.ID,
Name = x.NAME,
Code = x.Code,
DateSubmitted = x.SUB_DATE
}).ToList<Model_Table>();
You need an Expression<Func<Entity,bool>>, not a Predicate<Entity>. The difference is that a predicate is a compiled delegate, and an expression is code as data and thus can be translated to SQL.
Here is an example:
//You can have this expression have different values based on your logic
Expression<Func<Entity,bool>> where_expression = x => x.Login_Status == "Submitted";
var query =
ctx.Table
.Where(where_expression)
.OrderByDescending(x => x.SUB_DATE)
.Select(x => new Model_Table())
{
Id = x.ID,
Name = x.NAME,
Code = x.Code,
DateSubmitted = x.SUB_DATE
}).ToList();
Please note that you need to replace Entity with the name of the real class.
Create an extension method for IQueryable like this:
public static class MethodExtensions{
public static IEnumerable<Model_Table> Query(this IQueryable<TEntity> source, string data){
return (from x in source
where x.Login_Status == data
orderby x.SUB_DATE descending
select new Model_Table()
{
Id = x.ID,
Name = x.NAME,
Code = x.Code,
DateSubmitted = x.SUB_DATE
}).ToList<Model_Table>();
}
}
Now you can use it like this:
var result = ctx.table.Query("somethingelse");

LINQ GroupBy List<>

I have a straightforward LINQ query that is attempting to perform a GroupBy where one of the items in the statements is a List<string>.
var viewModel = reports
.GroupBy(c => new { c.Id, c.PetList })
.Select(g => new ArmReportModel
{
PetList = g.Key.PetList,
Pets = g.Count()
});
Prior to this statement I am executing my EF repository method which ultimately calls a method to create the PetList above.
If I remove the PetList from the GroupBy() it works as expected. Is there something I must do in order to group by a List<string> type?
I would assume that Id is an identifier, and hence any two c with the same Id is in fact the same and has the same PetList. As such we can GroupBy just the Id and get the PetList another way:
var viewModel = reports
.GroupBy(c => c.Id)
.Select(g => new ArmReportModel
{
PetList = g.First().PetList, // Might need FirstOrDefault() with some providers
Pets = g.Count()
});
Barring that, I'd want to first make sure I could use an IEqualityComparer<T> with the GroupBy. If the provider allows for that, then no problem. Otherwise I'd start with:
reports.Select(c => new {c.Id, c.PetList}).AsEnumerable()
This retrieves the minimum necessary from the provider into memory, so that the linq-to-objects provider can be used from that point on.
I need to be able to define an IEqualityComparer<T> for some T, so I stop using anonymous types:
private class IdAndList
{
public int Id { get; set; }
public List<string> PetList { get; set; }
}
private class ReportIdAndPetListComparer : IEqualityComparer<IdAndList>
{
public bool Equals(IdAndList x, IdAndList y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
if (x.Id != y.Id) return false;
if (x.PetList == null) return y.PetList == null;
if (y.PetList == null) return false;
int count = x.PetList.Count;
if (y.PetList.Count != count) return false;
for (int i = 0; i != count; ++i)
if (x.PetList[i] != y.PetList[i]) return false;
return true;
}
public int GetHashCode(IdAndList obj)
{
int hash = obj.Id;
if (obj.PetList != null)
foreach (string pet in obj.PetList)
hash = hash * 31 + pet.GetHashCode();
return hash;
}
}
Some of the tests for null PetLists can be removed if you know that's not possible.
Now:
var viewModel = reports.Select(c => new IdAndList{c.Id, c.PetList}).AsEnumerable()
.GroupBy(c => c, new ReportIdAndPetListComparer())
.Select(g => new ArmReportModel
{
PetList = g.Key.PetList,
Pets = g.Count()
});
Or if the provider can't deal with constructing the IdAndPetList type, then:
var viewModel = reports.Select(c => new {c.Id, c.PetList})
.AsEnumerable()
.Select(c => new IdAndList{c.Id, c.PetList})
.GroupBy(c => c, new ReportIdAndPetListComparer())
.Select(g => new ArmReportModel
{
PetList = g.Key.PetList,
Pets = g.Count()
});

Query Users who have all requested records in two tables

Data structure looks like:
User(id)
UserApp(user_id, app_id)
UserSkill(user_id, skill_id)
Using linq-to-sql or EF, how would I construct a query to elegantly return only users who possess every requested app and skill?
In addition, how would I adjust the query to return any user who possesses at least one of the requested apps or skills? Essentially an OR vs AND (above).
UPDATE 1:
So I think we're close. Basically I want to only return users who have ALL the requested apps and skills. If we have two arrays of requested ids for skills and apps:
int[] requestedAppIDs // [1, 2, 3]
int[] requestedSkillIDs // [4, 5, 6]
I would only want to return a user if they have apps 1,2,3 AND skills 4,5,6.
var usersWithAllSelectedAppsAndSkills =
context.Users
.GroupJoin(context.UserApp,
k => k.id,
k => k.user_id,
(o, i) => new { User = o, UserApps = i })
.GroupJoin(context.UserSkill,
k => k.User.id,
k => k.user_id,
(o, i) => new { User = o.User, o.UserApps, UserSkills = i })
.Where(w => !requestedAppIDs.Except(w.UserApps.Select(x => x.app_id).ToArray()).Any() && !requestedSkillIDs.Except(w.UserSkills.Select(x => x.skill_id).ToArray()).Any())
.Select(s => s.User)
.ToList();
Obviously, LINQ does not know how to translate the UserSkills.Select().ToArray()'s in my Where() to SQL. How can I accomplish this?
And, secondarily the OR solution as well (user has any one of the requested apps or skills).
This will do the job as long as the user_id – app_id and user_id – skill_id values in the UserApp and UserSkill tables are unique.
var requestedSkillIDs = new[] { 4, 5, 6 };
var skillCount = requestedSkillIDs.Length;
var requestedAppIDs = new[] { 1, 2, 3 };
var appCount = requestedAppIDs.Length;
using (var context = new TestContext()) {
context.Database.CreateIfNotExists();
var appQuery = context.UserApp.Where(p => requestedAppIDs.Contains(p.AppId))
.GroupBy(p => p.UserId)
.Where(p => p.Count() == appCount);
var skillQuery = context.UserSkill.Where(p => requestedSkillIDs.Contains(p.SkillId))
.GroupBy(p => p.UserId)
.Where(p => p.Count() == skillCount);
var result = from a in appQuery
join s in skillQuery on a.Key equals s.Key
join u in context.Users on s.Key equals u.Id
select u;
var users = result.ToList();
}
Here's one way to do it, I hope i got all the syntax right :)
using (var context = new YourContext())
{
var usersWithAllSkills = context.User
.Where(w => w.id == yourId)
.Join(context.UserApp,
k => k.id,
k => k.user_id,
(o,i) => o)
.Join(context.UserSkill,
k => k.id,
k => k.user_id,
(o,i) => o)
.ToList();
var usersWithAnySkill = context.User
.Where(w => w.id == yourId)
.GroupJoin(context.UserSkill,
k => k.id,
k => k.user_id,
(o,i) => new { User = o, UserSkills = i })
.GroupJoin(context.UserApp,
k => k.User.id,
k => k.user_id,
(o,i) => new { User = o.User, o.UserSkills ,UserApps = i })
.Where(w => w.UserSkills != null || w.UserApps != null)
.Select(s => s.User)
.ToList();
}
For the first case (AND) You just need to make inner join like below:
from t1 in db.UserApp
join t2 in db.UserSkill on t1.user_id equals t2.user_id
where t1.app_id == "someID" && t2.skill_id == "someID"
select new { t1.user_id,t1.user_app_id, t2.user_skill}
For the second case just swap &&(AND) with ||(OR).
There is a more direct way to write the required queries using L2E. To write these queries you have to forget thinking in SQL and start thinking in LINQ.
For the first case, look for users which have all the skills:
var usersWithAll = ctx.Users2.Where(u =>
appIds.All(aid => u.Apps.Any(a => a.AppId == aid))
&& skillIds.All(sid => u.Skills.Any(s => s.SkillId == sid))
);
Translated as: get the users where, for all the appIds the user has al leat an app with that application id and for all skillIds the user has at least one skill with that id
And, for the second case, users which have any of the apps and any of the skills:
var usersWithAny = ctx.Users2.Where(u =>
appIds.Any(aid => u.Apps.Any(a => a.AppId == aid))
&& skillIds.Any(sid => u.Skills.Any(s => s.SkillId == sid))
).ToList();
Translated as: get the users where, for at least one appId the user has an app with that application id and for any skillIds the user has at least one skill with that id
If you run this test class, you'll also see the executed query (please, note that, to do so, I'm using the Log property of Database. I think it's only available from EF6 on).
namespace Tests
{
[TestClass]
public class CheckSeveralRelationsAtOnce
{
[TestMethod]
public void HasAllAppsAndSkills()
{
int[] appIds = {1, 2, 3};
int[] skillIds = {6, 7, 8};
using (var ctx = new MyDbContext())
{
ctx.Database.Log = Console.Write;
var usersWithAll = ctx.Users2.Where(u =>
appIds.All(aid => u.Apps.Any(a => a.AppId == aid))
&& skillIds.All(sid => u.Skills.Any(s => s.SkillId == sid))
).ToList();
Assert.IsNotNull(usersWithAll);
}
}
[TestMethod]
public void HasAnyAppsOrSkill()
{
int[] appIds = { 1, 2, 3 };
int[] skillIds = { 6, 7, 8 };
using (var ctx = new MyDbContext())
{
ctx.Database.Log = Console.Write;
var usersWithAny = ctx.Users2.Where(u =>
appIds.Any(aid => u.Apps.Any(a => a.AppId == aid))
&& skillIds.Any(sid => u.Skills.Any(s => s.SkillId == sid))
).ToList();
Assert.IsNotNull(usersWithAny);
}
}
}
}
I believe the answer by codeworx is correct for getting users with all the skills / apps
As an aside - I answered pretty much the same question recently with a pure SQL solution (SQL Server) - that could be turned into a stored procedure (with a table valued parameter) - See here if interested. This will perform better for a large number of values. Entity framework will turn every skill/app in the list into its own SQL parameter, which is much slower.
Unfortunately Entity framework doesn't really support table valued parameters yet - although you can use the entity framework connection to directly call a stored procedure with a table valued parameter (as per this article
Back to the question at hand...
I'll add the (easier) query to select a user with ANY of the skills & ANY of the apps:
var result = from u in context.Users
join _a in (
from a in context.UserApp
where requestedAppIDs.Contains(a.AppId)
select a.UserId;
) on u.Id equals _a
into aGrp
join _s in (
from s in context.UserSkill
where requestedSkillIDs.Contains(s.SkillId)
select s.UserId;
) on u.Id equals _s
into sGrp
where aGrp.Any()
&& sGrp.Any()
select u;
And just for completeness - the ALL solution again:
var skillCount = requestedSkillIDs.Length;
var appCount = requestedAppIDs.Length;
var result = from u in context.Users
join _a in (
from a in context.UserApp
where requestedAppIDs.Contains(a.AppId)
select a.UserId;
) on u.Id equals _a
into aGrp
join _s in (
from s in context.UserSkill
where requestedSkillIDs.Contains(s.SkillId)
select s.UserId;
) on u.Id equals _s
into sGrp
where aGrp.Count() == appCount
&& sGrp.Count() == skillCount
select u;
and finally - an example where the main query body is fixed, but you can add differing where clauses depending upon the AND/OR requirement
bool onlyReturnWhereAllAreMatched = false;
var skillCount = requestedSkillIDs.Length;
var appCount = requestedAppIDs.Length;
IQueryable<User> result;
var query = from u in context.Users
join _a in (
from a in context.UserApp
where requestedAppIDs.Contains(a.AppId)
select a.UserId;
) on u.Id equals _a
into aGrp
join _s in (
from s in context.UserSkill
where requestedSkillIDs.Contains(s.SkillId)
select s.UserId;
) on u.Id equals _s
into sGrp
select new {u, aCount = aGrp.Count(), sCount = sGrp.Count()};
if (onlyReturnWhereAllAreMatched)
{
result = from x in query
where x.aCount == appCount
&& x.sCount == skillCount
select x.u;
} else {
result = from x in query
where x.aCount > 0
&& x.sCount > 0
select x.u;
}

LINQ to SQL error on .Join()

I'm trying to query a database and join two tables. I've never used Join() this way and I'm getting an error on the second Join():
var adjustments = data.Inventory_ARCHIVEs
.Where(i => i.Location == comboBox3.Text &&
upcCodes.Contains(i.UPCCode) &&
(i.AVtime.Value.Date >= dateTimePicker1.Value.Date &&
i.AVtime.Value.Date <= dateTimePicker1.Value.AddDays(1).Date) &&
(i.BVtime.Value.Date >= dateTimePicker1.Value.Date &&
i.BVtime.Value.Date <= dateTimePicker1.Value.AddDays(1).Date))
.GroupBy(i => new { i.UPCCode })
.Select(i => new
{
ID = i.Max(x => x.ID),
i.Key.UPCCode
})
.Join(data.Inventory_ARCHIVEs, a => a.ID,
b => b.ID, (a, b) => new { a, b })
.Join(data.BQItems, x => new { x.a.UPCCode, x.b.Location },
y => new { y.UPC_Code, y.Location }, (x, y) => new
{
ID = x.a.ID,
UPCCode = x.a.UPCCode,
Date = x.b.BVtime.Value.Date,
Description = y.Description,
BVamount = x.b.BVamount,
AVamount = x.b.AVamount,
Difference = x.b.AVamount - x.b.BVamount,
AverageCost = x.b.AverageCost,
ExtCost = (x.b.AVamount - x.b.BVamount) * x.b.AverageCost
});
x.a.UPCCode ,x.b.Location, y.UPC_Code, andy.Location are strings.
This is the error:
The type arguments for method 'System.Linq.Enumerable.Join<TOuter,TInner,TKey,TResult> (System.Collections.Generic.IEnumerable<TOuter>, System.Collections.Generic.IEnumerable<TInner>, System.Func<TOuter,TKey>, System.Func<TInner,TKey>, System.Func<TOuter,TInner,TResult>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
If I DO NOT include the join by "Location" column and just use "UPCCode", it works, but when I add the second join by column, I get the error
I suspect this is the problem - it's at least one problem:
.Join(data.BQItems, x => new { x.a.UPCCode, x.b.Location },
y => new { y.UPC_Code, y.Location },
...)
You're trying to join using two different anonymous types as the key types. They've got different properties - one has UPCCode, the other has UPC_Code. You probably want:
.Join(data.BQItems, x => new { x.a.UPCCode, x.b.Location },
y => new { UPCCode = y.UPC_Code, y.Location },
...)
Or just be more consistent with your property names so that you use UPCCode or UPC_Code everywhere, rather than a mixture.
You must have most care about type of data on both side of 'equals' clause, They should be of same datatype like int and int , or string and string.
Or using lambda expression the second and third parameter must be same datatype in the Join clause.

Categories