How to use LINQ on a join table? - c#

The query I am trying to replicate in LINQ is:
SELECT count(*) FROM joinTable WHERE object1ID = input_parameter1_from_code
AND object2ID = input_parameter2_from_code;
I have access to a IdentityDbContext, but it only contains references to the constituent objects' tables, not for the join table itself, so I don't know what to look for to try to get the result.
Alternatively, if I can just use this raw query, I would like to know how to do that as well.
Thank you.

I assume you have in mind many-to-many relationship with implicit "link" ("join", "junction") table. Something like this (most likely you are speaking for User and Role, but that's not essential):
public class One
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Two> Twos { get; set; }
}
public class Two
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<One> Ones { get; set; }
}
public class MyDbContext : DbContext
{
public DbSet<One> Ones { get; set; }
public DbSet<Two> Twos { get; set; }
}
Although you have no direct access to the link table, you can use either of the two "primary" tables combined with the navigation property of the other.
So, given
var db = new MyDbContext();
both
int count =
(from one in db.Ones
from two in one.Twos
where one.Id == input_parameter1_from_code && two.Id == input_parameter2_from_code
select new { one, two })
.Count();
and
int count =
(from two in db.Twos
from one in two.Ones
where one.Id == input_parameter1_from_code && two.Id == input_parameter2_from_code
select new { one, two })
.Count();
will produce identical SQL query similar to this:
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM [dbo].[TwoOne] AS [Extent1]
WHERE (1 = [Extent1].[One_Id]) AND (2 = [Extent1].[Two_Id])
) AS [GroupBy1]
which as you can see is against the link table.

In query syntax:
var amount = (from record in DBcontext.joinTable
where record.object1ID = input_parameter1_from_code &&
record.object2ID = input_parameter2_from_code
select record).Count();
In Method syntax:
var amount = DBcontext.joinTable
.Where(record => record.object1ID = input_parameter1_from_code &&
record.object2ID = input_parameter2_from_code)
.Count();

You can use Database.SqlQuery method which accepts raw sql query along with the parameters that you need to use with your query and advantage of using sql parameter is to avoid sql injection.
Try like this:
var data = yourContext.Database.SqlQuery<int>(
"SELECT count(*) FROM joinTable WHERE object1ID = #code1 AND object2ID = #code2",
new SqlParameter("#code1", input_parameter1_from_code),
new SqlParameter("#code2", input_parameter2_from_code)
);
Let me know if this didnt work for you :)

