LINQ Conditionally Add Join - c#

I have a LINQ query where I'm trying to return data from 2 tables, but the tables that I join are conditional.
This is what I'd like to do:
if (teamType == "A"){
var query = from foo in context.People
join foo2 in context.PeopleExtendedInfoA
select foo;
}
else {
var query = from foo in context.People
join foo2 in context.PeopleExtendedInfoB
select foo;
}
Then later on I'm filtering the query down even further. I obviously can't set it up this way because I won't be able to access "query" outside the if block, but it shows what I'm trying to do. This is an example of what I'm trying to do later on with the query:
if (state != null)
{
query = query.Where(p => p.State == state);
}
if (query != null) {
var queryFinal = from foo in query
select new PeopleGrid()
{
Name = foo.Name,
Address = foo.Address,
Hobby = foo2.Hobby
}
}
What I'm trying to return is all the data from table foo and then one field from the joined table, but depending on the logic, the joined table will differ. Both PeopleExtendedInfoA and PeopleExtendedInfoB both have the columb 'Hobby', but I have no way to access 'Hobby' from the joined table and that's the only field I need from the joined table.
How would I go about doing this?

Does PeopleExtendedInfoA and PeopleExtendedInfoB inherits from the same base class? You could create a IQueryable<BaseClass> and let the linq provider solve it for you when you add the join. For sample:
IQueryable<BasePeople> basePeople;
if (teamType == "A")
basePeople = context.PeopleExtendedInfoA;
else
basePeople = context.PeopleExtendedInfoB;
var query = from foo in context.People
join foo2 in basePeople on foo.Id equals foo2.PeopleId
select new PeopleGrid()
{
Name = foo.Name,
Address = foo.Address,
Hobby = foo2.Hobby
};

try like this:
var queryFinal = from foo in query
where foo.State == state !=null ? state : foo.State
select new PeopleGrid()
{
Name = foo.Name,
Address = foo.Address,
Hobby = foo2.Hobby
}

