How do I add a where filter using the original Linq-to-SQL object in the following scenario - linq

I am performing a select query using the following Linq expression:
Table<Tbl_Movement> movements = context.Tbl_Movement;
var query = from m in movements
select new MovementSummary
{
Id = m.DocketId,
Created = m.DateTimeStamp,
CreatedBy = m.Tbl_User.FullName,
DocketNumber = m.DocketNumber,
DocketTypeDescription = m.Ref_DocketType.DocketType,
DocketTypeId = m.DocketTypeId,
Site = new Site()
{
Id = m.Tbl_Site.SiteId,
FirstLine = m.Tbl_Site.FirstLine,
Postcode = m.Tbl_Site.Postcode,
SiteName = m.Tbl_Site.SiteName,
TownCity = m.Tbl_Site.TownCity,
Brewery = new Brewery()
{
Id = m.Tbl_Site.Ref_Brewery.BreweryId,
BreweryName = m.Tbl_Site.Ref_Brewery.BreweryName
},
Region = new Region()
{
Description = m.Tbl_Site.Ref_Region.Description,
Id = m.Tbl_Site.Ref_Region.RegionId
}
}
};
I am also passing in an IFilter class into the method where this select is performed.
public interface IJobFilter
{
int? PersonId { get; set; }
int? RegionId { get; set; }
int? SiteId { get; set; }
int? AssetId { get; set; }
}
How do I add these where parameters into my SQL expression? Preferably I'd like this done in another method as the filtering will be re-used across multiple repositories.
Unfortunately when I do query.Where it has become an IQueryable<MovementSummary>. I'm assuming it has become this as I'm returning an IEnumerable<MovementSummary>. I've only just started learning LINQ, so be gentle.
Answer:
private IQueryable<Tbl_Docket> BuildQuery(IQueryable<Tbl_Docket> movements, IMovementFilter filter)
{
if (filter != null)
{
if (filter.PersonId.HasValue) movements = movements.Where(m => m.UserId == filter.PersonId);
if (filter.SiteId.HasValue) ...
}
return movements;
}
Which is called like follows:
var query = from m in this.BuildQuery(movements, filter)
select new... {}

You have to call the where statement before you fire your select statement, e.g.:
IQueryable<Tbl_Movement> movements = context.Tbl_Movement;
if (filter != null)
{
if (filter.PersonId != null) movements = movements.Where(m => m....PersonId == filter.PersonId);
if (filter.RegionId != null) movements = movements.Where(m => m....RegionId == filter.RegionId);
if (filter.SiteId != null) movements = movements.Where(m => m...SiteId == filter.SiteId);
if (filter.AssetId != null) movements = movements.Where(m => m...AssetId == filter.AssetId);
}
var query = m from movements...
As opposed to using this IFilter class, you might want to consider a Fluent Pipe-based Repository structure, e.g.:
var movements = new MovementsPipe()
.FindSiteId(1)
.FindAssetIds(1, 2, 3)
.FindRegionId(m => m > 10)
.ToMovementSummaryList();
Hope this helps. Let me know if you have any questions.

Related

List Distinct is not working in LINQ instead of GroupBy