You can definitely use that query with a DbContext. Take a look at the MSDN documentation over here:
https://msdn.microsoft.com/en-us/library/system.data.linq.datacontext.executequery(v=vs.110).aspx
It will be something like:
var Count = DbContext.ExecuteQuery("SELECT count(*) FROM joinTable where object1ID = input_parameter1_from_code
AND object2ID = input_parameter2_from_code;");

This should work, even in case of link table
dbContext.CollectionOne.where(x => x.Id == 1).SelectMany(x => x.Collection2).where(y => y.Id == 2).Count()

Related

Left outer join using LINQ Query Syntax EF Core C#

I have a question in regards with the below,
Left outer join of two tables who are not connected through Foreign Key.
Order by the results matched in second table.
I would like this to be done in LINQ Query method syntax as I am adding lots of conditions depending on the input provided along with skip and limit.
If we have below Product and Favorite tables
So the output that I would like to have is:
meaning with the favorites as part of first set and which are not favorites should be behind them. Below are the tries that I did.
I am able to join the tables get the output but not sure how I can make sure that in the first page I get all the favs.
This answer was very near to what I thought but it gets the result and then does the ordering which will not be possible in my case as I am doing pagination and using IQueryable to get less data.
Group Join and Orderby while maintaining previous query
Open to any solutions to achieve the same.
[Table("Product")]
public class ProductModel
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ProductId { get; set; }
public string ProductName {get; set;}
public bool IsFavorite { get; set; }
}
[Table("UserFavorite")]
public class UserFavoriteModel
{
[Required]
public Guid UserId { get; set; }
[Required]
public Guid Identifier { get; set; }
[Required]
public FavoriteType Type { get; set; }
}
// Gets products
private async Task<List<ProductModel>> GetProductsAsync(
Guid categoryId,
Guid subCategoryId,
int from,
int limit)
{
var query = _context.Products.AsQueryable();
if (!string.IsNullOrEmpty(categoryId))
query = query.Where(product => product.CategoryId == categoryId);
if (!string.IsNullOrEmpty(subCategoryId))
query = query.Where(product => product.SubCategoryId == subCategoryId);
query = query.Skip(from).Take(limit);
var products = await query.ToListAsync();
query = query.GroupJoin(
_context.Favorites.AsNoTracking()
.Where(favorite => favorite.Type == FavoriteType.FASHION)
// This user Id will come from context just adding for overall picture.
.Where(favorite => favorite.UserId == userId),
//This orderby if I add will not make any difference.
//.OrderByDescending(favorite => favorite.Identifier),
v => v.ProductId,
f => f.Identifier,
(product, fav) => new { product, fav }).
SelectMany(x => x.Fav.DefaultIfEmpty(),
(x, y) => SetFavorite(x.Project, y));
}
private static ProductModel SetFavorite(ProductModel v, UserFavoriteModel si)
{
v.IsFavorite = (si != null);
return v;
}
I would do something like this:
var query =
_context.Products.AsQueryable().Select(p => new ProductModel {
ProductId = p.ProductId,
ProductName = p.ProductName,
IsFavorite =
_context.Favorites.Any(f =>
f.Identifier = p.ProductId &&
f.Type == FavoriteType.FASHION &&
f.UserId == userId
)
}).OrderByDescending(favorite => favorite.Identifier);

LinqToSql Equivalent to IN

How can do in LinqToSql an equivalent to the IN clause?
I have a SQL statement like this:
select *
from funktion
where institution_id IN (select id from institution where kreis_id = 36)
How can I express that in a Linq-to-Sql statement?
For explanation: the table funktion is something like CAPACITY and the table institution is representing INSTITUTIONS with hierarchy structure.
Every function is referenced between the tables with institution.id == funktion.institution_id. The hierarchy structures are from high to low: VERBAND, BEZIRK, KREIS, VEREIN.
Every VERBAND consists of several BEZIRK. Each BEZIRK consist of several KREIS. Each KREIS consist of several VEREIN. Every Hierarchy-layer has its ID for lower layers in referenced ID columns. So the lowest layer has a beside its own ID (Primary Key) a column for KREIS_ID, BEZIRK_ID, VERBAND_ID to know to which next levels it belongs. So I have to make statements for each layer to see the functions of all lower levels.
In my SQL example shown earlier, I have a KREIS that needs to get all FUNCTIONS where the INSTITUTION_ID is in a select of IDS where the KREIS_ID is like the OWN_ID (here for example 36).
I hope someone could help me. I am very new in LINQ, ,but have to change old software structures to a newer DataSource.
The IN statement in linq is Contains(). You can do something like:
var institutions = db.Institutions.Where(i => i.kreisId == 36).Select(i => i.id);
var funktions = db.Funktion.Where(f => institutions.Contains(f.instition_id)).ToList();
Solved with a JOIN in combination with WHERE:
from funktion in db.funktion
where funktion.deletedFlag == 0 && (funktion.bis == null || funktion.bis > DateTime.Now)
join institution in db.institution on funktion.institution_id equals institution.id
where institution.kreis_id == mySession.Current.benutzer.institution_id
select new
{
...
});
Try following :
class Program
{
static void Main(string[] args)
{
//select * from funktion where institution_id IN (select id from institution where kreis_id = 36)
Context db = new Context();
var results = db.funktion.Select(x => db.institution.Where(y => (x.institution_id == y.id) && (y.kreis_id == 36))).ToList();
}
}
public class Context
{
public List<funktion> funktion { get; set; }
public List<institution> institution { get; set; }
}
public class funktion
{
public string institution_id { get; set; }
}
public class institution
{
public string id { get; set; }
public int kreis_id { get; set; }
}

