How to perform "complex" join using Linq - linq

I need to join two objects (tables) A and B. For any A there can be zero to many B's. The query needs the return one row per A.
The B's I want to order before the join to be able to select the needed row from B's following a certain condition. Say B has a column Type. If there is a Type 1 then that's the B I need, if not: Type 2 must be selected, etc.
Now I think about it, I am not sure how I would to this even in T-sql.
I think something like this:
SELECT A.*
FROM A LEFT JOIN (
SELECT * FROM B AS B1 WHERE B1.Type = (SELECT TOP 1 B2.Type FROM B AS B2
WHERE B2.JoinID = B1.JoinID
ORDER BY B2.Type )
) AS B ON B.JoinID = A.JoinID
[edit]
With the answer of sgtz I've tried to make it work. If have to make an additional step because the field I want to order by is not present. I add this field in step 1, in step 2 I make a selection of the keys and join everything in step 3, but there I receive an error "The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'GroupJoin'." on join "join a in adressen1 on new { b.TopRelatieID..."
var adressen1 = from a in db.Adres
select new
{
RelatieAdres = a,
Sortering = (int)(a.AdresType.Code == codeVestAdres ?
1 : a.AdresType.Code == codePostAdres ?
2 : (100 + (int)a.AdresType.Code.ToCharArray()[0]))
};
var adressen2 = from b in adressen1
group b by new { RelatieID = b.RelatieAdres.RelatieID } into p
let TopAdresType = p.Min(at => at.Sortering)
select new { TopRelatieID = p.Key.RelatieID, TopAdresType };
var q = from k in db.Klants
join b in adressen2 on k.RelatieID equals b.TopRelatieID into b_join
from b in b_join.DefaultIfEmpty()
join a in adressen1 on new { b.TopRelatieID, b.TopAdresType } equals new { a.RelatieAdres.RelatieID, a.Sortering } into a_join
from a in a_join.DefaultIfEmpty()

