Linq query for joining many to many tables - sql

I'm new at LINQ and need your advice.
I have 2 tables like these:
public class Subjects
{
public Subjects()
{
Classes = new List<Classes>();
}
public int Id { get; set; }
public string SubjectName { get; set; }
public virtual List<Classes> Classes { get; set; }
}
public class Classes
{
public Classes()
{
Subjects = new List<Subjects>();
}
public int Id { get; set; }
public string ClassName { get; set; }
public virtual List<Subjects> Subjects { get; set; }
}
And Entity Framework create SubjectClasses
public SubjectsMap()
{
this.HasKey(s => s.Id);
this.Property(s => s.SubjectName)
.IsRequired()
.HasMaxLength(50);
this.ToTable("Subjects");
this.HasMany(c => c.Classes)
.WithMany(s => s.Subjects)
.Map(cs =>
{
cs.MapLeftKey("SubjectId");
cs.MapRightKey("ClassId");
cs.ToTable("SubjectClasses");
});
}
Subject---- SubjectClasses ----- Classes
My Context doesn't have SubjectClasses, so I need to convert the SQL query to Linq or Lambda. But Linqpad doesn't help me or I can't use it. I simply want to take Firstname, SubjectName
SELECT st.Firstname, s.SubjectName
FROM SubjectClasses sc
INNER JOIN Subjects s on s.Id = sc.SubjectId
INNER JOIN Students st on st.ClassId = sc.ClassId
WHERE sc.ClassId = 3

It's working.
(from st in context.Students
from s in context.Subjects
join c in context.Classes
on new { stuId = st.ClassId } equals new { stuId = c.Id }
select new ComplexExamResult
{
Id = c.Id,
Firstname = st.Firstname,
SubjectName = s.SubjectName
}).Where(c => c.Id == classId).AsNoTracking().ToList();

Related

how to use in clause in Linq Query and pass it dynamically from code

I am converting my project to EF Core in my old project I have a query running.
IDictionary<int, IGrouping<int, UserPurchaseItemAddonWithAmount>> addons =
context.Fetch<UserPurchaseItemAddonWithAmount>($"Select UPIA.*, EA.Amount From UserPurchaseItemAddons UPIA Inner Join ExtraAddons EA on UPIA.AddonID = EA.AddonID Where UPIA.UserPurchaseItemID in ({string.Join(',', userPurchaseItems.Select(S => S.UserPurchaseItemID))})")
.GroupBy(G => G.UserPurchaseItemID).ToDictionary(D => D.Key);
I need to convert this query in to Linq query what I am doing is below
IDictionary<int, IGrouping<int, UserPurchaseItemAddonWithAmount>> addons =
(from f in context.UserPurchaseItemAddons
join s in context.ExtraAddons
on f.AddonId equals s.AddonId
select new
{
Amount = s.Amount,
UserPurchaseItemAddonID = f.UserPurchaseItemAddonId,
UserPurchaseItemID = f.UserPurchaseItemId,
BranchItemVariantID = f.BranchItemVariantId,
AddonID = f.AddonId,
UserID = f.UserId,
IsDeleted = f.IsDeleted,
ModifiedOn = f.ModifiedOn,
ModifiedBy = f.ModifiedBy,
Reason = f.Reason,
}).GroupBy(G => G.UserPurchaseItemID).ToDictionary(D => D.Key);
This query is causing a compiler error related to casting to IGrouping<int, UserPurchaseItemAddonWithAmount> to an anonymous type. The other thing is that how can I apply in clause in where condition in above query, just like the first query .
class
public class UserPurchaseItemAddonWithAmount
{
public decimal Amount { get; set; }
public int UserPurchaseItemAddonID { get; set; }
public int UserPurchaseItemID { get; set; }
public int BranchItemVariantID { get; set; }
public int AddonID { get; set; }
public int UserID { get; set; }
public bool IsDeleted { get; set; }
public DateTime? ModifiedOn { get; set; }
public int? ModifiedBy { get; set; }
public string? Reason { get; set; }
}
Try the following query. Main mistake that you have returned anonymous class.
var purchaseItemIds = userPurchaseItems.Select(S => S.UserPurchaseItemID);
IDictionary<int, IGrouping<int, UserPurchaseItemAddonWithAmount>> addons =
(from f in context.UserPurchaseItemAddons
join s in context.ExtraAddons on f.AddonId equals s.AddonId
where purchaseItemIds.Contains(f.UserPurchaseItemID)
select new UserPurchaseItemAddonWithAmount
{
Amount = s.Amount,
UserPurchaseItemAddonID = f.UserPurchaseItemAddonId,
UserPurchaseItemID = f.UserPurchaseItemId,
BranchItemVariantID = f.BranchItemVariantId,
AddonID = f.AddonId,
UserID = f.UserId,
IsDeleted = f.IsDeleted,
ModifiedOn = f.ModifiedOn,
ModifiedBy = f.ModifiedBy,
Reason = f.Reason,
})
.AsEnumerable()
.GroupBy(G => G.UserPurchaseItemID)
.ToDictionary(D => D.Key);