LINQ to SQL and LINQ to Entity Join with static AND condition

I'm trying to convert the following Join statement into LINQ TO SQL or LINQ to Entity. I know how to join tables in either implementation; but, i'm struggling with the AND clause in the Join statement.
SELECT DISTINCT
p.LastName,
p.FirstName
FROM
dbo.Patient p INNER JOIN dbo.FormPat fp ON p.PatientID = fp.PatientID
INNER JOIN dbo.TxCyclePhase tcp ON fp.TxCyclePhase = tcp.TxCyclePhaseID AND tcp.Type = 2
As far as LINQ to SQL is concerned, I have the followings:
var query = (from p in Context.Set<Patient>().AsNoTracking()
join fp in Context.Set<PatientForm>().AsNoTracking() on p.Id equals fp.PatientId
join tcp in Context.Set<TxCyclePhase>().AsNoTracking() on new { fp.TxCyclePhaseId, seconProperty = true } equals new { tcp.Id, seconProperty = tcp.Type == 2 }
select new
{
p.FirstName,
p.LastName,
}).Distinct();
However, I'm getting an ArgumentNullException on the second join statement.
For the LINQ to Entity, I have the followings, however, this is giving me a distinct IQueryable of FormPat, instead of Patient.
var patients = Context.Set<Patient>().AsNoTracking()
.SelectMany(p => p.Forms)
.Where(fp => fp.Phase.Type == 2)
.Distinct();
As far as the LINQ to Entity is concerned, I was able to figure it out. I'd still like to know how to do it in LINQ to SQL tho.
I'm using the EF fluent API. My Patient object looks like:
public Patient()
{
Programs = new HashSet<Program>();
}
public virtual ICollection<PatientForm> Forms { get; set; }
My PatientForm object looks like:
public class PatientForm
{
public int FormId { get; set; }
public Patient CurrentPatient { get; set; }
public TxCyclePhase Phase { get; set; }
}
And the CyclePhase object looks like:
public TxCyclePhase()
{
this.FormPats = new HashSet<PatientForm>();
}
public int Id { get; set; }
public virtual ICollection<PatientForm> FormPats { get; set; }
In the entity configurations, I have the relationships set. So, in the repository, all I have to do is to use the Any() function when selecting the Patient forms.
var patients = Context.Set<Patient>().AsNoTracking()
.Where(p => p.Forms.Any(f => f.Phase.Type == 2))
.Distinct();

Can you add a Where() to an IQueryable when the field is not in the selected output

