Linq to entities: finding matches - c#

I have these two EF classes:
class Row
{
public long CategoryId { get; set; }
public virtual Category Category { get; set; }
public long VesselId { get; set; }
public virtual Vessel Vessel { get; set; }
public int TruckType { get; set; }
}
class RowFilter
{
public long? CategoryId { get; set; }
public virtual Category Category { get; set; }
public long? VesselId { get; set; }
public virtual Vessel Vessel { get; set; }
public int? TruckType { get; set; }
public long? PortId { get; set; }
public virtual Port Port { get; set; }
public bool IsMatch(Row row)
{
if (CategoryId == null || CategoryId == row.CategoryId) {
if (VesselId == null || VesselId == row.VesselId) {
if (TruckType == null || TruckType == row.TruckType) {
if (PortId == null || PortId == row.Vessel.PortId) {
return true;
}
}
}
}
return false;
}
}
That is:
A Filter matches a Row if IsMatch() returns true for that row.
I have a list of rows, in an IQueryable manner:
var rows = dbContext.Rows.AsQueryable().Where(...);
...and for each row, I want to select (project) the row itself and the list of filters that match this row. I can do this easily in a Linq-to-Objects way ("in memory"):
// Linq-to-objects
rows.ToList().Select(r => new
{
row = r,
filters = dbContext.RowsFilters.Where(f => f.IsMatch(r))
};
Question is... is it possible to do it with Linq-to-Entities? (sql, not "in memory")
In a static world, I would have these navigation properties:
class Row
{
...
public virtual List<RowFilter> RowFilters { get; set; }
}
class RowFilter
{
...
public virtual List<Rows> Rows { get; set; }
}
but... that means a lot of updating: when creating a new RowFilter, when creating a new Row, etc.

You can do the following steps:
Modify the IsMatch method to return a Expression<Func<Row, bool>> type and implement it like this :
public Expression<Func<Row, bool>> IsMatch()
{
Expression<Func<Row, bool>> filter = r => (CategoryId == null || CategoryId == r.CategoryId)
&& (VesselId == null || VesselId == r.VesselId)
&& (TruckType == null || TruckType == r.TruckType)
&& (PortId == null || PortId == r.PortId);
return filter;
}
Then just use it like this :
var rowFilter = new RowFilter { PortId = 1, CategoryId = 2, TruckType = 3, VesselId = 4 };
var query = context.Rows.Where(rowFilter.IsMatch());
All the linq are translated into SQL then executed on the server side. The generated SQL by EF looks like the following:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[CategoryId] AS [CategoryId],
[Extent1].[VesselId] AS [VesselId],
[Extent1].[TruckType] AS [TruckType],
[Extent1].[PortId] AS [PortId]
FROM [dbo].[Rows] AS [Extent1]
WHERE (#p__linq__0 IS NULL OR #p__linq__1 = [Extent1].[CategoryId]) AND (#p__linq__2 IS NULL OR #p__linq__3 = [Extent1].[VesselId]) AND (#p__linq__4 IS NULL OR #p__linq__5 = [Extent1].[TruckType]) AND (#p__linq__6 IS NULL OR #p__linq__7 = CAST( [Extent1].[PortId] AS bigint))

You can use the following query:
var query = from r in context.Rows
from f in context.RowFilters.Where(f =>
(f.CategoryId == null || f.CategoryId == r.CategoryId) &&
(f.VesselId == null || f.VesselId == r.VesselId) &&
(f.TruckType == null || f.TruckType == r.TruckType) &&
(f.PortId == null || f.PortId == r.Vessel.PortId))
.DefaultIfEmpty()
let x = new {r, f}
group x by x.r
into gr
select new
{
row = gr.Key,
filters = gr.Select(y => y.f).Where(yf => yf != null)
};
var result = query.ToList();
Here is an alternative syntax:
var query = context.Rows
.SelectMany(r =>
context.RowFilters.Where(f =>
(f.CategoryId == null || f.CategoryId == r.CategoryId) &&
(f.VesselId == null || f.VesselId == r.VesselId) &&
(f.TruckType == null || f.TruckType == r.TruckType) &&
(f.PortId == null || f.PortId == r.Vessel.PortId))
.DefaultIfEmpty()
.Select(f => new {r, f}))
.GroupBy(x => x.r)
.Select(x => new
{
row = x.Key,
filters = x.Select(y => y.f).Where(yf => yf != null)
});

Related

Orderby on left join null value

Following situation:
Table of users
Table of addresses
The user has a single optional reference to the address table (=left join)
The query to fetch the data is:
IQueryable<User> query =
from u in _dbContext.Users
join a in _dbContext.Address on u.AddressId equals a.Id into address
from addresses in address.DefaultIfEmpty()
select new User(u, a);
Now I want to do a sorting on the query based on the municipality of the address
query = query.OrderBy(u => u.Address.Municipality);
The problem is that the Address can be a null value (as the address is optional) and for that reason it is throwing following exception.
NullReferenceException: Object reference not set to an instance of an object.
Is there a way to order on the municipality knowing that it can be null?
Already tried following things with same outcome:
query = query.OrderBy(u => u.Address.Municipality ?? "");
query = query.OrderBy(u => u.Address == null).ThenBy(u => u.Address.Municipality);
You can use
query = query.OrderBy(u => u.Address.Municipality.HasValue);
You can write your comparer like this:
public class One
{
public int A { get; set; }
}
public class Two
{
public string S { get; set; }
}
public class T
{
public One One { get; set; }
public Two Two { get; set; }
}
public class OrderComparer : IComparer<Two>
{
public int Compare(Two x, Two y)
{
if (((x == null) || (x.S == null)) && ((y == null) || (y.S == null)))
return 0;
if ((x == null) || (x.S == null))
return -1;
if ((y == null) || (y.S == null))
return 1;
return x.S.CompareTo(y.S);
}
}
static void Main(string[] args)
{
var collection = new List<T> {
new T{ One = new One{A=1}, Two = new Two{ S="dd" } },
new T{ One = new One{A=5}, Two = null },
new T{ One = new One{A=0}, Two = new Two{ S=null } },
new T{ One = new One{A=6}, Two = new Two{ S="aa" } },
};
var comparer = new OrderComparer();
collection = collection.OrderBy(e => e.Two, comparer).ToList();
}
But in your case you have to write var collection = query.AsEnumerable().OrderBy(x=>x.Address).
Also there is other method:
var result = query.Where(x=>x.Address==null || x.Address.Municipality==null)
.Union(query.Where(x.Address!=null && x.Address.Municipality!=null).OrderBy(x=>x.Address.Municipality));
I create a simple demo and it works well when I add nullable foreign key to the two tables.
Besides, I do not understabd what is select new User(u, a); in your code.
Below is my sample code:
Models:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Address")]
public int? AddressId { get; set; }
public Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public string AddressName { get; set; }
public string Municipality { get; set; }
}
Action:
IQueryable<User> query =
from u in _context.Users
join a in _context.Address on u.AddressId equals a.Id into address
from addresses in address.DefaultIfEmpty()
select new User
{
Id = u.Id,
Name = u.Name,
AddressId = u.AddressId,
Address = addresses
};
query = query.OrderBy(u => u.Address.Municipality);

Possible to implement a conditional join clause - JOIN ON condition1 OR condition2?

Is it possible to implement a conditional join clause in Entity Framework 6? Specifically, INNER JOIN ON (boolean condition1) OR (boolean condition2).
The code below works, but calls the database twice. Is it possible to consolidate it down into one call?
There is a foreign key relationship that ties FirmFeatures.FeatureId to Nullable FirmParameters.FeatureId
var dbContext = new MyEntities();
var feature = dbContext.FirmFeatures
.Where(f => f.FeatureId == featureId)
.First();
var parameters = dbContext.FirmParameters.AsQueryable();
parameters = feature.IsDbTable
? parameters.Where(p => p.FeatureId == null)
: parameters.Where(p => p.FeatureId == featureId);
var list = parameters.ToList()
The SQL call would look something like:
SELECT feature.*, parameter.*
FROM [FirmFeature] AS feature
INNER JOIN [FirmParameter] AS parameter
ON (feature.IsDbTable = 0 AND feature.FeatureId = parameter.FeatureId) OR (feature.IsDbTable = 1 AND parameter.FeatureId IS NULL)
WHERE feature.[FeatureId] = 3
This leveraged database model first.
I'm new to the Entity Framework.
Edit2: I'm hoping to have both a features object and a parameters object loaded from the database as a result of this.
EDIT: As requested, here are the models:
{
public FirmFeature()
{ this.FirmParameters = new HashSet<FirmParameter>(); }
public byte FeatureId { get; set; }
public bool IsDbTable { get; set; }
...
public virtual ICollection<FirmParameter> FirmParameters { get; set; }
}
public partial class FirmParameter
{
public byte ParameterId { get; set; }
public Nullable<byte> FeatureId { get; set; }
...
public virtual FirmFeature FirmFeature { get; set; }
public virtual FirmParameter FirmParameter1 { get; set; }
public virtual FirmParameter FirmParameter2 { get; set; }
}
try giving this a shot:
var isDbTableQuery = dbContext.FirmFeatures.Where(f => f.FeatureId == featureId && f.IsDbTable);
var parameters = dbContext.FirmParameters.Where(p => isDbTableQuery.Any() ? p.FeatureId == null : p.FeatureId == featureId);
var list = parameters.ToList();
I cannot test it right now, but if your only problem is the two round trips you can acomplish that using two LEFT joins, and selecting the appropriate source.
Something like:
var query = from feature in dbContext.FirmFeatures
join parameter0 in dbContext.FirmParameters
on new { IsDbTable = feature.IsDbTable, FeatureId = feature.FeatureId } equals new { IsDbTable = false, FeatureId = parameter0.FeatureId ?? 0 }
into left_parameter_0
from parameter_0 in left_parameter_0.DefaultIfEmpty()
join parameter1 in dbContext.FirmParameters
on new { IsDbTable = feature.IsDbTable, FeatureId = (byte?)null } equals new { IsDbTable = true, FeatureId = parameter1.FeatureId }
into left_parameter_1
from parameter_1 in left_parameter_1.DefaultIfEmpty()
select new { Feature = feature, Parameter = parameter_0 != null ? parameter_0 : parameter_1 };
var list = query.ToList();
You can put the condition in the join statement. I'll do this in query syntax because that always reads far easier with joins:
var q = from f in dbContext.FirmFeatures
where f.FeatureId == featureId
join p in dbContext.FirmParameters on
(f.IsDbTable ? null : f.FeatureId) equals p.FeatureId
select new { p, f };
Or simply:
var q2 = from p in dbContext.FirmParameters.Include(p => p.FirmFeature)
where (p.FirmFeature.FeatureId == featureId && p.FirmFeature.IsDbTable)
|| p.Feature == null
select p;
where you use Include to get FirmParameters having their FirmFeature references loaded (if there are any).
var list = dbContext.FirmParameters
.Where(p => (p.FirmFeature.FeatureId == featureId && p.FirmFeature.IsDbTable) ?
p.FeatureId == null : p.FeatureId == featureId)
.ToList();
UPDATE
var list = dbContext.FirmParameters
.Join(dbContext.FirmFeature, p.FeatureId, f.FeatureId, (p, f) => new { Parameter = p, Feature = f})
.Where(#f => #f.Feature.FeatureId == featureId)
.Where(#p => (#p.Feature.IsDbTable ? #p.Parameter.FeatureId == null : #p.Parameter.FeatureId == featureId))
.Select(#x => new { Feature = #x.Feature, Parameter = #x.Parameter })
.DefaultIfEmpty()
.ToList();

Tunning my dash board linq query

I have the following linq query insde my asp.net mvc web application , which mainly build our dash board, by displaying count() for many entities:-
public SystemInformation GetSystemInfo(int pagesize)
{
var d = DateTime.Today;
string[] notservers = new string[] { "vmware virtual platform", "storage device", "router", "switch", "firewall" };
string[] types = new String[] { "server", "workstation" };
var tmpCustomCount = tms.CustomAssets.Sum(a => (int?)a.Quantity);
SystemInformation s = new SystemInformation()
{
AssetCount = new AssetCount() {
CustomerCount = entities.AccountDefinitions.Count(),
RackCount = tms.TMSRacks.Count(),
ServerCount = tms.TMSServers.Count(),
VirtualMachineCount = tms.TMSVirtualMachines.Count(),
StorageDeviceCount = tms.TMSStorageDevices.Count(),
FirewallCount = tms.TMSFirewalls.Count(),
SwitchCount = tms.TMSSwitches.Count(),
RouterCount = tms.TMsRouters.Count(),
DataCenterCount = tms.DataCenters.Count(),
CustomCount = tmpCustomCount == null ? 0 : tmpCustomCount.Value
//tms.CustomAssets==null? 0 : tms.CustomAssets.Sum(a => a.Quantity)
},
AdminAudit = AllIncludingAdminAudit("", auditinfo => auditinfo.SecurityTaskType, auditinfo2 => auditinfo2.AuditAction).OrderByDescending(a => a.DateTimeStart)
.Take(pagesize).ToList(),
LatestTechnology = tms.Technologies.Where(a=> !a.IsDeleted && a.IsCompleted).OrderByDescending(a => a.TechnologyID).Take(pagesize).ToList(),
IT360ServerNo = entities.Resources
.Where(a => String.IsNullOrEmpty(a.ASSETTAG) && (a.SystemInfo.ISSERVER == true ) && !(notservers.Contains(a.SystemInfo.MODEL.Trim().ToLower()))).Count(),
IT360VMNo = entities.Resources
.Where(a => String.IsNullOrEmpty(a.ASSETTAG) && (a.SystemInfo.ISSERVER == true) && a.SystemInfo.MODEL.Trim().Equals("VMware Virtual Platform", StringComparison.OrdinalIgnoreCase)).Count(),
IT360SDNo = entities.Resources
.Where(a => String.IsNullOrEmpty(a.ASSETTAG) && a.SystemInfo.MODEL.Trim().Equals("Storage Device", StringComparison.OrdinalIgnoreCase)).Count(),
IT360SwitchNo = entities.Resources
.Where(a => String.IsNullOrEmpty(a.ASSETTAG) && a.SystemInfo.MODEL.Trim().Equals("Switch", StringComparison.OrdinalIgnoreCase)).Count(),
IT360FirewallNo = entities.Resources
.Where(a => String.IsNullOrEmpty(a.ASSETTAG) && a.SystemInfo.MODEL.Trim().Equals("Firewall", StringComparison.OrdinalIgnoreCase)).Count(),
IT360RouterNo = entities.Resources
.Where(a => String.IsNullOrEmpty(a.ASSETTAG) && a.SystemInfo.MODEL.Trim().Equals("Router", StringComparison.OrdinalIgnoreCase)).Count(),
DeleteNo = tms.TechnologyAudits.Where(a => ( EntityFunctions.TruncateTime(a.DateTimeEnd) == d && a.AuditAction.Name.ToLower() == "delete" && a.TechnologyID != null)).Count(),//TechnologyId != null so that only assets that have tags will be included in the count
CreateNo = tms.TechnologyAudits.Where(a => (EntityFunctions.TruncateTime(a.DateTimeEnd) == d && a.AuditAction.Name.ToLower() == "add" && a.TechnologyID != null)).Count(),
EditNo = tms.TechnologyAudits.Where(a => (EntityFunctions.TruncateTime(a.DateTimeEnd) == d && a.AuditAction.Name.ToLower() == "Edit" && a.TechnologyID != null)).Count(),
OtherNo = tms.TechnologyAudits.Where(a => (EntityFunctions.TruncateTime(a.DateTimeEnd) == d
&&
!((a.AuditAction.Name.ToLower() == "delete" && a.TechnologyID != null)
|| (a.AuditAction.Name.ToLower() == "add" && a.TechnologyID != null)
|| (a.AuditAction.Name.ToLower() == "edit" && a.TechnologyID != null)))).Count(),
};
return s;
}
And the model class is :-
public class SystemInformation
{
public AssetCount AssetCount { get; set; }
public IPagedList<TechnologyAudit> TechnologyAudit { get; set; }
public ICollection<AdminAudit> AdminAudit { get; set; }
public ICollection<Technology> LatestTechnology { get; set; }
[Display(Name = "Server/s")]
public int IT360ServerNo { get; set; }
[Display(Name = "VM/s")]
public int IT360VMNo { get; set; }
[Display(Name = "SD/s")]
public int IT360SDNo { get; set; }
[Display(Name = "Switch/s")]
public int IT360SwitchNo { get; set; }
[Display(Name = "Firewall/s")]
public int IT360FirewallNo { get; set; }
[Display(Name = "Router/s")]
public int IT360RouterNo { get; set; }
[Display(Name = "Delete Opeartion/s")]
public int DeleteNo { get; set; }
[Display(Name = "Create Opeartion/s")]
public int CreateNo { get; set; }
[Display(Name = "Edit Opeartion/s")]
public int EditNo { get; set; }
[Display(Name = "Other Opeartion/s")]
public int OtherNo { get; set; }
public Double HourSpan { get; set; }
public int RefreshInSeconds { get; set; }
}
The above is functioning well, but the problem is that i am sending a separate query to the DB to populate each variable such as customercount, RackCount, ServerCount ,etc...
I know that having single query to build the above might not be possible, as i am retrieving count() from separate tables. But is there a way to join the Count() on the same table to a single query, i mean to use something such as GroupBy and return the count based on the where critical ?
GroupBy could be used for calculating counts of groups with regard to specified key. In your example you use a lot of additional filtering after or before equaling to a string key. You may try with the following case, but in other cases it will be hard to apply GroupBy efficiently:
var ActionCounts = tms.TechnologyAudits.Where(a =>
(EntityFunctions.TruncateTime(a.DateTimeEnd) == d && a.TechnologyID != null))
.GroupBy(a => a.AuditAction.Name.ToLower())
.Select(g => new {
Action = g.Key
ItemCount = g.Count();
}).ToLookup(a => a.Action);
How to integrate with your code:
var tmpCustomCount = tms.CustomAssets.Sum(a => (int?)a.Quantity);
[Insert here:]
SystemInformation s = new SystemInformation()
...
DeleteNo = ActionCounts["delete"] == null ? 0 : ActionCounts["delete"].Single().ItemCount;
CreateNo = ActionCounts["add"] == null ? 0 : ActionsCount["add"].Single().ItemCount;
EditNo = ActionCounts["edit"] == null ? 0 : ActionsCount["edit"].Single().ItemCount;

MSUnit: Assert.AreEqual fails trees

I have to test the equality of trees. In other other words objects which contains List<T> with childs and the childs also contains List<T> with childs and so on.
I've found that you can test List with CollectionAssert, however it does not work that well with composites.
Any suggestions? MSUnit is my test library.
Example
IReagentComposed bronzeBarParsed = (from n in composedCrafts where n.ItemId == 2841 select n).Single();
IReagentComposed bronzeBar = new Craft()
{
ItemId = 2841,
Profession = Profession.Mining,
Quantity = 0,
QuantityCrafted = 0,
Skill = 50,
Reagents = new List()
{
new Craft()
{
ItemId = 2840,
Quantity = 0,
Skill = 1,
Profession = Profession.Mining,
Reagents = new List()
{
new Reagent()
{
ItemId = 2770,
Quantity = 1
}
}
},
new Craft()
{
ItemId = 3576,
Quantity = 0,
Skill = 50,
Profession = Profession.Mining,
Reagents = new List()
{
new Reagent()
{
ItemId = 2771,
Quantity = 1
}
}
}
}
};
Assert.AreEqual(bronzeBar, bronzeBarParsed);
Craft and Reagent
public class Craft : IReagentComposed
{
public int QuantityCrafted { get; set; }
public int Quantity { get; set;}
public int ItemId { get; set; }
public int Skill { get; set; }
public Profession Profession { get; set; }
public IEnumerable Reagents { get; set; }
public override bool Equals(object other)
{
if (other == null || GetType() != other.GetType()) return false;
IReagentComposed o = other as IReagentComposed;
return o != null && this.Quantity == o.Quantity &&
this.ItemId == o.ItemId &&
this.Profession == o.Profession &&
this.Reagents == o.Reagents && //also tried Equals
this.Skill == o.Skill;
}
public override int GetHashCode()
{
return 0;
}
}
public class Reagent : IReagent
{
public int ItemId { get; set; }
public int Quantity { get; set; }
public override bool Equals(object other)
{
if (other == null || GetType() != other.GetType()) return false;
IReagent o = other as IReagent;
return o != null && o.ItemId == this.ItemId && o.Quantity == this.Quantity;
}
public override int GetHashCode()
{
return 0;
}
}
return o != null && this.Quantity == o.Quantity &&
this.ItemId == o.ItemId &&
this.Profession == o.Profession &&
this.Reagents == o.Reagents && //also tried Equals
this.Skill == o.Skill;
The Reagents are unlikely to match, they are distinct List objects. List<> doesn't override Equals although it isn't that clear what actual type you use. Write a little helper function that takes two lists of reagents and checks for equality. You'd typically start at comparing List<>.Count and then work down the elements one by one.
Added a extension method for IEnumberable<T>:
public static class IEnumberableExtensions
{
public static bool AreEnumerablesEqual<T>(this IEnumerable<T> x, IEnumerable<T> y)
{
if (x.Count() != y.Count()) return false;
bool equals = false;
foreach (var a in x)
{
foreach (var b in y)
{
if (a.Equals(b))
{
equals = true;
break;
}
}
if (!equals)
{
return false;
}
equals = false;
}
return true;
}
}

LINQ- Max in where condition

I have a class TaskWeekUI with this definition:
public class TaskWeekUI {
public Guid TaskWeekId { get; set; }
public Guid TaskId { get; set; }
public Guid WeekId { get; set; }
public DateTime EndDate { get; set; }
public string PersianEndDate { get; set; }
public double PlanProgress { get; set; }
public double ActualProgress { get; set; } }
and I wrote this query :
TaskWeekUI ti = tis.First( t => t.PlanProgress > 0 && t.EndDate == tis.Where(p => p.PlanProgress != null && p.PlanProgress > 0).Max(w => w.EndDate));
Is this query is true? Can I write my query better than this?
I think you want the one whose PlanProgress > 0 has a most recent EndDate.
TaskWeekUI ti = tis.Where(t => t.PlanProgress > 0)
.OrderByDescending(t => t.EndDate)
.FirstOrDefault();
This query seems to be correct from point of view of result obtained.
But in your inner query tis.Where(p => p.PlanProgress != null && p.PlanProgress > 0).Max(w => w.EndDate) is computed for each element in collection with t.PlanProgress > 0
So its a better way to obtain Max value outside of a query as follows:
var max = tis.Where(p => p.PlanProgress != null && p.PlanProgress > 0).Max(w => w.EndDate);
tis.First( t => t.PlanProgress > 0 && t.EndDate == max);
Going further p.PlanProgress != null is allways true since p.PlanProgress is not of Nullable type. So our code becomes like this:
var max = tis.Where(p => p.PlanProgress > 0).Max(w => w.EndDate);
tis.First( t => t.PlanProgress > 0 && t.EndDate == max);
Or you can change a definition of your class and make p.PlanProgress of Nullable type:
public class TaskWeekUI {
public Guid TaskWeekId { get; set; }
public Guid TaskId { get; set; }
public Guid WeekId { get; set; }
public DateTime EndDate { get; set; }
public string PersianEndDate { get; set; }
public double? PlanProgress { get; set; }
public double ActualProgress { get; set; }
}
var max = tis.Where(p => p.PlanProgress.HasValue && p.PlanProgress.Value > 0).Max(w => w.EndDate);
tis.First( t => t.PlanProgress.HasValue && t.PlanProgress.Value > 0 && t.EndDate == max);
var max_Query =
(from s in db.Songs
join bk in db.Albums on s.BookId equals addAlbumDetailsViewModel.BookId
select s.SongId).Max();
max_Query++;
You don't need compare PlanProgress with null because double is struct type, it can't be null.
If you want TaskWeekUI with Max EndDate and positive PlanProgress You can try this code:
TaskWeekUI ti = tis.Where(t => t.PlanProgress > 0).Max(w => w.EndDate);

Categories