public class ConsumableThreshold
{
public int ThresholdType { get; set; }
public int ManufacturerID { get; set; }
public int ModelID { get; set; }
public int ConsumableVariantID { get; set; }
}
I'm attempting to check two lists of objects for shared properties.
I will need to check various other properties depending on the results of previous matches.
For example if the ThresholdType matches, I then need to check a second property and if that matches I need to check the ModelID.
I have this query, which effectively does what I want but there are problems with it mainly that further down I drill the more the readability is going to be reduced.
var query= existingThresholds.Where(
e => defaultThresholds.Any(
d => d.ThresholdType == e.ThresholdType)).Where(
e => defaultThresholds.Any(
d => d.ManufacturerID == e.ManufacturerID)).ToList();
I wanted to do this using join but it does not support the && operator.
var query2 = from e in existingThresholds
join d in defaultThresholdson
e.ThresholdType equals d.ThresholdType &&
e.ManufacturerID equals d.ManufacturerID
select e;
Is there a way to write this as a query without chaining .Where() conditions?
Sure - you're just trying to join on a compound key, which is usually accomplished with an anonymous type:
var query2 = from e in existingThresholds
join d in defaultThresholdson
on new { e.ThresholdType, e.ManufacturerID } equals
new { d.ThresholdType, d.ManufacturerID }
select e;
(It's slightly odd to ignore one half of the join later on, admittedly...)
Is there a way to write this as a query without chaining .Where() conditions?
Yes, use an anonymous type, which has a built-in equality check that compares the values of all properties by name:
var query2 = from e in existingThresholds
join d in defaultThresholds
on new { e.ThresholdType , e.ManufacturerID }
equals new { d.ThresholdType , d.ManufacturerID }
select e;
Related
Here is sample code to reproduce the exception:
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore;
namespace Demo
{
[Keyless]
public class Contact
{
public string Name { get; set; } = default!;
public string? Address { get; set; } = default!;
public int? CCode { get; set; } = 0;
public int OtherValue { get; set; } = 0;
}
public class Foo
{
public static void Main()
{
List<Contact> raw = new();
raw.Add(new Contact { CCode = 1, Name = "Foo", Address = "Bar" });
raw.Add(new Contact { CCode = 2, Name = "Foo", Address = "Bar" });
ProcessRawResults(raw);
}
public static void ProcessRawResults(List<Contact> raw)
{
var q = from i in raw
group i by new { i.CCode, i.Name, i.Address } into g
orderby g.Key
select g;
foreach (var group in q)
{
}
}
}
}
When executing this program, an exception is thrown when execution reaches foreach (var group in q):
System.InvalidOperationException: 'Failed to compare two elements in the array.'
Inner Exception
ArgumentException: At least one object must implement IComparable
I have looked at other SO questions about this error message occurring when trying to Sort a List; but in this code I'm not sure which operation needs the comparator. It seems like the orderby g.Key operation might need to compare the anonymous class in the group, but then shouldn't the anon class have a default comparator? Or if it doesn't, I'm not sure where to put the code to implement this.
Confusingly, if I take i.CCode out of the group i by new line, then the exception doesn't happen any more.
Background: My real project is a Blazor app using EFCore 6 , and am receiving a List<Contact> from a Stored Procedure result, so it has to be [Keyless]. I have to work with the existing Stored Procedure unmodified, so am performing a transformation of the result in my code. I hope to collapse the set of results so that all entries with the same (CCode, Name, Address) result in a single row, and I'll concatenate the OtherValue into a list within that single row.
I guess it's because int? is actually Nullable<int> and Nullable<T> doesn't implement IComparable. I just tested your code but changed the grouping to this:
group i by new { CCode = i.CCode.HasValue ? i.CCode.Value : (int?)null, i.Name, i.Address } into g
and it seemed to work. It didn't throw that exception, at least.
Anonymous type do not have comparator, specify oder by properties:
var q = from i in raw
group i by new { i.CCode, i.Name, i.Address } into g
orderby g.Key.CCode, g.Key.Name, g.Key.Address
select g;
I'm trying to use OrderBy for a nested property but I can't get it to work.
Models:
public class TPRenewalCycle
{
public virtual ICollection<TPCaseEvent> CaseEvents { get; set; }
}
public class TPCaseEvent
{
public DateTime? DueDate { get; set; }
}
Method:
List<TPRenewalCycle> cycles = renewalCycles
var nextRenewalCycle = cycles.OrderBy(cycle => cycle.CaseEvents.OrderBy(caseEvent => caseEvent.DueDate)).FirstOrDefault();
This gives me the runtime error:
At least one object must implement IComparable.
Is this due to the nullable DateTime or CaseEvents? How can I solve this?
In T-SQL I can do this:
SELECT CE.DueDate
FROM TPRenewalCycles RC
INNER JOIN TPCaseEvents CE on (CE.BusinessSystemId = RC.BusinessSystemId and CE.CaseId = RC.CaseId and CE.Action = RC.Action and CE.Cycle = RC.Cycle)
Order by CE.DueDate
Since OrderBy expression needs to supply a value to be used as the comparison key for the entire record, you need to select the earliest due date in the list:
var nextRenewalCycle = cycles
.OrderBy(cycle => cycle.CaseEvents.Select(caseEvent => caseEvent.DueDate).Min())
.FirstOrDefault();
If you are looking for the earliest date, as in your SQL query, you could use SelectMany instead:
var nextRenewalCycle = cycles
.SelectMany(cycle => cycle.CaseEvents)
.Select(caseEvent => caseEvent.DueDate)
.Min();
I have method which has LINQ query and query return columns from multiple tables.
How can I return that LINQ results object and catch it in caller method iterate results and assign to model class?
public ??? GetLocation(string CustomerNum)
{
if (!string.IsNullOrEmpty(CustomerNum))
{
var results = from ca in _context.CUS_ADDRESS
join cad in _context.CUS_ADDRESS_DETAIL on ca.CUS_ADDRESS_ID equals cad.CUS_ADDRESS_ID
where (cad.PRIORITY_SEQ == 0) && (ca.MASTER_CUSTOMER_ID == CustomerNum)
select new
{
CustomerNumber = ca.MASTER_CUSTOMER_ID,
ca.ADDRESS_1,
ca.ADDRESS_2,
ca.ADDRESS_3,
ca.ADDRESS_4,
ca.CITY,
ca.STATE,
ca.COUNTRY_DESCR,
cad.ADDRESS_TYPE_CODE,
cad.ADDRESS_STATUS_CODE
};
return results;
}
else
{
return null;
}
}
Caller method
var results = Data.GetLocation(CustomerNum)
if (results.Any())
{
var location = results.FirstOrDefault();
.....
.....
}
What will be the GetLocation return type?
Depending on how you are actually using the results, you could return an IQueryable instead of IQueryable<T>.
I've used this in some situations (using IEnumerable), like WebForms, that have dynamic binding (either through Eval or by using a BoundField for instance.
You are creating an anonymous object with select new, you can't return a collection of anonymous object from your function, instead you have to create a class which would have all the properties from your select statement and then return IQueryable<YourClass>
class YourClass
{
public int CustomerNumber { get; set; }
public string ADDRESS_1 { get; set; }
//..............
}
and then :
var results = from ca in _context.CUS_ADDRESS
join cad in _context.CUS_ADDRESS_DETAIL on ca.CUS_ADDRESS_ID equals cad.CUS_ADDRESS_ID
where (cad.PRIORITY_SEQ == 0) && (ca.MASTER_CUSTOMER_ID == CustomerNum)
select new YourClass
{
CustomerNumber = ca.MASTER_CUSTOMER_ID,
ADDRESS_1 = ca.ADDRESS_1,
//...............
And modify your function return type as:
public IQueryable<YourClass> GetLocation(string CustomerNum)
You can look at this question for returning IQueryable or Not
If you didn't feel like creating a class you could use Tuples:
public IEnumerable<Tuple<int, string, string>> GetCustomer(int custId) {
return from p in customers
where p.Id == custId
select new Tuple<int, string, string>(
p.Id,
p.FirstName,
p.LastName
);
}
Though this means that you can't name their fields since you access the data like this:
var customer = GetCustomer(1);
var custId = customer.Item1;
var custFirstName = customer.Item2;
var custLastName = customer.Item3;
Create a custom helper class having all columns as properties. Say its MyClass. Fill this as below. I know this not exactly what you want but will help you to get what you want.
var o= (from c in context.table1
from d in context.table2
where c.key=d.key
select new MyClass
{
Property1=c.abc,
Property2=d.xyz
}).SingleOrDefault();
Or write your joins and where in such a way that it will give you only single row fron db.
In the function you are creating an anonymous object and hence cannot be used in caller without some methods of reflection. But it will be much easier to return an object like
public class CustomerLocation
{
public string CustomerNumber {get; set;}
// and so on
}
which will can be placed in a common layer and accessed by both caller and sender and use properties explicitly.
For this your function is better be
public IQueryable<CustomerLocation> GetLocation(string CustomerNum)
{
// your code here
}
With the following code;
using (var context = new FINSAT613Entities())
{
gridControl1.ForceInitialize();
DateTime endtime= new DateTime(2013, 03, 29, 15, 49, 54);
Text = "endtime:"+endtime.ToShortDateString();
var query =
from A in context.A
join B in context.B on A.ID equals B.ID
join C in context.C on A.ID2 equals C.ID2
where A.endtime> endtime && A.Chk.StartsWith("320")
select new
{
A.ID,B.FOO,C.BAR etc...
};
BindingSource.DataSource = query;
gridControl1.DataSource = BindingSource;
}
How can i add computed columns to it?(multiples a.bar with b.foo for example)
Tried using partial class but no luck with it.
public partial class A
{
public decimal Calculated
{
get { return 15; }
}
}
The exact error i get is :
{"The specified type member 'Computed' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported."}
You can create class, wich has got all the fields you need: id, foo, bar, etc. and add to it Calculated field, then just modify your query:
var query =
from A in context.A
join B in context.B on A.ID equals B.ID
join C in context.C on A.ID2 equals C.ID2
where A.endtime> endtime && A.Chk.StartsWith("320")
select new YourNewClass
{
Foo = A.foo,
Bar = B.bar,
... ,
Calculated = A.foo * B.bar
};
EDITED: if you have got a lot of fields, you can use automapper
Here is an example. Say, you have got class User from your DB and you have got class UserModel, in which you added a field FullName which constist from fields Name and Surname. So you want to create an object UserModel from object User but not to copy all the fields exclicitly. That's how you can do it:
class Program
{
static void Main(string[] args)
{
User u = new User { Name = "My", Surname = "Name" };
Mapper.CreateMap<User, UserModel>().ForMember(dest => dest.FullName, o => o.MapFrom(src => string.Format("{0} {1}", src.Name, src.Surname)));
UserModel um = Mapper.Map<User, UserModel>(u);
Console.WriteLine(um.FullName);
}
}
public class User
{
public string Name { get; set; }
public string Surname { get; set; }
}
public class UserModel
{
public string Name { get; set; }
public string Surname { get; set; }
public string FullName { get; set; }
}
Did you solve this?
I've been looking for a neater way myself,
I don't like using ViewModels when all I want to add to an existing model is only one or two pieces of additional info.
What I tend to do is create have a non mapped field in my model, get all the info I need from the LINQ into a temporary model and then perform any required remapping in another loop before I send it back to the client.
So for example;
public partial class A
{
[NotMapped]
public decimal Calculated {get;set;}
}
then in my Linq Query
var query =
from A in context.A
join B in context.B on A.ID equals B.ID
join C in context.C on A.ID2 equals C.ID2
where A.endtime> endtime && A.Chk.StartsWith("320")
select new
{
A = A,
B = B,
C = C
};
foreach(item i in query)
{
A.Calculated = A.Foo * B.Bar;
}
return query
OK, it means another loop, but at least it's only one DB call.
Another option would be to do it all in T-SQL and issues the T-SQL directly (or via a stored proc)
this link explains how - as an added bonus this method is much faster with more complex queries, but it always feels like a bit of a hack to me.
http://weblogs.asp.net/scottgu/archive/2007/08/16/linq-to-sql-part-6-retrieving-data-using-stored-procedures.aspx
I accomplished this by putting my data into a List first. This is probably terrible (especially if you have a large amount of results) but it does work and is easy to understand.
There's a loop involved going through the whole list. I'm not sure if other solutions require this but I also am currently having a hard time understanding them, so I need something that works for what I was trying to do, which was to populate a DropDownList.
Again I really don't think this solution is ideal:
Let's say I have an entity YourEntities with a table Registration
using (var context = new YourEntities())
{
var query = from x in context.Registrations
select x; //Just an example, selecting everything.
List<Registration> MyList = query.ToList();
foreach (Registration reg in MyList) //Can also be "var reg in MyList"
{
MyDropDownList.Items.Add(new ListItem(reg.LastName + ", " + reg.FirstName, reg.ID));
//Or do whatever you want to each List Item
}
}
I'm using NHibernate and I have the two following classes which map my DataBase schema:
public class A
{
public virtual int Id { get; set;}
public virtual List<B> MyList { get; set; }
}
public class B
{
public virtual int Id { get; set; }
public virtual DateTime Date { get; set; }
public virtual A FKtoA { get; set; }
}
I would like to get all the entries of table A that have all the elements of their MyList property with a Date less than a given value.
How can I do that with an elegant NHibernate syntax?
I owe you the "elegant" part... :-)
This is a possible HQL. Note that inverted your condition: instead of looking for "A that have all the elements of their MyList property with a Date less than a given value", I look for "A that don't have any elements of their MyList property with a Date bigger than or equal to a given value".
from A a
where a not in
(select a1
from A a1, B b
where b.Date >= :date
and b in elements(a1.MyList))
Usage:
var results = session.CreateQuery("hql from above")
.SetParameter("date", DateTime.Today)
.List();
Note that, if you declare a bidirectional relationship between A and B (by adding an A property), the query is much simpler:
from A a
where a not in
(select b.A
from B b
where b.Date >= :date)
Update: here's how to do it with Criteria:
session.CreateCriteria<A>().Add(
Subqueries.PropertyNotIn("id",
DetachedCriteria.For<A>()
.CreateCriteria("MyList")
.SetProjection(Projections.Property("id"))
.Add(Restrictions.Ge("Date", DateTime.Today))))
Use this
ICriteria criteria = session.CreateCriteria<ClassOfTableOne>();
criteria.CreateAlias("FieldNameOfTypeTable2","aliasName");
criteria.SetFetchMode("aliasName", FetchMode.Join);
criteria.Add(Restrictions.Lt("aliasName.Date", yourdate));
if your class B looks something like this (where the MyList property of A looks for this FK)
public class B
{
public virtual int Id { get; set; }
public virtual DateTime Date { get; set; }
public virtual A FK_ToA { get; set; }
}
then i think you are looking for (HQL)
nhSes.CreateQuery("select b.FK_ToA from B b where b.Date < :passedDate")
.SetTimestamp("passedDate", DateTime.Now).List<A>()
The currently accepted answer relies on a correlated sub-query, which as a rule-of-thumb is just "bad SQL".
It's much better to simply express this using set based semantics rather than a more functional approach.
Essentially you want your SQL to look like this:
SELECT
A.Id
FROM A
LEFT OUTER JOIN B ON A.Id = B.FKtoA
WHERE B.Date < #MyDate
This reads as "I want a set of columns from A as related to a set of B where B's Date is less than some value". This can be achieved using the ICriteria API:
ICriteria criteria = session.CreateCriteria<A>();
criteria.CreateAlias("MyList", "b", JoinType.LeftOuterJoin)
criteria.Add(Restrictions.Lt("b.Date", myDate));
criteria.SetResultTransformer(new DistinctRootEntityResultTransformer());
criteria.List<A>();
Part of the trick is using NHibernate's built-in DistinctRootEntityResultTransformer: since the left outer join could return multiple instances of A per B, we want our ICriteria to only return the distinct instances (assuming we don't care about ordering or whatever else).