You can query into an intermediate type that holds the relevant fields, or if the query is simple enough you can use anonymous types as seen below. The important part is that the Join calls both have the same return types ({ p.Name, a.Hobby } and { p.Name, b.Hobby } will both be the same anon type).
var baseQuery = context.People;
var joined = type == "A" ?
baseQuery.Join(PeopleExtendedInfoA,
p => p.Id,
a => a.PeopleId,
(p, a) => new { p, a.Hobby }) :
baseQuery.Join(PeopleExtendedInfoB,
p => p.Id,
b => b.PeopleId,
(p, b) => new { p, b.Hobby });
var result = joined.Select(x => new
{
x.p.Name,
x.p.Address,
// etc.
x.Hobby
};

I figured it out. Thanks everyone for all the replies, it got my brain working again and gave me some new ideas (even though I didn't directly take any of them) I realize I needed to do the join at the very end instead of the beginning that way I don't have to deal with filtering on different types. This is what I did:
var query = from foo in context.People
select foo;
if (state != null)
{
query = query.Where(p => p.State == state);
}
if (query != null) {
if (teamType == "A")
{
var queryFinal = from foo in query
join foo2 in context.PeopleExtendedInfoA
select new PeopleGrid()
{
Name = foo.Name,
Address = foo.Address,
Hobby = foo2.Hobby
}
}
else
{
var queryFinal = from foo in query
join foo2 in context.PeopleExtendedInfoB
select new PeopleGrid()
{
Name = foo.Name,
Address = foo.Address,
Hobby = foo2.Hobby
}
}
}

Related

how to apply join or innerquery to connect two table

Here MyChildTable contains only id and the parent table contains id + name.
I have written a query to fetch the existing data from the table
await _dbContext.MyChildTable
.Where(c => c.CustomerId == **(select customerid from tableParent where customername= reqcustomername)**
Here i want to match customerId with the matching customer id from the second table ie tableParent.How to replace the query in to linq to get the proper record.select customerid from tableParent where customername= reqcustomername i want to replace this selection
I don't understand what you mean
so my maybe answer error
LINQ
using (entity entityData = new entity())
{
var checkqry2 = from T1 in entityData.MyChildTable.AsNoTracking()
join T2 in entityData.tableParent on
T1.CustomerId equals T2.customerid
where T1.customerid == "ID" && T2.customername == reqcustomername
group new { T2.customerid, T2.customername } by new { T1.customerid, T1.customername } into c
orderby c.Key.customerid
select new { customername=c.Key.customername,
customerid=c.Key.customerid,
};
}
you can try entity lambda
entity lambda
using (entity entityData = new entity())
{
var query1 = entityData.MyChildTable
.Join(entityData.tableParent , o => o.CustomerId , p => p.CustomerId , (o, p) => new
{
o.CustomerId,
p.customername,
}).Where(o => o.CustomerId == "123" && o.customername == "name").ToList();
}
Here I want to match customerId with the matching customer id from the
second table ie tableParent.How to replace the query in to linq to get
the proper record.select customerid from tableParent where
customername= reqcustomername i want to replace this selection
Well, lot of way around to handle this kind of scenario. Most easy and convenient way you could consider by using linq join or linq Enumerable which you can implement as following:
Sample Data:
var childList = new List<ChildTable>()
{
new ChildTable(){ Id =101,ChildName = "Child-A",CustomerId = 202},
new ChildTable(){ Id =102,ChildName = "Child-B",CustomerId = 203},
new ChildTable(){ Id =103,ChildName = "Child-C",CustomerId = 202},
new ChildTable(){ Id =104,ChildName = "Child-D",CustomerId = 204},
};
var parentList = new List<ParentTable>()
{
new ParentTable(){ Id =301,ParentName = "Parent-A",CustomerId = 202},
new ParentTable(){ Id =302,ParentName = "Parent-B",CustomerId = 202},
new ParentTable(){ Id =303,ParentName = "Parent-C",CustomerId = 203},
new ParentTable(){ Id =304,ParentName = "Parent-D",CustomerId = 205},
};
Linq Query:
Way One:
var findMatchedByCustId = from child in childList
where (from parent in parentList select parent.CustomerId)
.Contains(child.CustomerId)
select child;
Way Two:
var usingLinqJoin = (from parent in parentList
join child in childList on parent.CustomerId equals child.CustomerId
select parent).ToList().Distinct();
Output:
Note: If you need more information you could check our official document for Linq join and Linq Projction here.

Linq to Entities - where statement throws System.NotSupported Exception

var entity =
from document in db.Context.DocumentEntity
join product in db.Context.ProductEntity on document.ProductId equals product.Id
join partner in db.Context.PartnerEntity on product.PartnerId equals partner.Id
select new
{
document,
product,
partner
} into t1
where request.PartnerFilter.Contains(t1.partner.Name)
group t1 by t1.document.Date into rp
select new
{
PartnerName = rp.FirstOrDefault().partner.Name,
Date = rp.FirstOrDefault().document.Date,
Income = rp.Sum(x => x.document.Income),
Click= rp.Sum(x => x.document.Click)
};
result = ToDataTable(entity.OrderByDescending(d=>d.Date).ToList());
public static DataTable ToDataTable<T>(List<T> items)
{
DataTable dataTable = new DataTable(typeof(T).Name);
PropertyInfo[] Props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in Props)
{
var type = (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) ? Nullable.GetUnderlyingType(prop.PropertyType) : prop.PropertyType);
dataTable.Columns.Add(prop.Name, type);
}
foreach (T item in items)
{
var values = new object[Props.Length];
for (int i = 0; i < Props.Length; i++)
{
values[i] = Props[i].GetValue(item, null);
}
dataTable.Rows.Add(values);
}
return dataTable;
}
The problem is on where clause. request.PartnerFilter is a string array and might be null. I need to check if partner.Name is included in it. Kind of Sql Where-In. In the end entity.ToList() throws System.NotSupported Exception. How can I accomplish to filter?
If you want to use Contains inside the EF query expression tree, you need to ensure the variable is not null. And you need to do that (along with the condition if it needs to be applied) outside the query.
For instance:
var partnerFilter = request.PartnerFilter ?? Enumerable.Empty<string>();
bool applyPartnerFilter = partnerFilter.Any();
var entity =
...
where (!applyPartnerFilter || partnerFilter.Contains(t1.partner.Name))
...
But in my opinion it would be much better to apply the optional filter(s) outside the query, for instance:
var partners = db.Context.PartnerEntity.AsQueryable();
if (request.PartnerFilter != null && request.PartnerFilter.Any())
partners = partners.Where(partner => request.PartnerFilter.Contains(partner.Name));
var entity =
...
join partner in partners on product.PartnerId equals partner.Id
...
(no where)
Take this request part out of the equation because Entity Framework doesn't know what to do with a Request object, it can handle strings, string arrays, etc.
string[] strArray=request.PartnerFilter;
var entity =
from document in db.Context.DocumentEntity
join product in db.Context.ProductEntity on document.ProductId equals product.Id
join partner in db.Context.PartnerEntity on product.PartnerId equals partner.Id
select new
{
document,
product,
partner
} into t1
//Check if null
where strArray!=null && strArray.Any() && strArray.Contains(t1.partner.Name)
group t1 by t1.document.Date into rp
select new
{
PartnerName = rp.FirstOrDefault().partner.Name,
Date = rp.FirstOrDefault().document.Date,
Income = rp.Sum(x => x.document.Income),
Click= rp.Sum(x => x.document.Click)
};
Also, use Navigation Properties instead of joins
You use the SQL WHERE IN () Clause with Contains correct. Your only problem is the possible null exception.
What should happen if the array is empty? Would you like to have all the values? Use true if array is null otherwise false
Try this:
string[] partnerNames = request.PartnerFilter;
var entity =
from document in db.Context.DocumentEntity
join product in db.Context.ProductEntity on document.ProductId equals product.Id
join partner in db.Context.PartnerEntity on product.PartnerId equals partner.Id
select new
{
document,
product,
partner
} into t1
where partnerNames?.Contains(t1.partner.Name) ?? true
group t1 by t1.document.Date into rp
select new
{
PartnerName = rp.FirstOrDefault().partner.Name,
Date = rp.FirstOrDefault().document.Date,
Income = rp.Sum(x => x.document.Income),
Click= rp.Sum(x => x.document.Click)
};
WHERE IN - as query Syntax
var selected = from document in Document
where new[] {"Paul", "Peter"}.Contains(document.UserName)
select document
WHERE IN - as method Syntax
var selected = Document
.Where(d => new[] ["Paul","Peter"}.Contains(d.UserName))

LINQ query with addition to WHERE clause

I want to run the following LINQ query twice but with an addition to the Where clause:
var TickList =
(from comp in Companies
join eqRes in Equity_issues on comp.Ticker equals eqRes.Ticker
where !comp.Coverage_status.Contains("dropp")
&& !comp.Coverage_status.Contains("Repla") && eqRes.Primary_equity.Equals('N')
select new
{
LocalTick = eqRes.Local_ticker.Trim(),
Exchange = eqRes.Exchange_code.Contains("HKSE") ? "HK" : (eqRes.Exchange_code.Contains("NSDQ") ? "NASDQ" : eqRes.Exchange_code),
Ticker = comp.Ticker.Trim()
}).ToList();
This query works fine, but I need to pass an additional parameter to the Where clause:
where !comp.Coverage_status.Contains("dropp")
&& !comp.Coverage_status.Contains("Repla") && eqRes.Primary_equity.Equals('N')
&& !comp.Coverage_status.Contains("Intl") <--- new addition
Is there a way to do this without being DRY? Isn't there an efficient way of doing this without repeating the query with the new addition?
// select additional Intl field (similar to Exchange)
var TickList =
(from comp in Companies
join eqRes in Equity_issues on comp.Ticker equals eqRes.Ticker
where !comp.Coverage_status.Contains("dropp")
&& !comp.Coverage_status.Contains("Repla") && eqRes.Primary_equity.Equals('N')
select new
{
LocalTick = eqRes.Local_ticker.Trim(),
Exchange = eqRes.Exchange_code.Contains("HKSE") ? "HK" : (eqRes.Exchange_code.Contains("NSDQ") ? "NASDQ" : eqRes.Exchange_code),
Intl = comp.Coverage_status.Contains("Intl") ? 1 : 0,
Ticker = comp.Ticker.Trim()
}).ToList();
// use LINQ to objects to filter results of the 1st query
var intl = TickList.Where(x => x.Intl = 0).ToList();
See the code below if you want to be DRY:
var keywords = new string[] { "dropp", "Repla", "Intl" };
var TickList = Companies
.Join(Equity_issues, c => c.Ticker, e => e.Ticker, (c, e) => new { c, e })
.Where(ce => ce.e.Primary_equity.Equals('N')
&& keywords.All(v => !ce.c.Coverage_status.Contains(v)))
.Select(ce => new
{
LocalTick = ce.e.Local_ticker.Trim(),
Exchange = ce.e.Exchange_code.Contains("HKSE")
? "HK"
: (ce.e.Exchange_code.Contains("NSDQ")
? "NASDQ"
: ce.e.Exchange_code),
Ticker = ce.c.Ticker.Trim()
})
.ToList();
Now you can run this query with any combination of keywords.
Probably overkill here but there have been situations where I've created a full blown query object that internally held an IQueryable and used methods on the object to add the where clause (mostly for letting users filter and sort their results)
public class TickList{
IQueryable<Foo> _query;
public TickList(){
_query = from comp in Companies
join eqRes in Equity_issues on comp.Ticker equals eqRes.Ticker
select new Foo {
LocalTick = eqRes.Local_ticker.Trim(),
Exchange = eqRes.Exchange_code.Contains("HKSE") ? "HK" :(eqRes.Exchange_code.Contains("NSDQ") ? "NASDQ" : eqRes.Exchange_code),
Ticker = comp.Ticker.Trim()
};
}
public void WhereCoverageContains(string text){
_query = _query.Where(x => x.Coverage_Status.Contains(text));
}
public void WherePrimaryEquityIs(string text){
_query = _query.Where(x => x.PrimaryEquity.Equals(text));
}
public List<Foo> ToList(){
return _query.ToList();
}
}
It's super verbose so use with caution. Sometimes it's possible to be too dry.

Entity Framework 6 - Outer Joins and Method Syntax Queries

I'm trying to re-write the following SQL LEFT OUTER JOIN query using Entity Framework 6:
select tblA.*, tblB.*
from dbo.TableA tblA left outer join dbo.TableB tblB on
tblA.Student_id=tblB.StudentId and tblA.other_id=tblB.OtherId
where tblB.Id is null
Here's my current C# code:
using (var db = EFClass.CreateNewInstance())
{
var results = db.TableA.GroupJoin(
db.TableB,
tblA => new { StudentId = tblA.Student_id, OtherId = tblA.other_id },
tblB => new { tblB.StudentId, tblB.OtherId },
(tblA, tblB) => new { TableAData = tblA, TableBData = tblB }
)
.Where(x => x.TableBData.Id == null)
.AsNoTracking()
.ToList();
return results;
}
And here's the following compiler error I'm getting:
The type arguments cannot be inferred from the usage. Try specifying
the type arguments explicitly.
In a nutshell: I need to OUTER JOIN the two DbSet objects made available via Entity Framework, using more than one column in the join.
I'm also fairly certain this won't do a LEFT OUTER JOIN properly, even if I wasn't getting a compiler error; I suspect I need to involve the DefaultIfEmpty() method somehow, somewhere. Bonus points if you can help me out with that, too.
UPDATE #1: It works if I use a strong type in the join... is it simply unable to handle anonymous types, or am I doing something wrong?
public class StudentOther
{
public int StudentId { get; set; }
public int OtherId { get; set; }
}
using (var db = EFClass.CreateNewInstance())
{
var results = db.TableA.GroupJoin(
db.TableB,
tblA => new StudentOther { StudentId = tblA.Student_id, OtherId = tblA.other_id },
tblB => new StudentOther { StudentId = tblB.StudentId, OtherId = tblB.OtherId },
(tblA, tblB) => new { TableAData = tblA, TableBData = tblB }
)
.Where(x => x.TableBData.Id == null)
.AsNoTracking()
.ToList();
return results;
}
can you please try this solution? I'm not sure about the result :(
(from tblA in dbo.TableA
join tblB in dbo.TableB on new { tblA.Student_id, tblA.other_id } equals new { blB.StudentId, tblB.OtherId }
into tblBJoined
from tblBResult in tblBJoined.DefaultIfEmpty()
where tblBResult.Id == null
select new {
TableAData = tblA,
TableBData = tblB
}).ToList();

Refactoring C# code - doing more within Linq

The code below is what I currently have and works fine. I feel that I could do more of the work I am doing in Linq instead of C# code.
Is there is anyone out there who can accomplish the same result with more Linq code and less C# code.
public List<Model.Question> GetSurveyQuestions(string type, int typeID)
{
using (eMTADataContext db = DataContextFactory.CreateContext())
{
List<Model.Question> questions = new List<Model.Question>();
List<Linq.Survey_Question> survey_questions;
List<Linq.Survey> surveys = db.Surveys
.Where(s => s.Type.Equals(type) && s.Type_ID.Equals(typeID))
.ToList();
if (surveys.Count > 0)
{
survey_questions = db.Survey_Questions
.Where(sq => sq.Survey_ID == surveys[0].ID).ToList();
foreach (Linq.Survey_Question sq in survey_questions)
{
Model.Question q = Mapper.ToBusinessObject(sq.Question);
q.Status = sq.Status;
questions.Add(q);
}
}
else
{
questions = null;
}
return questions;
}
}
Here is my Mapper function from my Entity to Biz Object
internal static Model.Question ToBusinessObject(Linq.Question q)
{
return new Model.Question
{
ID = q.ID,
Name = q.Name,
Text = q.Text,
Choices = ToBusinessObject(q.Question_Choices.ToList())
};
}
I want my mapper funciton to map the Question Status like so.
internal static Model.Question ToBusinessObject(Linq.Question q)
{
return new Model.Question
{
ID = q.ID,
Name = q.Name,
Text = q.Text,
Choices = ToBusinessObject(q.Question_Choices.ToList()),
Status = q.Survey_Questions[?].Status
};
}
? the issue is this function does not know which survey to pull the status from.
Instead of creating the biz object then setting the Status property in a foreach loop like so
foreach (Linq.Survey_Question sq in survey_questions)
{
Model.Question q = Mapper.ToBusinessObject(sq.Question);
q.Status = sq.Status;
questions.Add(q);
}
I would like to somehow filter the EntitySet<Survey_Question> in the q object above in the calling method, such that there would only be one item in the q.Survey_Questions[?] collection.
below is my database schema and business object schema
What I needed to do was setup a join.
public List<Model.Question> GetSurveyQuestions(string type, int typeID)
{
using (eMTADataContext db = DataContextFactory.CreateContext())
{
return db.Survey_Questions
.Where(s => s.Survey.Type.Equals(type) && s.Survey.Type_ID.Equals(typeID))
.Join(db.Questions,
sq => sq.Question_ID,
q => q.ID,
(sq, q) => Mapper.ToBusinessObject(q, sq.Status)).ToList();
}
}
And then overload my Mapper Function
internal static Model.Question ToBusinessObject(Linq.Question q, string status)
{
return new Model.Question
{
ID = q.ID,
Name = q.Name,
Text = q.Text,
Status = status,
Choices = ToBusinessObject(q.Question_Choices.ToList()),
};
}
from question in db.Survey_Questions
let surveys = (from s in db.Surveys
where string.Equals(s.Type, type, StringComparison.InvariantCultureIgnoreCase) &&
s.Type_ID == typeID)
where surveys.Any() &&
surveys.Contains(s => s.ID == question.ID)
select new Mapper.Question
{
ID = question.Id,
Name = question.Name,
Text = question.Text,
Choices = ToBusinessObject(question.Question_Choices.ToList()),
Status = question.Status
}
Does that get you on the right track?
Why are you duplicating all your classes? You could just extend the LINQ to SQL classes with your business logic - they are partial classes. This is somewhat against the purpose of an OR mapper - persisting business entities.

Categories

Resources