Computed Columns in EF using LINQ - c#

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
}
}

Related

Exception when using LINQ orderby: "Failed to compare two elements in the array"

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;

Populating a list within a list in C# not using foreach loop. better way?

I have a list of objects within a list of objects (List-ParentClass) that has as one of its objects a nested list (List-ChildClass). To populate List-ChildClass I have used a foreach loop as shown below. I have also nested a linq query as show below.
At this point I am having some performance issues and I feel like there is a better way to do this that I am just not finding.
Question: How could I do this better/faster?
Note - This is a Web based .net MVC application written in C#. I use EF back to a SQL database.
public class ParentClass
{
public int pcid { get; set; }
public List<ChildClass> ChildClassList { get; set; }
}
public class ChildClass
{
public int pcid { get; set; }
public int ccid { get; set; }
}
public class DoWork
{
public void ExampleMethodForEach()
{
List<ParentClass> ParentClassList = new List<ParentClass>();
foreach(ParentClass a in ParentClassList)
{
a.ChildClassList = EFDatabase2.where(b => b.pcid == a.pcid).select(b => b.ccid).ToList();
}
}
public void ExampleMethodLinq()
{
var ParentClassList = (from a in EFDatabase
select new ParentClass
{
ccid = a.ccid,
pcid = (from b in EFDatabase2
where b.pcid == a.pcid
select b.ccid).ToList()
//something like this were I nest a query
}).ToList();
}
}
The best way when working with relational databases and LINQ is to use joins to correlate data. In your case, the most appropriate is group join:
var ParentClassList =
(from p in EFDatabase
join c in EFDatabase2 on p.pcid equals c.pcid into children
select new ParentClass
{
pcid = p.pcid,
ChildClassList =
(from c in children
select new ChildClass
{
pcid = c.pcid,
ccid = c.ccid
}).ToList()
}).ToList();
which should give you a nice fast single database query.
P.S. Hope your EFDatabase and EFDatabase2 variables refer to two tables inside one and the same database.
You are hitting your database multiple times. You have a N+1 issue.
What I suggest is to query all parents first, but excluding the children data. Then get the ID of all parents that you retrieved and put it inside an array. We will use that array to create a IN clause in SQL.
After loading all the children using the array of parent IDs, map them to a Lookup using ToLookup using the parent ID as the key and use a foreach to assign the list of children to the parent.
var parents = EFDatabase2.Parents.Where(...).Select(p => new ParentClass { pcid = p.pcid }).ToList();
var ids = parents.Select(p => p.pcid).ToArray();
var children = EFDatabase2.Children.Where(c => ids.Contains(c.ccid)).Select(c => new ChildClass { pcid = c.pcid, ccid = c.ccid }).ToLookup(c => c.pcid);
foreach (var parent in parents)
{
parent.Children = children[parent.pcid];
}
In this case, you will only do two queries to your database.

Member is "not supported in LINQ to Entities"

So I am new to C#, LINQ, and MVC. I am trying to get a list of Ages, but it says
The specified type member 'Age' is not supported in LINQ to Entities.
Only initializers, entity members, and entity navigation properties
are supported.
For a previous tutorial, they use this exact same logic, except they check a string, not an int (Age). Why is this giving me a fit, and how can I fix it?
public ActionResult SearchIndex(string ageValue, string searchString)
{
if (!string.IsNullOrEmpty(ageValue))
{
var AgeList = new List<string>();
var AgeListQry = from d in db.Actors orderby d.Age select d.Age.ToString();
AgeList.AddRange(AgeListQry.Distinct());
}
// other stuff
}
I want to learn what is going on, so that I can avoid this in the future!
Entity Model code
public class Actor
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime BirthDate { get; set; }
public int Age
{
get {
return (int)(DateTime.Now - BirthDate).TotalDays / 365;
}
}
public decimal NetValue { get; set; }
}
public class ActorDBContext : DbContext
{
public DbSet<Actor> Actors { get; set; }
}
As mentioned in the comments, you can't call ToString() in a Linq to Entities query. Instead do it like this:
var AgeList = new List<string>();
//retrieve as whatever type Age is, no conversion in SQL Server
var AgeListQry = (from d in db.Actors orderby d.Age select d.Age).ToList();
//convert them after the fact, using Linq to Objects
AgeList.AddRange(AgeListQry.Select(a => a.ToString()).Distinct());
EDIT
I saw your latest update that does show that Age is not a database column. You are then required to do something like this (assuming BirthDate is properly mapped):
var AgeList = new List<string>();
//retrieve BirthDate from SQL Server and use ToList() to get it to run immediately
var AgeListQry = (from d in db.Actors orderby d.BirthDate select d.BirthDate).ToList();
//convert them after the fact, using Linq to Objects
AgeList.AddRange(AgeListQry.Select(bd => ((int)(DateTime.Now - bd).TotalDays / 365).ToString()).Distinct());
Linq to Entities maps your expressions to SQL statements and there is nothing for it to map to when you use your Age property. Instead, you need to get what you can from SQL Server (BirthDate) and then do the translation to Age yourself. You could replace the inline code with a method call like this if you'd rather:
AgeList.AddRange(AgeListQry.Select(bd => CalculateAge(bd)).Distinct());
//...
private string CalculateAge(DateTime birthday)
{
return ((int)(DateTime.Now - bd).TotalDays / 365).ToString();
}
You haven't the Age in you DB scheme and it is impossible to convert LINQ to DB query.
You must order the Age collection in client side or add calculated column to your table.
There is another way. Have a converter file, where you pass the object, works with the birthdate and produces the age, returns the same object. That also means, that you can't search the database for the age column

Comparing Lists under multiple conditions

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;

How to return LINQ object from method?

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
}

Categories