Filter with Left Join in Entity Framework

I have User, Contact and ContactOfUser entities in an ASP.NET Core API project. I want to filter users based on input over these tables.
My entity classes are like this:
public class User
{
public int Id { get; set; }
[MaxLength(50)]
public string Name { get; set; }
[MaxLength(50)]
public string Surname { get; set; }
[MaxLength(60)]
public string Username { get; set; }
}
public class Contact
{
public int Id { get; set; }
[MaxLength(50)]
public string Value{ get; set; }
}
public class ContactOfUser
{
public int Id { get; set; }
public int UserId { get; set; }
[ForeignKey(nameof(UserId))]
public User User { get; set; }
public int ContactId { get; set; }
}
I want to get filtered users based on this FilterModel object:
public class FilterModel
{
public string Name { get; set; }
public string Surname { get; set; }
public string Username { get; set; }
public List<int> ContactId { get; set; }
}
How can I make this filtering process in Entity Framework with Linq methods considering that don't apply special filter data when that data will be accepted as null?
I did something like that method but it is not properly working:
List<User> GetFilteredUsers(FilterModel filter)
{
var query1 = dbContext.Users
.Where(u => u.Name.Contains(filter.Name ?? string.Empty) &&
u.Surname.Contains(filter.Surname ?? string.Empty) &&
u.Username.Contains(filter.Username ?? string.Empty));
var query2 = from u in query1
join cu in dbContext.ContactOfUsers on u.Id equals cu.UserId
into res
from item in res.DefaultIfEmpty()
where filter.Contacts.Contains(item.ContactId)
select new InitialUserModel
{
Id = u.Id,
Name = u.Name,
Surname = u.Surname,
Username = u.Username
};
}
You can achieve it in this way
Using GroupBy to get userId and ContactIds accordingly.
var userContactIds = _dbContext.ContactOfUser.GroupBy(p => p.UserId).Select(g => new { UserId = g.UserId, ContactIds = g.Select(p => p.ContactId).ToList() });
Get the result :
var result = _dbContext.User.Select(p => new FilterModel
{
Name = p.Name, Surname = p.Surname, Username = p.Username,
ContactId = userContactIds.Where(c => p.Id == c.UserId).ToList()
});
Updated
List<User> GetFilteredUsers(FilterModel filter)
{
return (from u in _dbContext.User
join c in _dbContext.ContactOfUser on u.Id equals c.UserId
join fContactId in filter.ContactId on c.ContactId equals fContactId
where u.Name == filter.Name && u.Surname == filter.Surname && u.Username == filter.Username
select u).ToList();
}

Need Help using Groupby and Join together in Query Statement for MVC