Suppose I have a 2 table join in a function that returns an IQueryable, but the output is a named type that is neither of the two tables:
var qry = from p in Persons
join h in Hobbies on p.PersonId equals h.PersonId
select new OutputType
{
Name = p.FirstName,
Hobby = h.HobbyName
}
return qry
Let's say now I wanted to take this returned query and do something like:
var newQuery = qry.Where( p=>p.Age > 18 )
As you can see this is a problem because the IQueryable is of type OutputType, so I can't add a where to a person's age unless I were to add the Age to OutputType.
Is there anyway of 'breaking into' the IQueryable expression tree and adding a lambda somehow that will query on the source collection specified in it and add a Where clause to it? Or do I have do I have to add a Where field to the OutputType even though I'm uninterested in ultimately projecting it?
It is easier to narrow your view later than to try to backtrack. Here is a stripped down example of how I like to layer methods for reuse so that they spit out nice sql.
private IQueryable<Part> GetParts_Base()
{
//Proprietary. Replace with your own.
var context = ContextManager.GetDbContext();
var query = from c in context.Component
where c.Active
//kind of pointless to select into a new object without a join, but w/e
select new Part()
{
PartNumber = c.ComponentNumber,
Description = c.ComponentDescription,
Cost = c.ComponentCost,
Price = c.ComponentPrice
};
return query;
}
//Exclude cost from this view
public IEnumerable<Part_PublicView> GetParts_PublicView(decimal maxPrice)
{
var query = GetParts_Base();
var results = from p in query
where p.Cost < maxPrice
select new Part_PublicView()
{
PartNumber = p.PartNumber,
Description = p.Description,
Price = p.Price
};
return results;
}
public class Part_PublicView
{
public string PartNumber { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
}
private class Part : Part_PublicView
{
public decimal Cost { get; set; }
}
Linq-to-entity does not penalize you for selecting the extra column early on. As you can see, the sql includes the Cost column in the constraint but not in the select.
SELECT
1 AS [C1],
[Extent1].[ComponentNumber] AS [ComponentNumber],
[Extent1].[ComponentDescription] AS [ComponentDescription],
[Extent1].[ComponentPrice] AS [ComponentPrice]
FROM [dbo].[Component] AS [Extent1]
WHERE [Extent1].[ComponentCost] < #p__linq__0

LINQ Union between two tables with the same fields and then returned in a collection

I have given up trying to create a linq query to retrieve a sql server view which is a union between two tables. I will now try to create a linq union.
I have two views, MemberDuesPaid and MemberDuesOwed. They have the same fields in both; (BatchNo, TranDate, DebitAmount, CreditAmount, ReceiptNo, CheckNo, SocSecNo).
I also have a helper class in my application which is called MemberTransaction. It has all the same properties.
How how do i do a union between the two tables where socSecNo = the ssn passed in? I want to union the two tables and return an IEnumerable collection of MemberTransaction. After the two tables are unioned together i want to have the collection that is returned ordered by trandate in descending order.
You can do it in a Linq Union query:
var infoQuery =
(from paid in db.MemberDuesPaid
select new MemberTransaction() {
BatchNo = paid.BatchNo,
TranDate = paid.TranDate,
DebitAmount = paid.DebitAmount,
CreditAmount = paid.CreditAmount,
ReceiptNo = paid.ReceiptNo,
CheckNo = paid.CheckNo,
SocSecNo = paid.SocSecNo})
.Union
(from owed in db.MemberDuesOwed
select new MemberTransaction() {
BatchNo = owed.BatchNo,
TranDate = owed.TranDate,
DebitAmount = owed.DebitAmount,
CreditAmount = owed.CreditAmount,
ReceiptNo = owed.ReceiptNo,
CheckNo = owed.CheckNo,
SocSecNo = owed.SocSecNo});
That should return you a set with everything combined into a single list.
[Edit]
If you want distinct values, you can do something like this after the above statement (you can do it inline if you bracket everything, but this is simpler to explain):
infoQuery = infoQuery.Distinct();
The variable infoQuery will by this time be populated entirely with objects of type MemberTransaction rather than the two disparate types in the union statement.
Assuming you've got two collections, one representing each view:
var paid = new List<MemberDuesPaid>();
var owed = new List<MemberDuesOwed>();
Convert both collections above to instances of the third class before performing the union:
var everyone
= paid.Select(x => new MemberTransaction { BatchNo = x.BatchNo, ... })
.Union(owed.Select(x => new MemberTransaction { BatchNo = x.BatchNo, ... }))
.Where(x => x.SocSecNo == ssn)
.OrderByDescending(x => x.TranDate)
.ToList();
Now you've got a collection of MemberTransaction, but there's nothing to indicate how one MemberTransaction equals another. So if you just run the above, you'll end up with everything from both collections in the result, instead of a true union.
You have to tell it what makes two instance equal, by implementing IEquatable<T> on the MemberTransaction class.
public class MemberTransaction : IEquatable<MemberTransaction>
{
public int BatchNo { get; set; }
public DateTime TranDate { get; set; }
public decimal DebitAmount { get; set; }
public decimal CreditAmount { get; set; }
public int ReceiptNo { get; set; }
public int CheckNo { get; set; }
public int SocSecNo { get; set; }
public bool Equals(MemberTransaction other)
{
return BatchNo == other.BatchNo
&& TranDate.Equals(other.TranDate)
&& DebitAmount == other.DebitAmount
&& CreditAmount == other.CreditAmount
&& ReceiptNo == other.ReceiptNo
&& CheckNo == other.CheckNo
&& SocSecNo == other.SocSecNo;
}
}

Categories

Resources