Thanks in advance. I can get required output when using var but i want to get required output by using Distinct in List<>.
InventoryDetails.cs
public class InventoryDetails
{
public int? PersonalInventoryGroupId { get; set; }
public int? PersonalInventoryBinId { get; set; }
}
InventoryController.cs
[HttpGet("GetInventory")]
public IActionResult GetInventory(int id)
{
//Below code will return distinct record
var inventory = (from i in _context.TempTbl
where i.TempId == id
select new
{
PersonalInventoryBinId = i.PersonalInventoryBinId,
PersonalInventoryGroupId = i.PersonalInventoryGroupId,
}).ToList().Distinct().ToList();
//Below code is not doing distinct
List<InventoryDetails> inventory = (from i in _context.TempTbl
where i.TempId == id
select new InventoryDetails
{
PersonalInventoryBinId = i.PersonalInventoryBinId,
PersonalInventoryGroupId = i.PersonalInventoryGroupId,
}).ToList().Distinct().ToList();
}
If i use var as return type, then i am able to get distinct records. Could some one assist it.
Please try like this it may help.
IList<InventoryDetails> inventory = _context.InventoryDetails.Where(x=>x.TempId == id).GroupBy(p => new {p.PersonalInventoryGroupId, p.PersonalInventoryBinId } )
.Select(g => g.First())
.ToList();
You need to override Equals and GetHashCode.
First, let's see the AnonymousType vs InventoryDetails
var AnonymousTypeObj1 = new { PersonalInventoryGroupId = 1, PersonalInventoryBinId = 1 };
var AnonymousTypeObj2 = new { PersonalInventoryGroupId = 1, PersonalInventoryBinId = 1 };
Console.WriteLine(AnonymousTypeObj1.Equals(AnonymousTypeObj2)); // True
var InventoryDetailsObj1 = new InventoryDetails { PersonalInventoryBinId = 1, PersonalInventoryGroupId = 1 };
var InvertoryDetailsObj2 = new InventoryDetails { PersonalInventoryBinId = 1, PersonalInventoryGroupId = 1 };
Console.WriteLine(InventoryDetailsObj1.Equals(InvertoryDetailsObj2)); // False
You can see the Equals behave differently which make Distinct behave differently. The problem is not var you mentioned in your question but AnonoymizeType
To make Distinct works as you expect, you need to override Equals and GetHashCode
public class InventoryDetails
{
public int? PersonalInventoryGroupId { get; set; }
public int? PersonalInventoryBinId { get; set; }
public override bool Equals(object obj)
{
if (obj == null) return false;
if (obj is InventoryDetails)
{
if (PersonalInventoryGroupId == (obj as InventoryDetails).PersonalInventoryGroupId
&& PersonalInventoryBinId == (obj as InventoryDetails).PersonalInventoryBinId)
return true;
}
return false;
}
public override int GetHashCode()
{
int hash = 17;
hash = hash * 23 + PersonalInventoryBinId.GetHashCode();
hash = hash * 23 + PersonalInventoryGroupId.GetHashCode();
return hash;
}
}
Another approach would be
List<InventoryDetails> inventory = (from i in TempTbl
where i.TempId == id
select new InventoryDetails
{
PersonalInventoryBinId = i.PersonalInventoryBinId,
PersonalInventoryGroupId = i.PersonalInventoryGroupId,
}).AsQueryable().ToList().Distinct(new customComparer()).ToList();
public class customComparer:IEqualityComparer<InventoryDetails>
{
public bool Equals(InventoryDetails x, InventoryDetails y)
{
if (x.TempId == y.TempId && x.PersonalInventoryBinId == y.PersonalInventoryBinId
&& x.PersonalInventoryGroupId == y.PersonalInventoryGroupId)
{
return true;
}
return false;
}
public int GetHashCode(InventoryDetails obj)
{
return string.Concat(obj.PersonalInventoryBinId.ToString(),
obj.PersonalInventoryGroupId.ToString(),
obj.TempId.ToString()).GetHashCode();
}
}
As said in a comment by Ivan, you make your life difficult by calling ToList before Distinct. This prevents the SQL provider from incorporating the Distinct call into the generated SQL statement. But that leaves the question: what causes the difference?
The first query generates anonymous type instances. As per the C# specification, by default anonymous types (in C#) are equal when their properties and property values are equal (structural equality). Conversely, by default, reference types (like InventoryDetails) are equal when their reference (say memory address) is equal (reference equality or identity). They can be made equal by overriding their Equals and GetHashcode methods, as some people suggested to do.
But that's not necessary if you remove the first ToList():
var inventory = (from i in _context.TempTbl
where i.TempId == id
select new InventoryDetails
{
PersonalInventoryBinId = i.PersonalInventoryBinId,
PersonalInventoryGroupId = i.PersonalInventoryGroupId,
}).Distinct().ToList();
Now the whole statement until ToList() is an IQueryable that can be translated into SQL. The SQL is executed and the database returns a distinct result set of raw records from which EF materializes InventoryDetails objects. The C# runtime code was even never aware of duplicates!