Here's a worked example. I did it two stages.
[Test]
public void Test333()
{
List<Order> O;
var M = Prepare333Data(out O);
var OTop = from o in O
group o by new {id=o.id, orderid=o.orderid}
into p
let topType = p.Min(tt => tt.type)
select new Order(p.Key.id, p.Key.orderid, topType);
var ljoin = from m in M
join t in OTop on m.id equals t.id into ts
from u in ts.DefaultIfEmpty()
select new {u.id, u.orderid, u.type};
}
public class Manufacturer
{
public Manufacturer(int id, string name)
{
this.id = id;
this.name = name;
}
public int id { get; set; }
public string name { get; set; }
}
public class Order
{
public Order(int id, int orderid, int type)
{
this.orderid = orderid;
this.id = id;
this.type = type;
}
public int orderid { get; set; }
public int id { get; set; }
public int type { get; set; }
}
private List<Manufacturer> Prepare333Data(out List<Order> O)
{
var M = new List<Manufacturer>() {new Manufacturer(1, "Abc"), new Manufacturer(2, "Def")};
O = new List<Order>()
{
new Order(1, 1, 2),
new Order(1, 2, 2),
new Order(1, 2, 3),
new Order(2, 3, 1)
,
new Order(2, 3, 1)
,
new Order(2, 3, 2)
};
return M;
}
response to comments:
your "new {" creates a new anonymous type. Two anonymous types created by difference processes are said to have the same signature if types are declared in the same order and they have the same type definition (i.e. int matches int, not int matches short). I haven't tested this scenario extensively in LINQ.
That's why I worked with real concrete classes, and not anon types within the JOIN portion. There's probably a way to rework it with pure LINQ, but I don't know what that is yet. I'll post you a response if it occurs to me okay.
I'd suggest using concrete classes too for now.
i.e. instead of
*new {*
when doing joins, always use
*new CLASSNAME(){prop1="abc",prop2="123"*
It's a little bit longer, but safer... safer at least until we work out what is going on inside the LINQ internals.

To be meaningful, you should add at least something to query result, not only A.*. Otherwise you'll have a copy of A with some rows possibly duplicated. If I understood the question correctly, this SQL query should work:
SELECT DISTINCT A.*, B.Type
FROM A LEFT JOIN
(SELECT TOP (1) JoinID, Type
FROM B
ORDER BY Type
GROUP BY JoinID, Type
) AS B ON A.JoinID = B.JoinID
Translated to LINQ, it is (UPDATED)
(from a in As
join b in
(from b1 in Bs
orderby b1.Type
group b1 by b1.JoinID into B1
from b11 in B1
group b11 by b11.Type into B11
from b111 in B11
select new { b111.JoinID, b111.Type }).Take(1)
on a.JoinID equals b.JoinID into a_b
from ab in a_b.DefaultIfEmpty()
select new { a_b.JoinID, /*all other a properties*/ a_b.Type }).Distinct()
LINQ may not work 100% correct, but you should grab the idea.

Related

Linq left outer join doesn't work while matching sql does [duplicate]

How to perform left outer join in C# LINQ to objects without using join-on-equals-into clauses? Is there any way to do that with where clause?
Correct problem:
For inner join is easy and I have a solution like this
List<JoinPair> innerFinal = (from l in lefts from r in rights where l.Key == r.Key
select new JoinPair { LeftId = l.Id, RightId = r.Id})
but for left outer join I need a solution. Mine is something like this but it's not working
List< JoinPair> leftFinal = (from l in lefts from r in rights
select new JoinPair {
LeftId = l.Id,
RightId = ((l.Key==r.Key) ? r.Id : 0
})
where JoinPair is a class:
public class JoinPair { long leftId; long rightId; }
As stated in "Perform left outer joins":
var q =
from c in categories
join pt in products on c.Category equals pt.Category into ps_jointable
from p in ps_jointable.DefaultIfEmpty()
select new { Category = c, ProductName = p == null ? "(No products)" : p.ProductName };
If a database driven LINQ provider is used, a significantly more readable left outer join can be written as such:
from c in categories
from p in products.Where(c == p.Category).DefaultIfEmpty()
If you omit the DefaultIfEmpty() you will have an inner join.
Take the accepted answer:
from c in categories
join p in products on c equals p.Category into ps
from p in ps.DefaultIfEmpty()
This syntax is very confusing, and it's not clear how it works when you want to left join MULTIPLE tables.
Note
It should be noted that from alias in Repo.whatever.Where(condition).DefaultIfEmpty() is the same as an outer-apply/left-join-lateral, which any (decent) database-optimizer is perfectly capable of translating into a left join, as long as you don't introduce per-row-values (aka an actual outer apply). Don't do this in Linq-2-Objects (because there's no DB-optimizer when you use Linq-to-Objects).
Detailed Example
var query2 = (
from users in Repo.T_User
from mappings in Repo.T_User_Group
.Where(mapping => mapping.USRGRP_USR == users.USR_ID)
.DefaultIfEmpty() // <== makes join left join
from groups in Repo.T_Group
.Where(gruppe => gruppe.GRP_ID == mappings.USRGRP_GRP)
.DefaultIfEmpty() // <== makes join left join
// where users.USR_Name.Contains(keyword)
// || mappings.USRGRP_USR.Equals(666)
// || mappings.USRGRP_USR == 666
// || groups.Name.Contains(keyword)
select new
{
UserId = users.USR_ID
,UserName = users.USR_User
,UserGroupId = groups.ID
,GroupName = groups.Name
}
);
var xy = (query2).ToList();
When used with LINQ 2 SQL it will translate nicely to the following very legible SQL query:
SELECT
users.USR_ID AS UserId
,users.USR_User AS UserName
,groups.ID AS UserGroupId
,groups.Name AS GroupName
FROM T_User AS users
LEFT JOIN T_User_Group AS mappings
ON mappings.USRGRP_USR = users.USR_ID
LEFT JOIN T_Group AS groups
ON groups.GRP_ID == mappings.USRGRP_GRP
Edit:
See also "
Convert SQL Server query to Linq query "
for a more complex example.
Also, If you're doing it in Linq-2-Objects (instead of Linq-2-SQL), you should do it the old-fashioned way (because LINQ to SQL translates this correctly to join operations, but over objects this method forces a full scan, and doesn't take advantage of index searches, whyever...):
var query2 = (
from users in Repo.T_Benutzer
join mappings in Repo.T_Benutzer_Benutzergruppen on mappings.BEBG_BE equals users.BE_ID into tmpMapp
join groups in Repo.T_Benutzergruppen on groups.ID equals mappings.BEBG_BG into tmpGroups
from mappings in tmpMapp.DefaultIfEmpty()
from groups in tmpGroups.DefaultIfEmpty()
select new
{
UserId = users.BE_ID
,UserName = users.BE_User
,UserGroupId = mappings.BEBG_BG
,GroupName = groups.Name
}
);
Using lambda expression
db.Categories
.GroupJoin(db.Products,
Category => Category.CategoryId,
Product => Product.CategoryId,
(x, y) => new { Category = x, Products = y })
.SelectMany(
xy => xy.Products.DefaultIfEmpty(),
(x, y) => new { Category = x.Category, Product = y })
.Select(s => new
{
CategoryName = s.Category.Name,
ProductName = s.Product.Name
});
Now as an extension method:
public static class LinqExt
{
public static IEnumerable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(this IEnumerable<TLeft> left, IEnumerable<TRight> right, Func<TLeft, TKey> leftKey, Func<TRight, TKey> rightKey,
Func<TLeft, TRight, TResult> result)
{
return left.GroupJoin(right, leftKey, rightKey, (l, r) => new { l, r })
.SelectMany(
o => o.r.DefaultIfEmpty(),
(l, r) => new { lft= l.l, rght = r })
.Select(o => result.Invoke(o.lft, o.rght));
}
}
Use like you would normally use join:
var contents = list.LeftOuterJoin(list2,
l => l.country,
r => r.name,
(l, r) => new { count = l.Count(), l.country, l.reason, r.people })
Hope this saves you some time.
Take a look at this example.
This query should work:
var leftFinal = from left in lefts
join right in rights on left equals right.Left into leftRights
from leftRight in leftRights.DefaultIfEmpty()
select new { LeftId = left.Id, RightId = left.Key==leftRight.Key ? leftRight.Id : 0 };
An implementation of left outer join by extension methods could look like
public static IEnumerable<Result> LeftJoin<TOuter, TInner, TKey, Result>(
this IEnumerable<TOuter> outer, IEnumerable<TInner> inner
, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
, Func<TOuter, TInner, Result> resultSelector, IEqualityComparer<TKey> comparer)
{
if (outer == null)
throw new ArgumentException("outer");
if (inner == null)
throw new ArgumentException("inner");
if (outerKeySelector == null)
throw new ArgumentException("outerKeySelector");
if (innerKeySelector == null)
throw new ArgumentException("innerKeySelector");
if (resultSelector == null)
throw new ArgumentException("resultSelector");
return LeftJoinImpl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer ?? EqualityComparer<TKey>.Default);
}
static IEnumerable<Result> LeftJoinImpl<TOuter, TInner, TKey, Result>(
IEnumerable<TOuter> outer, IEnumerable<TInner> inner
, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
, Func<TOuter, TInner, Result> resultSelector, IEqualityComparer<TKey> comparer)
{
var innerLookup = inner.ToLookup(innerKeySelector, comparer);
foreach (var outerElment in outer)
{
var outerKey = outerKeySelector(outerElment);
var innerElements = innerLookup[outerKey];
if (innerElements.Any())
foreach (var innerElement in innerElements)
yield return resultSelector(outerElment, innerElement);
else
yield return resultSelector(outerElment, default(TInner));
}
}
The resultselector then has to take care of the null elements. Fx.
static void Main(string[] args)
{
var inner = new[] { Tuple.Create(1, "1"), Tuple.Create(2, "2"), Tuple.Create(3, "3") };
var outer = new[] { Tuple.Create(1, "11"), Tuple.Create(2, "22") };
var res = outer.LeftJoin(inner, item => item.Item1, item => item.Item1, (it1, it2) =>
new { Key = it1.Item1, V1 = it1.Item2, V2 = it2 != null ? it2.Item2 : default(string) });
foreach (var item in res)
Console.WriteLine(string.Format("{0}, {1}, {2}", item.Key, item.V1, item.V2));
}
take look at this example
class Person
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
}
class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}
public static void LeftOuterJoinExample()
{
Person magnus = new Person {ID = 1, FirstName = "Magnus", LastName = "Hedlund"};
Person terry = new Person {ID = 2, FirstName = "Terry", LastName = "Adams"};
Person charlotte = new Person {ID = 3, FirstName = "Charlotte", LastName = "Weiss"};
Person arlene = new Person {ID = 4, FirstName = "Arlene", LastName = "Huff"};
Pet barley = new Pet {Name = "Barley", Owner = terry};
Pet boots = new Pet {Name = "Boots", Owner = terry};
Pet whiskers = new Pet {Name = "Whiskers", Owner = charlotte};
Pet bluemoon = new Pet {Name = "Blue Moon", Owner = terry};
Pet daisy = new Pet {Name = "Daisy", Owner = magnus};
// Create two lists.
List<Person> people = new List<Person> {magnus, terry, charlotte, arlene};
List<Pet> pets = new List<Pet> {barley, boots, whiskers, bluemoon, daisy};
var query = from person in people
where person.ID == 4
join pet in pets on person equals pet.Owner into personpets
from petOrNull in personpets.DefaultIfEmpty()
select new { Person=person, Pet = petOrNull};
foreach (var v in query )
{
Console.WriteLine("{0,-15}{1}", v.Person.FirstName + ":", (v.Pet == null ? "Does not Exist" : v.Pet.Name));
}
}
// This code produces the following output:
//
// Magnus: Daisy
// Terry: Barley
// Terry: Boots
// Terry: Blue Moon
// Charlotte: Whiskers
// Arlene:
now you are able to include elements from the left even if that element has no matches in the right, in our case we retrived Arlene even he has no matching in the right
here is the reference
How to: Perform Left Outer Joins (C# Programming Guide)
This is the general form (as already provided in other answers)
var c =
from a in alpha
join b in beta on b.field1 equals a.field1 into b_temp
from b_value in b_temp.DefaultIfEmpty()
select new { Alpha = a, Beta = b_value };
However here's an explanation that I hope will clarify what this actually means!
join b in beta on b.field1 equals a.field1 into b_temp
essentially creates a separate result set b_temp that effectively includes null 'rows' for entries on the right hand side (entries in 'b').
Then the next line:
from b_value in b_temp.DefaultIfEmpty()
..iterates over that result set, setting the default null value for the 'row' on the right hand side, and setting the result of the right hand side row join to the value of 'b_value' (i.e. the value that's on the right hand side,if there's a matching record, or 'null' if there isn't).
Now, if the right hand side is the result of a separate LINQ query, it will consist of anonymous types, which can only either be 'something' or 'null'. If it's an enumerable however (e.g. a List - where MyObjectB is a class with 2 fields), then it's possible to be specific about what default 'null' values are used for its properties:
var c =
from a in alpha
join b in beta on b.field1 equals a.field1 into b_temp
from b_value in b_temp.DefaultIfEmpty( new MyObjectB { Field1 = String.Empty, Field2 = (DateTime?) null })
select new { Alpha = a, Beta_field1 = b_value.Field1, Beta_field2 = b_value.Field2 };
This ensures that 'b' itself isn't null (but its properties can be null, using the default null values that you've specified), and this allows you to check properties of b_value without getting a null reference exception for b_value. Note that for a nullable DateTime, a type of (DateTime?) i.e. 'nullable DateTime' must be specified as the 'Type' of the null in the specification for the 'DefaultIfEmpty' (this will also apply to types that are not 'natively' nullable e.g double, float).
You can perform multiple left outer joins by simply chaining the above syntax.
Here's an example if you need to join more than 2 tables:
from d in context.dc_tpatient_bookingd
join bookingm in context.dc_tpatient_bookingm
on d.bookingid equals bookingm.bookingid into bookingmGroup
from m in bookingmGroup.DefaultIfEmpty()
join patient in dc_tpatient
on m.prid equals patient.prid into patientGroup
from p in patientGroup.DefaultIfEmpty()
Ref: https://stackoverflow.com/a/17142392/2343
Here is a fairly easy to understand version using method syntax:
IEnumerable<JoinPair> outerLeft =
lefts.SelectMany(l =>
rights.Where(r => l.Key == r.Key)
.DefaultIfEmpty(new Item())
.Select(r => new JoinPair { LeftId = l.Id, RightId = r.Id }));
Extension method that works like left join with Join syntax
public static class LinQExtensions
{
public static IEnumerable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
this IEnumerable<TOuter> outer, IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector,
Func<TOuter, TInner, TResult> resultSelector)
{
return outer.GroupJoin(
inner,
outerKeySelector,
innerKeySelector,
(outerElement, innerElements) => resultSelector(outerElement, innerElements.FirstOrDefault()));
}
}
just wrote it in .NET core and it seems to be working as expected.
Small test:
var Ids = new List<int> { 1, 2, 3, 4};
var items = new List<Tuple<int, string>>
{
new Tuple<int, string>(1,"a"),
new Tuple<int, string>(2,"b"),
new Tuple<int, string>(4,"d"),
new Tuple<int, string>(5,"e"),
};
var result = Ids.LeftJoin(
items,
id => id,
item => item.Item1,
(id, item) => item ?? new Tuple<int, string>(id, "not found"));
result.ToList()
Count = 4
[0]: {(1, a)}
[1]: {(2, b)}
[2]: {(3, not found)}
[3]: {(4, d)}
I would like to add that if you get the MoreLinq extension there is now support for both homogenous and heterogeneous left joins now
http://morelinq.github.io/2.8/ref/api/html/Overload_MoreLinq_MoreEnumerable_LeftJoin.htm
example:
//Pretend a ClientCompany object and an Employee object both have a ClientCompanyID key on them
return DataContext.ClientCompany
.LeftJoin(DataContext.Employees, //Table being joined
company => company.ClientCompanyID, //First key
employee => employee.ClientCompanyID, //Second Key
company => new {company, employee = (Employee)null}, //Result selector when there isn't a match
(company, employee) => new { company, employee }); //Result selector when there is a match
EDIT:
In retrospect this may work, but it converts the IQueryable to an IEnumerable as morelinq does not convert the query to SQL.
You can instead use a GroupJoin as described here: https://stackoverflow.com/a/24273804/4251433
This will ensure that it stays as an IQueryable in case you need to do further logical operations on it later.
There are three tables: persons, schools and persons_schools, which connects persons to the schools they study in. A reference to the person with id=6 is absent in the table persons_schools. However the person with id=6 is presented in the result lef-joined grid.
List<Person> persons = new List<Person>
{
new Person { id = 1, name = "Alex", phone = "4235234" },
new Person { id = 2, name = "Bob", phone = "0014352" },
new Person { id = 3, name = "Sam", phone = "1345" },
new Person { id = 4, name = "Den", phone = "3453452" },
new Person { id = 5, name = "Alen", phone = "0353012" },
new Person { id = 6, name = "Simon", phone = "0353012" }
};
List<School> schools = new List<School>
{
new School { id = 1, name = "Saint. John's school"},
new School { id = 2, name = "Public School 200"},
new School { id = 3, name = "Public School 203"}
};
List<PersonSchool> persons_schools = new List<PersonSchool>
{
new PersonSchool{id_person = 1, id_school = 1},
new PersonSchool{id_person = 2, id_school = 2},
new PersonSchool{id_person = 3, id_school = 3},
new PersonSchool{id_person = 4, id_school = 1},
new PersonSchool{id_person = 5, id_school = 2}
//a relation to the person with id=6 is absent
};
var query = from person in persons
join person_school in persons_schools on person.id equals person_school.id_person
into persons_schools_joined
from person_school_joined in persons_schools_joined.DefaultIfEmpty()
from school in schools.Where(var_school => person_school_joined == null ? false : var_school.id == person_school_joined.id_school).DefaultIfEmpty()
select new { Person = person.name, School = school == null ? String.Empty : school.name };
foreach (var elem in query)
{
System.Console.WriteLine("{0},{1}", elem.Person, elem.School);
}
Easy way is to use Let keyword. This works for me.
from AItem in Db.A
Let BItem = Db.B.Where(x => x.id == AItem.id ).FirstOrDefault()
Where SomeCondition
Select new YourViewModel
{
X1 = AItem.a,
X2 = AItem.b,
X3 = BItem.c
}
This is a simulation of Left Join. If each item in B table not match to A item , BItem return null
This is a SQL syntax compare to LINQ syntax for inner and left outer joins.
Left Outer Join:
http://www.ozkary.com/2011/07/linq-to-entity-inner-and-left-joins.html
"The following example does a group join between product and category. This is essentially the left join. The into expression returns data even if the category table is empty. To access the properties of the category table, we must now select from the enumerable result by adding the from cl in catList.DefaultIfEmpty() statement.
As per my answer to a similar question, here:
Linq to SQL left outer join using Lambda syntax and joining on 2 columns (composite join key)
Get the code here, or clone my github repo, and play!
Query:
var petOwners =
from person in People
join pet in Pets
on new
{
person.Id,
person.Age,
}
equals new
{
pet.Id,
Age = pet.Age * 2, // owner is twice age of pet
}
into pets
from pet in pets.DefaultIfEmpty()
select new PetOwner
{
Person = person,
Pet = pet,
};
Lambda:
var petOwners = People.GroupJoin(
Pets,
person => new { person.Id, person.Age },
pet => new { pet.Id, Age = pet.Age * 2 },
(person, pet) => new
{
Person = person,
Pets = pet,
}).SelectMany(
pet => pet.Pets.DefaultIfEmpty(),
(people, pet) => new
{
people.Person,
Pet = pet,
});
This is the LeftJoin implementation I use. Notice that the the resultSelector expression accepts 2 parameters: one instance from both sides of the join. In most other implementations that I've seen the result selector only accepts one parameter, which is a "join model" with a left/right or outer/inner property. I like this implementation better because it has the same method signature as the built-in Join method. It also works with IQueryables and EF.
var results = DbContext.Categories
.LeftJoin(
DbContext.Products, c => c.Id, p => p.CategoryId,
(c, p) => new { Category = c, ProductName = p == null ? "(No Products)" : p.ProductName })
.ToList();
public static class QueryableExtensions
{
public static IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
this IQueryable<TOuter> outer,
IEnumerable<TInner> inner, Expression<Func<TOuter, TKey>> outerKeySelector,
Expression<Func<TInner, TKey>> innerKeySelector,
Expression<Func<TOuter, TInner, TResult>> resultSelector)
{
var query = outer
.GroupJoin(inner, outerKeySelector, innerKeySelector, (o, i) => new { o, i })
.SelectMany(o => o.i.DefaultIfEmpty(), (x, i) => new { x.o, i });
return ApplySelector(query, x => x.o, x => x.i, resultSelector);
}
private static IQueryable<TResult> ApplySelector<TSource, TOuter, TInner, TResult>(
IQueryable<TSource> source,
Expression<Func<TSource, TOuter>> outerProperty,
Expression<Func<TSource, TInner>> innerProperty,
Expression<Func<TOuter, TInner, TResult>> resultSelector)
{
var p = Expression.Parameter(typeof(TSource), $"param_{Guid.NewGuid()}".Replace("-", string.Empty));
Expression body = resultSelector?.Body
.ReplaceParameter(resultSelector.Parameters[0], outerProperty.Body.ReplaceParameter(outerProperty.Parameters[0], p))
.ReplaceParameter(resultSelector.Parameters[1], innerProperty.Body.ReplaceParameter(innerProperty.Parameters[0], p));
var selector = Expression.Lambda<Func<TSource, TResult>>(body, p);
return source.Select(selector);
}
}
public static class ExpressionExtensions
{
public static Expression ReplaceParameter(this Expression source, ParameterExpression toReplace, Expression newExpression)
=> new ReplaceParameterExpressionVisitor(toReplace, newExpression).Visit(source);
}
public class ReplaceParameterExpressionVisitor : ExpressionVisitor
{
public ReplaceParameterExpressionVisitor(ParameterExpression toReplace, Expression replacement)
{
this.ToReplace = toReplace;
this.Replacement = replacement;
}
public ParameterExpression ToReplace { get; }
public Expression Replacement { get; }
protected override Expression VisitParameter(ParameterExpression node)
=> (node == ToReplace) ? Replacement : base.VisitParameter(node);
}
Perform left outer joins in linq C#
// Perform left outer joins
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
class Child
{
public string Name { get; set; }
public Person Owner { get; set; }
}
public class JoinTest
{
public static void LeftOuterJoinExample()
{
Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
Child barley = new Child { Name = "Barley", Owner = terry };
Child boots = new Child { Name = "Boots", Owner = terry };
Child whiskers = new Child { Name = "Whiskers", Owner = charlotte };
Child bluemoon = new Child { Name = "Blue Moon", Owner = terry };
Child daisy = new Child { Name = "Daisy", Owner = magnus };
// Create two lists.
List<Person> people = new List<Person> { magnus, terry, charlotte, arlene };
List<Child> childs = new List<Child> { barley, boots, whiskers, bluemoon, daisy };
var query = from person in people
join child in childs
on person equals child.Owner into gj
from subpet in gj.DefaultIfEmpty()
select new
{
person.FirstName,
ChildName = subpet!=null? subpet.Name:"No Child"
};
// PetName = subpet?.Name ?? String.Empty };
foreach (var v in query)
{
Console.WriteLine($"{v.FirstName + ":",-25}{v.ChildName}");
}
}
// This code produces the following output:
//
// Magnus: Daisy
// Terry: Barley
// Terry: Boots
// Terry: Blue Moon
// Charlotte: Whiskers
// Arlene: No Child
https://dotnetwithhamid.blogspot.in/
Here's a version of the extension method solution using IQueryable instead of IEnumerable
public class OuterJoinResult<TLeft, TRight>
{
public TLeft LeftValue { get; set; }
public TRight RightValue { get; set; }
}
public static IQueryable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(this IQueryable<TLeft> left, IQueryable<TRight> right, Expression<Func<TLeft, TKey>> leftKey, Expression<Func<TRight, TKey>> rightKey, Expression<Func<OuterJoinResult<TLeft, TRight>, TResult>> result)
{
return left.GroupJoin(right, leftKey, rightKey, (l, r) => new { l, r })
.SelectMany(o => o.r.DefaultIfEmpty(), (l, r) => new OuterJoinResult<TLeft, TRight> { LeftValue = l.l, RightValue = r })
.Select(result);
}
If you need to join and filter on something, that can be done outside of the join. Filter can be done after creating the collection.
In this case if I do this in the join condition I reduce the rows that are returned.
Ternary condition is used (= n == null ? "__" : n.MonDayNote,)
If the object is null (so no match), then return what is after the ?. __, in this case.
Else, return what is after the :, n.MonDayNote.
Thanks to the other contributors that is where I started with my own issue.
var schedLocations = (from f in db.RAMS_REVENUE_LOCATIONS
join n in db.RAMS_LOCATION_PLANNED_MANNING on f.revenueCenterID equals
n.revenueCenterID into lm
from n in lm.DefaultIfEmpty()
join r in db.RAMS_LOCATION_SCHED_NOTE on f.revenueCenterID equals r.revenueCenterID
into locnotes
from r in locnotes.DefaultIfEmpty()
where f.LocID == nLocID && f.In_Use == true && f.revenueCenterID > 1000
orderby f.Areano ascending, f.Locname ascending
select new
{
Facname = f.Locname,
f.Areano,
f.revenueCenterID,
f.Locabbrev,
// MonNote = n == null ? "__" : n.MonDayNote,
MonNote = n == null ? "__" : n.MonDayNote,
TueNote = n == null ? "__" : n.TueDayNote,
WedNote = n == null ? "__" : n.WedDayNote,
ThuNote = n == null ? "__" : n.ThuDayNote,
FriNote = n == null ? "__" : n.FriDayNote,
SatNote = n == null ? "__" : n.SatDayNote,
SunNote = n == null ? "__" : n.SunDayNote,
MonEmpNbr = n == null ? 0 : n.MonEmpNbr,
TueEmpNbr = n == null ? 0 : n.TueEmpNbr,
WedEmpNbr = n == null ? 0 : n.WedEmpNbr,
ThuEmpNbr = n == null ? 0 : n.ThuEmpNbr,
FriEmpNbr = n == null ? 0 : n.FriEmpNbr,
SatEmpNbr = n == null ? 0 : n.SatEmpNbr,
SunEmpNbr = n == null ? 0 : n.SunEmpNbr,
SchedMondayDate = n == null ? dMon : n.MondaySchedDate,
LocNotes = r == null ? "Notes: N/A" : r.LocationNote
}).ToList();
Func<int, string> LambdaManning = (x) => { return x == 0 ? "" : "Manning:" + x.ToString(); };
DataTable dt_ScheduleMaster = PsuedoSchedule.Tables["ScheduleMasterWithNotes"];
var schedLocations2 = schedLocations.Where(x => x.SchedMondayDate == dMon);
class Program
{
List<Employee> listOfEmp = new List<Employee>();
List<Department> listOfDepart = new List<Department>();
public Program()
{
listOfDepart = new List<Department>(){
new Department { Id = 1, DeptName = "DEV" },
new Department { Id = 2, DeptName = "QA" },
new Department { Id = 3, DeptName = "BUILD" },
new Department { Id = 4, DeptName = "SIT" }
};
listOfEmp = new List<Employee>(){
new Employee { Empid = 1, Name = "Manikandan",DepartmentId=1 },
new Employee { Empid = 2, Name = "Manoj" ,DepartmentId=1},
new Employee { Empid = 3, Name = "Yokesh" ,DepartmentId=0},
new Employee { Empid = 3, Name = "Purusotham",DepartmentId=0}
};
}
static void Main(string[] args)
{
Program ob = new Program();
ob.LeftJoin();
Console.ReadLine();
}
private void LeftJoin()
{
listOfEmp.GroupJoin(listOfDepart.DefaultIfEmpty(), x => x.DepartmentId, y => y.Id, (x, y) => new { EmpId = x.Empid, EmpName = x.Name, Dpt = y.FirstOrDefault() != null ? y.FirstOrDefault().DeptName : null }).ToList().ForEach
(z =>
{
Console.WriteLine("Empid:{0} EmpName:{1} Dept:{2}", z.EmpId, z.EmpName, z.Dpt);
});
}
}
class Employee
{
public int Empid { get; set; }
public string Name { get; set; }
public int DepartmentId { get; set; }
}
class Department
{
public int Id { get; set; }
public string DeptName { get; set; }
}
OUTPUT
Overview: In this code snippet, I demonstrate how to group by ID where Table1 and Table2 have a one to many relationship. I group on
Id, Field1, and Field2. The subquery is helpful, if a third Table lookup is required and it would have required a left join relationship.
I show a left join grouping and a subquery linq. The results are equivalent.
class MyView
{
public integer Id {get,set};
public String Field1 {get;set;}
public String Field2 {get;set;}
public String SubQueryName {get;set;}
}
IList<MyView> list = await (from ci in _dbContext.Table1
join cii in _dbContext.Table2
on ci.Id equals cii.Id
where ci.Field1 == criterion
group new
{
ci.Id
} by new { ci.Id, cii.Field1, ci.Field2}
into pg
select new MyView
{
Id = pg.Key.Id,
Field1 = pg.Key.Field1,
Field2 = pg.Key.Field2,
SubQueryName=
(from chv in _dbContext.Table3 where chv.Id==pg.Key.Id select chv.Field1).FirstOrDefault()
}).ToListAsync<MyView>();
Compared to using a Left Join and Group new
IList<MyView> list = await (from ci in _dbContext.Table1
join cii in _dbContext.Table2
on ci.Id equals cii.Id
join chv in _dbContext.Table3
on cii.Id equals chv.Id into lf_chv
from chv in lf_chv.DefaultIfEmpty()
where ci.Field1 == criterion
group new
{
ci.Id
} by new { ci.Id, cii.Field1, ci.Field2, chv.FieldValue}
into pg
select new MyView
{
Id = pg.Key.Id,
Field1 = pg.Key.Field1,
Field2 = pg.Key.Field2,
SubQueryName=pg.Key.FieldValue
}).ToListAsync<MyView>();
This is the prettiest solution I use, give it a try! 😉
(from c in categories
let product = products.Where(d=> d.Category == c.Category).FirstOrDefault()
select new { Category = c, ProductName = p == null ? "(No products)" : product.ProductName };
(from a in db.Assignments
join b in db.Deliveryboys on a.AssignTo equals b.EmployeeId
//from d in eGroup.DefaultIfEmpty()
join c in db.Deliveryboys on a.DeliverTo equals c.EmployeeId into eGroup2
from e in eGroup2.DefaultIfEmpty()
where (a.Collected == false)
select new
{
OrderId = a.OrderId,
DeliveryBoyID = a.AssignTo,
AssignedBoyName = b.Name,
Assigndate = a.Assigndate,
Collected = a.Collected,
CollectedDate = a.CollectedDate,
CollectionBagNo = a.CollectionBagNo,
DeliverTo = e == null ? "Null" : e.Name,
DeliverDate = a.DeliverDate,
DeliverBagNo = a.DeliverBagNo,
Delivered = a.Delivered
});

EF Core LINQ use scalar function

I use Entity Framework Core 2.1.
I have a scalar function in the database which adds specified number of days.
I created an extension method to execute it:
public static class AdventureWorks2012ContextExt
{
public static DateTime? ExecFn_AddDayPeriod(this AdventureWorks2012Context db, DateTime dateTime, int days, string periodName)
{
var sql = $"set #result = dbo.[fn_AddDayPeriod]('{dateTime.ToString("yyyy-MM-dd HH:mm:ss.fff")}', {days}, '{periodName}')";
var output = new SqlParameter { ParameterName = #"result", DbType = DbType.DateTime, Size = 16, Direction = ParameterDirection.Output };
var result = db.Database.ExecuteSqlCommand(sql, output);
return output.Value as DateTime?;
}
}
I try to use a scalar function in the query (to simplify things I use AdventureWorks2012) as follows:
var persons =
(from p in db.Person
join pa in db.Address on p.BusinessEntityId equals pa.AddressId
where p.ModifiedDate > db.ExecFn_AddDayPeriod(pa.ModifiedDate, 100, "DayPeriod_day")
select p).ToList();
But get an System.InvalidOperationException: 'A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.'
How can I achieve this?
UPDATE:
I managed to do it with the help of Ivan's answer:
var persons =
(from p in db.Person
join bea in db.BusinessEntityAddress on p.BusinessEntityId equals bea.BusinessEntityId
join a in db.Address on bea.AddressId equals a.AddressId
where p.ModifiedDate > AdventureWorks2012ContextFunctions.AddDayPeriod(a.ModifiedDate, 100, "DayPeriod_day")
select p).ToList();
But now I need to update ModifiedDate for filtered persons. So I'm doing like this:
var persons =
(from p in db.Person
join bea in db.BusinessEntityAddress on p.BusinessEntityId equals bea.BusinessEntityId
join a in db.Address on bea.AddressId equals a.AddressId
let date = AdventureWorks2012ContextFunctions.AddDayPeriod(a.ModifiedDate, 100, "DayPeriod_day")
where p.ModifiedDate > date
select new { Person = p, NewDate = date }).ToList();
foreach (var p in persons)
p.Person.ModifiedDate = p.NewDate ?? DateTime.Now;
db.SaveChanges();
But got System.NotSupportedException: 'Specified method is not supported.'
How can I use scalar function in select statement?
I tried to split the query by two parts:
var filteredPersons = // ok
(from p in db.Person
join bea in db.BusinessEntityAddress on p.BusinessEntityId equals bea.BusinessEntityId
join a in db.Address on bea.AddressId equals a.AddressId
where p.ModifiedDate > AdventureWorks2012ContextFunctions.AddDayPeriod(a.ModifiedDate, 100, "DayPeriod_day")
select new { Person = p, a.ModifiedDate }).ToList();
var persons = // here an exception occurs
(from p in filteredPersons
select new { Person = p, NewDate = AdventureWorks2012ContextFunctions.AddDayPeriod(p.ModifiedDate, 100, "DayPeriod_day") }).ToList();
Instead of invoking the function client side (which is this particular case happens as part of the client evaluation of the query filter, while the query reading is still in progress), you can use EF Core Database scalar function mapping so it
can be used in LINQ queries and translated to SQL.
One way to do that is to create a public static method in the derived context class and mark it with DbFunction attribute:
public partial class AdventureWorks2012Context
{
[DbFunction("fn_AddDayPeriod")]
public static DateTime? AddDayPeriod(DateTime dateTime, int days, string periodName) => throw new NotSupportedException();
}
and use
where p.ModifiedDate > AdventureWorks2012Context.AddDayPeriod(pa.ModifiedDate, 100, "DayPeriod_day")
Another way is to create a public static method in another class
public static class AdventureWorks2012DbFunctions
{
[DbFunction("fn_AddDayPeriod")]
public static DateTime? AddDayPeriod(DateTime dateTime, int days, string periodName) => throw new NotSupportedException();
}
but then you'll need to register it with fluent API (which happens automatically for methods defined inside the context derived class):
modelBuilder
.HasDbFunction(() => AdventureWorks2012DbFunctions.AddDayPeriod(default(DateTime), default(int), default(string)));
The usage is the same:
where p.ModifiedDate > AdventureWorksDbFunctions.AddDayPeriod(pa.ModifiedDate, 100, "DayPeriod_day")

Linq join two lists: is it more efficient to use Dictionary?

Final rephrase
Below I join two sequences and I wondered if it would be faster to create a Dictionary of one sequence with the keySelector of the join as key and iterate through the other collection and find the key in the dictionary.
This only works if the key selector is unique. A real join has no problem with two records having the same key. In a dictionary you'll have to have unique keys
I measured the difference, and I noticed that the dictionary method is about 13% faster. In most use cases ignorable. See my answer to this question
Rephrased question
Some suggested that this question is the same question as LINQ - Using where or join - Performance difference?, but this one is not about using where or join, but about using a Dictionary to perform the join.
My question is: if I want to join two sequences based on a key selector, which method would be faster?
Put all items of one sequence in a Dictionary and enumerate the other sequence to see if the item is in the Dictionary. This would mean to iterate through both sequences once and calculate hash codes on the keySelector for every item in both sequences once.
The other method: use System.Enumerable.Join.
The question is: Would Enumerable.Join for each element in the first list iterate through the elements in the second list to find a match according to the key selector, having to compare N * N elements (is this called second order?) or would it use a more advanced method?
Original question with examples
I have two classes, both with a property Reference. I have two sequences of these classes and I want to join them based on equal Reference.
Class ClassA
{
public string Reference {get;}
...
}
public ClassB
{
public string Reference {get;}
...
}
var listA = new List<ClassA>()
{
new ClassA() {Reference = 1, ...},
new ClassA() {Reference = 2, ...},
new ClassA() {Reference = 3, ...},
new ClassA() {Reference = 4, ...},
}
var listB = new List<ClassB>()
{
new ClassB() {Reference = 1, ...},
new ClassB() {Reference = 3, ...},
new ClassB() {Reference = 5, ...},
new ClassB() {Reference = 7, ...},
}
After the join I want combinations of ClassA objects and ClassB objects that have an equal Reference. This is quite simple to do:
var myJoin = listA.Join(listB, // join listA and listB
a => a.Reference, // from listA take Reference
b => b.Reference, // from listB take Reference
(objectA, objectB) => // if references equal
new {A = objectA, B = objectB}); // return combination
I'm not sure how this works, but I can imagine that for each a in listA the listB is iterated to see if there is a b in listB with the same reference as A.
Question: if I know that the references are Distinct wouldn't it be more efficient to convert B into a Dictionary and compare the Reference for each element in listA:
var dictB = listB.ToDictionary<string, ClassB>()
var myJoin = listA
.Where(a => dictB.ContainsKey(a.Reference))
.Select(a => new (A = a, B = dictB[a.Reference]);
This way, every element of listB has to be accessed once to put in the dictionary and every element of listA has to be accessed once, and the hascode of Reference has to be calculated once.
Would this method be faster for large collections?
I created a test program for this and measured the time it took.
Suppose I have a class of Person, each person has a name and a Father property which is of type Person. If the Father is not know, the Father property is null
I have a sequence of Bastards (no father) that have exactly one Son and One Daughter. All Daughters are put in one sequence. All sons are put in another sequences.
The query: join the sons and the daughters that have the same father.
Results: Joining 1 million families using Enumerable.Join took 1.169 sec. Joining them using Dictionary join used 1.024 sec. Ever so slightly faster.
The code:
class Person : IEquatable<Person>
{
public string Name { get; set; }
public Person Father { get; set; }
// + a lot of equality functions get hash code etc
// for those interested: see the bottom
}
const int nrOfBastards = 1000000; // one million
var bastards = Enumerable.Range (0, nrOfBastards)
.Select(i => new Person()
{ Name = 'B' + i.ToString(), Father = null })
.ToList();
var sons = bastards.Select(father => new Person()
{Name = "Son of " + father.Name, Father = father})
.ToList();
var daughters = bastards.Select(father => new Person()
{Name = "Daughter of " + father.Name, Father = father})
.ToList();
// join on same parent: Traditionally and using Dictionary
var stopwatch = Stopwatch.StartNew();
this.TraditionalJoin(sons, daughters);
var time = stopwatch.Elapsed;
Console.WriteLine("Traditional join of {0} sons and daughters took {1:F3} sec", nrOfBastards, time.TotalSeconds);
stopwatch.Restart();
this.DictionaryJoin(sons, daughters);
time = stopwatch.Elapsed;
Console.WriteLine("Dictionary join of {0} sons and daughters took {1:F3} sec", nrOfBastards, time.TotalSeconds);
}
private void TraditionalJoin(IEnumerable<Person> boys, IEnumerable<Person> girls)
{ // join on same parent
var family = boys
.Join(girls,
boy => boy.Father,
girl => girl.Father,
(boy, girl) => new { Son = boy.Name, Daughter = girl.Name })
.ToList();
}
private void DictionaryJoin(IEnumerable<Person> sons, IEnumerable<Person> daughters)
{
var sonsDictionary = sons.ToDictionary(son => son.Father);
var family = daughters
.Where(daughter => sonsDictionary.ContainsKey(daughter.Father))
.Select(daughter => new { Son = sonsDictionary[daughter.Father], Daughter = daughter })
.ToList();
}
For those interested in the equality of Persons, needed for a proper dictionary:
class Person : IEquatable<Person>
{
public string Name { get; set; }
public Person Father { get; set; }
public bool Equals(Person other)
{
if (other == null)
return false;
else if (Object.ReferenceEquals(this, other))
return true;
else if (this.GetType() != other.GetType())
return false;
else
return String.Equals(this.Name, other.Name, StringComparison.OrdinalIgnoreCase);
}
public override bool Equals(object obj)
{
return this.Equals(obj as Person);
}
public override int GetHashCode()
{
const int prime1 = 899811277;
const int prime2 = 472883293;
int hash = prime1;
unchecked
{
hash = hash * prime2 + this.Name.GetHashCode();
if (this.Father != null)
{
hash = hash * prime2 + this.Father.GetHashCode();
}
}
return hash;
}
public override string ToString()
{
return this.Name;
}
public static bool operator==(Person x, Person y)
{
if (Object.ReferenceEquals(x, null))
return Object.ReferenceEquals(y, null);
else
return x.Equals(y);
}
public static bool operator!=(Person x, Person y)
{
return !(x==y);
}
}

Seperate linq results by first letter asp.net mvc 3

I have a linq query which returns results ordered by first letter. Is there a way to return the first letter before the group ordered by that letter? For instance;
**A**
Acountants
Apothecary
**B**
Basketball
Biscuits
and so on. I tried grouping my results like this;
var companyquery = (from c in db.Categories
group c by c.Name.Substring(0, 1)
into cgroup
select new
{
FirstLetter = cgroup.Key,
Names = cgroup
}).OrderBy(letter => letter.FirstLetter);
return View(companyquery);
but got error:
"The model item passed into the dictionary is of type 'System.Data.Entity.Infrastructure.DbQuery1[<>f__AnonymousType31[System.String]]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[NewAtAClick.Models.Category]'.'
So now I'm using orderby instead of group _ into _ . Here is my query;
var companyquery = (from c in db.Categories
orderby c.Name.Substring(0, 1)
select c);
This returns them in alphebetical order. I tried saying
letter1 = companyquery.ToString().SubString(0,1)
then
return view(letter1 + companyquery.ToList());
But nothing.
Thanks in advance!
The error says it all. Your view expects a model of type IEnumerable<Category> but you passed in something else (in this case because you are creating a new anonymous type via the call to select new { ... }).
Instead you could declare a new type:
public class AlphabeticalMapping<T> {
public char FirstLetter { get; set; }
public List<T> Items { get; set; }
}
And change your query to return:
var companyquery = (from c in db.Categories
group c by c.Name.Substring(0, 1)
into cgroup
select new AlphabeticalMapping<Category>() {
FirstLetter = cgroup.Key,
Items= cgroup.ToList()
}).OrderBy(mapping => mapping.FirstLetter);
And change your view to expect IEnumerable<AlphabeticalMapping<Category>>
What happens if you try this?
var companyquery = (from c in db.Categories
group c by c.Name.Substring(0, 1)
into cgroup
select new,
{
FirstLetter = cgroup.Key,
Names = cgroup
})
.OrderBy(letter => letter.FirstLetter)
.ToDictionary(k => k.FirstLetter, e => e.Names);

Need Help Translating SQL Server UNION Syntax to LINQ

I have the below SQL which works just fine:
SELECT Message, CreateDate, AccountId, AlertTypeId
FROM dbo.Alerts
UNION
SELECT TOP (100) PERCENT Status, CreateDate, AccountId,
(SELECT 10 AS Expr1) AS AlertTypeId
FROM dbo.StatusUpdates
WHERE AccountId = PassedInParameter
ORDER BY CreateDate DESC
I am trying to convert it to LINQ, which doesn't work just fine :) Obviously, there is a lot wrong here - it is just a rough start. It does not account for the above temp column or the order by condition and the generics / return type ambiguity is my attempt to make sense of the two different return types:
public List<T> GetSomething<T>(Int32 accountId)
{
List<T> result;
using (DataContext dc = _conn.GetContext())
{
IEnumerable<Alert> alerts = (from a in dc.Alerts
where a.AccountId == accountId
select a);
IEnumerable<StatusUpdate> updates = (from s in dc.StatusUpdates
where s.AccountId == accountId
select s);
IEnumerable<T> obj = alerts.Union(updates);
result = obj.ToList();
}
return result;
}
The problems I am having are:
1) I am dealing with two different types (Alerts and StatusUpdate) in my selects and
I am not sure how to combine them (or what type to return). I am guessing this might
be solved with generics?
2) In my SQL, I have this code: (SELECT 10 AS Expr1) AS AlertTypeId which adds the value ten to the temp column AlertTypeId (allowing the union to match it to Alert's real column AlertTypeId). How are temp columns such as this accomplished in LINQ / how do I do this?
Thanks for your help.
EDIT---------------------------------EDIT------------------------------------------EDIT
OK, I am a little further along. Below is what I have currently. You will notice I added some logic to return the updates for friend relations. I also made this a generic method of type IList given that alerts and updates have to be generic to agree. I pass in StatusUpdate in the calling method (further down below).
public IList GetUpdatesByAccountId<T>(Int32 accountId)
{
List<Friend> friends = _friendRepository.GetFriendsByAccountId(accountId);
using (DataContext dc = _conn.GetContext())
{
// Get all the account ids related to this user
var friendAccountIds =
friends.Select(friend => friend.MyFriendsAccountId).Distinct();
friendAccountIds = friendAccountIds.Concat(new[] { accountId });
var updates =
dc.StatusUpdates.Where(s => s.AccountId.HasValue && friendAccountIds.Contains(s.AccountId.Value)).Select(
s => new { Alert = (Alert)null, StatusUpdate = s});
var alerts =
dc.Alerts.Where(a => a.AccountId == accountId).Select(
a => new {Alert = a, StatusUpdate = (StatusUpdate) null});
var obj = updates.Union(alerts).Take(100);
return obj.OrderByDescending(su => su.StatusUpdate.CreateDate).ToList();
}
}
And, the calling method:
protected void LoadStatus()
{
repStatusUpdates.DataSource = _statusRepository
.GetUpdatesByAccountId<StatusUpdate>(_userSession.CurrentUser.AccountId);
repStatusUpdates.DataBind();
}
AND here are the interfaces to the repositories I am using to access my Alert and StatusUpdate tables via LINQ:
public interface IAlertRepository
{
List<Alert> GetAlertsByAccountId(Int32 accountId);
void SaveAlert(Alert alert);
void DeleteAlert(Alert alert);
}
public interface IStatusUpdateRepository
{
StatusUpdate GetStatusUpdateById(Int32 statusUpdateId);
List<StatusUpdate> GetStatusUpdatesByAccountId(Int32 accountId);
List<StatusUpdate> GetFriendStatusUpdatesByAccountId(Int32 accountId, Boolean addPassedInAccount);
void SaveStatusUpdate(StatusUpdate statusUpdate);
List<StatusUpdate> GetTopNStatusUpdatesByAccountId(Int32 accountId, Int32 number);
List<StatusUpdate> GetTopNFriendStatusUpdatesByAccountId(Int32 accountId, Int32 number, Boolean addPassedInAccount);
}
Current Problems:
1) When I compile this code, I get this strange error:
Unable to cast object of type
'System.Data.Linq.SqlClient.SqlNew' to
type
'System.Data.Linq.SqlClient.SqlValue'.
The only reading I can find on it is this link although there isn't a clear solution there (at least that I can tell). However, if the above LINQ code does not look good to you, maybe whatever you suggest will cause this error to disappear.
2) The above code is still not accounting for this line from the original SQL:
(SELECT 10 AS Expr1) AS AlertTypeId
but this is minor.
Thanks again for the help.
Try this (i converted the StatusUpdate to an alert, if this isn't acceptable, you're going to have to either convert the Alert to a StatusUpdate, or create a new class):
var alerts = (from a in dc.Alerts
where a.AccountId == accountId
select a);
var updates = (from s in dc.StatusUpdates
where s.AccountId == accountId
select s)
.OrderByDescending( x => x.CreateDate)
.Take(100)
.Select( x => new Alert
{
Message = x.Percent.ToString(),
CreateDate = x.CreateDate,
AccountId = x.AccountId,
AlertTypeId = 10 // Is this right?
}
);
var obj = alerts.Union(updates);
result = obj.ToList();
The reason I do the Select last is so that you don't have to construct a new alert for all the results your are not using.
This will give you a list of Alerts.
Using a generic in this situation is sort of hard to pull off. For instance, you can't do this:
IQueryable alerts = (from a in _alerts
where a.AccountId == accountId
select a);
Because that implicitly converts a to type T. Even if you try to limit what T implements or inherits from:
public List<T> GetSomething<T>(Int32 accountId) where T : IAlert// Interface that both StatusUpdates and IAlert implement
public List<T> GetSomething<T>(Int32 accountId) where T : Alert
public List<T> GetSomething<T>(Int32 accountId) where T : AlertBase // Base class for both Status and Alert
You'll still run into problems because there is no way to statically know exactly what type T is, so you cannot know if it can be converted from Alert and StatusUpdate.
An alternative is to explicitly use IAlert as your return type:
public List<IAlert> GetSomething(Int32 accountId)
With IAlert:
public interface IAlert
{
int AccountId { get; set; }
int AlertTypeId { get; set; }
DateTime CreateDate { get; set; }
string Message { get; set; }
}
If you have have both Alert and StatusUpdate implement IAlert, you could rewrite it as so:
IQueryable<IAlert> alerts = (from a in dc.Alerts
where a.AccountId == accountId
select a);
IQueryable<IAlert> updates = (from s in dc.StatusUpdates
where s.AccountId == accountId
select s)
.OrderByDescending( x => x.CreateDate)
.Take(100);
var obj = alerts.Union(updates);
result = obj.ToList();
This is the route I would take instead of passing in some unknown type and trying to limit what it implements or inherits, because casting to that type might still be invalid.
You can only take unions of sequences of equal types. You need to convert alerts and updates to sequences of a common type, then take the union. You can do so using anonymous types. Especially useful if the types don't have anything in common.
//this is a hack and probably not what you would want to use.
var alerts =
from a in dc.Alerts
where a.AccountId == accountId
select new { Alert = a, StatusUpdate = (StatusUpdate)null };
var updates =
from s in dc.StatusUpdates
where s.AccountId == accountId
select new { Alert = (Alert)null, StatusUpdate = s };
//both are sequences of anonymous type with properties:
// Alert (of type Alert)
// StatusUpdate (of type StatusUpdate)
var obj = alerts.Union(updates);
If you have fields in common, you'd still use anonymous types except you'd include the known fields.
var alerts =
from a in dc.Alerts
where a.AccountId == accountId
select new
{
a.Message, //assuming is a string
Status = (string)null,
a.CreateDate,
a.AccountId,
a.AlertTypeId //assuming is an int
};
var updates =
(from s in dc.StatusUpdates
where s.AccountId == accountId
select new
{
Message = (string)null,
s.Status, //assuming is a string
s.CreateDate,
s.AccountId,
AlertTypeId = 10 //this should handle the "10 AS AlertTypeId" part
}).OrderByDescending(s => s.CreateDate);
var obj = alerts.Union(updates);
The key is that both anonymous types has the same exact properties of the same exact types. Then you can take the union between them both.

Resources