I have two models: Departments and Invoice.
public partial class Invoice
{
public int Id { get; set; }
public int VendorId { get; set; }
public int DepartmentID { get; set; }
public string ItemName { get; set; }
public decimal Price { get; set; }
public DateTime Date { get; set; }
}
public partial class Department
{
public int Id { get; set; }
public string Departments { get; set; }
public virtual ICollection<Invoice> Invoices { get; set; }
}
I am trying to group by Departments and list total price per Department. I may be almost there with but leaning towards totally lost so any help would be appreciated.
For example:
Foo - $135
Bar - $450
What I have now but I am lost with the select portion of statement:
var deptGroup = content.Departments
.Join(content.Invoices, d => d.Id, i => i.DepartmentID, (d, i) => new { Department = d, Invoice = i })
.GroupBy(d => d.Department.Departments)
.OrderBy(d => d.Key);
You're almost there, you just need to select the sum of the invoice prices for each group:
var deptGroup = content.Departments
.Join(content.Invoices, d => d.Id, i => i.DepartmentID, (d, i) => new { Department = d, Invoice = i })
.GroupBy(d => d.Department.Departments)
.OrderBy(d => d.Key)
.Select(d => new
{
DepartmentName = d.Key,
TotalPrice = d.Sum(i => i.Price)
};
It's worth noting that you shouldn't need to use your own joins and groupings for this, though, since each Department already has a collection of invoices on it:
var deptGroup = content.Departments
.Select(d => new
{
DepartmentName = d.Departments,
TotalPrice = d.Invoices.Sum(i => i.Price)
})
.OrderBy(d => d.DepartmentName);

Can I use an index as the source of an index in RavenDB

I'm trying to define an index in RavenDb that uses the output of another index as it's input but I can't get it to work.
I have the following entities & indexes defined.
SquadIndex produces the result I expect it to do but SquadSizeIndex doesn't even seem to execute.
Have I done something wrong or is this not supported?
class Country
{
public string Id { get; private set; }
public string Name { get; set; }
}
class Player
{
public string Id { get; private set; }
public string Name { get; set; }
public string CountryId { get; set; }
}
class Reference
{
public string Id { get; set; }
public string Name { get; set; }
}
class SquadIndex : AbstractIndexCreationTask<Player, SquadIndex.Result>
{
public SquadIndex()
{
Map = players => from player in players
let country = LoadDocument<Country>(player.CountryId)
select new Result
{
Country = new Reference
{
Id = country.Id,
Name = country.Name
},
Players = new[]
{
new Reference
{
Id = player.Id,
Name = player.Name
}
}
};
Reduce = results => from result in results
group result by result.Country
into g
select new Result
{
Country = g.Key,
Players = g.SelectMany(x => x.Players)
};
}
internal class Result
{
public Reference Country { get; set; }
public IEnumerable<Reference> Players { get; set; }
}
}
class SquadSizeIndex : AbstractIndexCreationTask<SquadIndex.Result, SquadSizeIndex.Result>
{
public SquadSizeIndex()
{
Map = squads => from squad in squads
select new Result
{
Country = squad.Country,
PlayerCount = squad.Players.Count()
};
Reduce = results => from result in results
group result by result.Country
into g
select new Result
{
Country = g.Key,
PlayerCount = g.Sum(x => x.PlayerCount)
};
}
internal class Result
{
public Reference Country { get; set; }
public int PlayerCount { get; set; }
}
}
No, you can't. The output of indexes are not documents to be indexed.
You can use the scripted index results to chain indexes, but that isn't trivial.

RavenDb: querying by a property in related entity

Either I'm having a mental block, or its not that straightforward.
I have 2 classes, something like that:
public class House
{
public string Id { get; set; }
public string City { get; set; }
public string HouseNumber { get; set; }
}
public class Person
{
public string Id { get; set; }
public string HouseId { get; set; }
public string Name { get; set; }
}
Now I want a list of all people living in a given city, in a flattened model ({City, HouseNumber, PersonName}).
I can't figure out a way on how to map that.. If I had a City in a Person class that would be easy, but I don't, and it doesn't make sense there, imo.
Help ?
Edit:
I came up with this index, which actually works with in-memory list, but Raven returns nothing :(
public class PeopleLocations : AbstractMultiMapIndexCreationTask<PeopleLocations.EntryLocation>
{
public class PeopleLocation
{
public string PersonId { get; set; }
public string HouseId { get; set; }
public string City { get; set; }
}
public PeopleLocations()
{
this.AddMap<House>(venues => venues.Select(x => new
{
x.City,
HouseId = x.Id,
PersonId = (string)null
}));
this.AddMap<Person>(people => people.Select(x => new
{
City = (string)null,
HouseId = x.HouseId,
PersonId = x.Id
}));
this.Reduce = results => results.GroupBy(x => x.HouseId)
.Select(x => new
{
HouseId = x.Key,
People = x.Select(e => e.PersonId),
City = x.FirstOrDefault(y => y.City != null).City,
})
.SelectMany(x =>
x.People.Select(person => new PeopleLocation
{
PersonId = person,
HouseId = x.HouseId,
City = x.City,
})
)
.Select(x => new { PersonId = x.PersonId, x.City, x.HouseId });
}
}
You can do this with a MultiMap Index - but there's a great new feature in RavenDB 2.0 called Indexing Related Documents that is much easier.
Map = people => from person in people
let house = LoadDocument<House>(person.HouseId)
select new
{
house.City,
house.HouseNumber,
PersonName = person.Name,
}