LINQ Query to join three tables

I need a help in LINQ Query for the below.
public interface IBrand
{
int BrandId { get; set; }
IEnumerable<IBuyingAgency> BuyingAgencies { get; set; }
}
public interface IBuyingAgency
{
int BuyingAgencyId { get; set; }
}
public interface IClientGroup
{
IBuyingAgency BuyingAgency { get; set; }
int ClientGroupId { get; set; }
}
1). var brands = LoggedInUserHelper.GetUser().GetBrands(roles); // returns IEnumerable<Tuple<IBrand, string>>
2). var buyingAgencies = LoggedInUserHelper.GetUser().GetBuyingAgencies(roles); //IEnumerable<IBuyingAgency>
3). var clientGroups = LoggedInUserHelper.GetUser().GetClientGroups(roles); //IEnumerable<IClientGroup>
function IEnumerable<IClientGroup> GetClientGroups( List<int> BrandIds)
{
var brands = LoggedInUserHelper.GetUser().GetBrands(roles); // returns IEnumerable<Tuple<IBrand, string>>
var buyingAgencies = LoggedInUserHelper.GetUser().GetBuyingAgencies(roles); //IEnumerable<IBuyingAgency>
var clientGroups = LoggedInUserHelper.GetUser().GetClientGroups(roles); //IEnumerable<IClientGroup>
var lstBrandagencies = brands.Where(brand => BrandIds.Contains(brand.Item1.BrandId) && brand.Item1.BuyingAgencies.Any( ba => buyingAgencies.Contains(ba.BuyingAgencyId))).SelectMany(brand => brand.Item1.BuyingAgencies);
var buyingAgencyIDs = lstBrandagencies.Select(b => b.BuyingAgencyId);
clientGroups = clientGroups.Where(cg => buyingAgencyIDs.Contains(cg.BuyingAgency.BuyingAgencyId));
return Mapper.Map<IEnumerable<IClientGroup>>(clientGroups.ToList());
}
I wrote the above function but not working, it gets all the clientgroups instead of filtering
I wants to write a query to get all ClientGroups whch satisfies the below condition
1. retrieve the brand from brands ( above ) that matches the list of brandId's passing in as parameter
2. Than get all the buyingAgencies under brands (1) above which matches with the id's of (2) above
3. Finally get all clientgroups which matches with the buyingAgency retrieving in step (2)
Please can you help.
you are not filtering from your source 2) in this line
var buyingAgencyIDs = lstBrandagencies.Select(b => b.BuyingAgencyId);
just projecting from the previous query.
If I understood correctly you want to do this.
var lstBrandagencies = (from a in brands
where BrandIds.Contains(a.Item1.BrandId )
select a).SelectMany (b => b.Item1.BuyingAgencies )
.Select (b => b.BuyingAgencyId );
var buyingAgencyIDs = from a in buyingAgencies
where lstBrandagencies.Contains(a.BuyingAgencyId )
select a.BuyingAgencyId;
var clientGroupsResult = clientGroups.Where(cg => buyingAgencyIDs.Contains(cg.BuyingAgency.BuyingAgencyId));

how to practically assign repeating objects from groups

I am having a difficult time finding a proper Linq query to utilize the group output.
I want to populate an existing students List where Student class has 2 properties ID and and int[] Repeats array (can be a list too) to keep how many times they took any of the 4 lectures (L101,L201,L202,L203). So if student takes L101 twice, L202 and L203 once, and but didn't take L201 this should be {2,0,1,1,}
class Student{
public string ID{get;set;}
public int[] Repeats{get;set;} //int[0]->L101, int[1]->L201...
}
In my main class I do this basic operation for this task:
foreach (var student in students)
{
var countL101 = from s in rawData
where student.Id==s.Id & s.Lecture =="L101"
select; //do for each lecture
student.Repeats = new int[4];
student.Repeats[0] = countL101.Count(); //do for each lecture
}
This works; but I wonder how do you make it practically using Linq in case where there are 100s of lectures?
I am using Lamba Expressions rather than query syntax. Then assuming rawData is IEnumerable<T> where T looks something like...
class DataRow
{
/// <summary>
/// Id of Student taking lecture
/// </summary>
public string Id { get; set; }
public string Lecture { get; set;}
}
Then you could do something like...
var lectures = rawData.Select(x => x.Lecture).Distinct().ToList();
int i = 0;
lectures.ForEach(l =>
{
students.ForEach(s =>
{
if (s.Repeats == null)
s.Repeats = new int[lectures.Count];
s.Repeats[i] = rawData.Count(x => x.Id == s.Id && x.Lecture == l);
});
i++;
});
Now if Repeats could just be of type IList<int> instead of int[] then...
var lectures = rawData.Select(x => x.Lecture).Distinct().ToList();
lectures.ForEach(l =>
{
students.ForEach(s =>
{
if (s.Repeats == null)
s.Repeats = new List<int>();
s.Repeats.Add(rawData.Count(x => x.Id == s.Id && x.Lecture == l));
});
});
Things are further simplified if Repeats could just be instantiated to a new List<int> in the Student constructor...
class Student
{
public Student()
{
Repeats = new List<int>();
}
public string Id { get; set; }
public IList<int> Repeats { get; private set; }
}
Then you can do it in one line...
rawData.Select(x => x.Lecture).Distinct().ToList()
.ForEach(l =>
{
students.ForEach(s =>
{
s.Repeats.Add(rawData.Count(x => x.Id == s.Id && x.Lecture == l));
});
});

Lambda statement to project results based on if

I have a user who is part of a company, this company can have multiple offices, so the user can be part of head/main office or part of another office, I am projecting from a lamdba expression but I cannot figure out how to do: if user is not headoffice throw out the office address.
The code below shows user, joined onto userhistory (2 table join - which is necessary so I can throw out some info that is held on that table related to this user), here is what I have done so far:
[HttpPost]
[AjaxOnly]
public PartialViewResult GetUsers(string term)
{
var _sf = _repo.Single<Company>(x => x.Type == x.IsActive &&
(x.Identifier.Contains(term) || x.Name.Contains(term)));
//get company with addresses...
var users = _repo.All<User>().Where(x => x.CompanyID == _sf.CompanyID);
var offices = _repo.All<Office>().Where(x => x.CompanyID == _sf.CompanyID);
var _users = _repo.All<UserHistory>()
.Join(users, x => x.UserID, y => y.UserID,
(s, u) => new
{
_s = s,
_user = u
}).Select(x => new QuoteData
{
Login = x._user.Login,
Name = string.Format("{0} {1}", x._user.FirstName, x._user.LastName),
Tel = x._s.Mobile,
//let me know where user is based, if head office get me the address too...
IsBasedInHeadOffice = x._user.IsBasedInHeadOffice
//here: if !IsBasedInHeadOffice => GetMeAddress
});
return PartialView("QuoteUsersUC", _users);
}
public class QuoteData
{
public string Login { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Tel { get; set; }
public bool IsBasedInHeadOffice { get; set; }
}
Could I have written this even better/ simpler?
You can do it like this:
.Select(x =>
{
var result = new QuoteData
{
Login = x._user.Login,
Name = string.Format("{0} {1}", x._user.FirstName,
x._user.LastName),
Tel = x._surveyor.Mobile,
IsBasedInHeadOffice = x._user.IsBasedInHeadOffice
};
if(!result.IsBasedInHeadOffice)
result.Address = GetMeAddress();
return result;
});
UPDATE:
Using LINQ2SQL or EF, this should be a lot simpler because of the so called navigation properties. Basically, they remove the need for manual joins in your C# code, if your database is properly set up with foreign keys.
For example, if your table USER_HISTORY would have a foreign key constraint on the column user_id to the table USER, your class User would have a property UserHistories of type IEnumerable<UserHistory> that contains all associated user histories and the class UserHistory would have a property User. The same is true for all other associations: User <-> Company and Company <-> Office
Using this, your code could easily be rewritten to this:
public PartialViewResult GetUsers(string term)
{
var sf = _repo.Single<Company>(x => x.Type == x.IsActive &&
(x.Identifier.Contains(term) || x.Name.Contains(term)));
var users =
sf.Users
.SelectMany(x => x.UserHistories
.Select(y =>
new QuoteData
{
Login = x.Login,
Name = string.Format("{0} {1}",
x.FirstName,
x.LastName),
Tel = y.Mobile,
IsBasedInHeadOffice = x.IsBasedInHeadOffice
Address = x.IsBasedInHeadOffice ?
sf.Office.Address :
string.Empty
}));
return PartialView("QuoteUsersUC", _users);
}

LINQ GroupBy month

I am having trouble getting an IQueryable list of a (subsonic) object grouped by Month and Year.
Basic view of the object...
public partial class DatabaseObject
{
[SubSonicPrimaryKey]
public int objectID { get; set; }
public string Description { get; set; }
public decimal Value { get; set; }
public string Category { get; set; }
public DateTime DateOccurred { get; set; }
}
Method to get IQueryable in my Database repository...
public IQueryable GetData(string DataType)
{
return (from t in db.All<DatabaseObject>()
orderby t.DateOccurred descending
select t)
.Where(e => e.Category == DataType);
}
My question is, how can I return the dates grouped by Month? I have tried the below, but this results in compiler warnings regarding anonymous types...
public IQueryable GetData(string DataType)
{
var datalist = (from t in db.All<FinancialTransaction>().Where(e => e.Category == DataType);
let m = new
{
month = t.DateOccurred.Month,
year = t.DateOccurred.Year
}
group t by m into l select new
{
Description = string.Format("{0}/{1}", l.Key.month, l.Key.year),
Value = l.Sum(v => v.Value), // Sum(v => v.Value),
Category = "Grouped"
DateOccurred = l.Last(v => v.DateOccurred)
}
return datalist;
}
Any ideas?
Try this couple issues i found, but you basically need to select a Database object versus anonymous type?
IQueryable<DatabaseObject> datalist = (
from t in db.All<FinancialTransaction>().Where(e => e.Category == DataType)
let m = new
{
month = t.DateOccurred.Month,
year = t.DateOccurred.Year
}
group t by m into l
select new DatabaseObject()
{
Description = string.Format("{0}/{1}", l.Key.month, l.Key.year),
Value = l.Sum(v => v.Value), //Sum(v => v.Value),
Category = "Grouped",
DateOccurred = l.Max(v => v.DateOccurred)
}).AsQueryable();
Let me know if my solution is now what you want. I also noticed you were using Last? The extension you were using I do not have so I replaced it with Max. I don't have subsonic installed so it might come with the libraries.
Any way don't combine LINQ in query syntax and LINQ in extension methods syntax. Use next:
from t in db.All<DatabaseObject>()
where e.Category equals DataType
orderby t.DateOccurred descending
select t;
The issue is apparantly to do with the way Subsonic interprests certain linq statements and is a known bug.
IEnumerable<DatabaseObject> datalist = (
from t in db.All<FinancialTransaction>().Where(e => e.Category == DataType).ToList()
let m = new
{
month = t.DateOccurred.Month,
year = t.DateOccurred.Year
}
group t by m into l
select new DatabaseObject()
{
Description = string.Format("{0}/{1}", l.Key.month, l.Key.year),
Value = l.Sum(v => v.Value), //Sum(v => v.Value),
Category = "Grouped",
DateOccurred = l.Max(v => v.DateOccurred)
}).AsQueryable();
I have fixed this by declaring an list of type IEnumerable and using ToList() to cast the database interaction, finally the query is then recast AsQueryable()

Resources