linq to sql custom method using Expression<Func<...>> - c#

Maybe I am making myself somewhat difficult in this issue. The goal is to use an object (here SearchForm) to analyze the database records and returns if the record matches the search entry.
Since my website allows an advanced search form, the SearchForm object contains a lot properties (5 input fields, some dropdown lists, and a lot checkboxes).
I am using
C# NET 4.0 (not higher due of web host)
LINQ-to-SQL
ASP.NET MVC 4
Here is my snippet so far:
public static IEnumerable<CarDetails> SearchByForm(SearchForm form) {
// get db instance
_db = new MyDBDataContext();
var q = from Cars_car c in _db.Cars_cars
join Cars_equipment e in _db.Cars_equipments on c.equipmentId equals e.id
where ValidData.Invoke(c, form)
select new CarDetails {
// filling CarDetails data
};
return q;
}
And the ValidData method is an Expression<Func<...> type.
private static Expression<Func<Cars_car, SearchForm, bool>> ValidData = (c, f) => MatchesSearchData(f, c);
where MatchesSearchData is the method behind to validate each records
private static bool MatchesSearchData(SearchForm f, Cars_car c) {
// long check if true or false and returns it
}
But there is an error, the overloaded method has some invalid arguments at ValidData.Invoke(c, form).
Am I making myself difficult, or is this the good way to accomplish my results?
If so, can someone provide me a solution to the above error note?
If not, some guidance to use other way to solve this?
There is a way to do this with stored procedures, true, but I rather prefer to do this at this layer.
Used sources;
article about custom LINQ to SQL method
blog post calling custom methods
other blog post about same subject
EDIT 1
An answer from user Erti-Chris Eelmaa has solved the compiler error by using Compile()-method.
where ValidData.Compile().Invoke(c, form)
But when running, it redirects me to the error page. Apparently, i am getting method has no supported translation to sql error.
In Controllers,
public ActionResult Results(SearchForm form) {
ViewBag.cars = Cars_car.SearchByForm(form);
return View("Results");
}
and on template (just quick testing)
#foreach (var car in ViewBag.cars)** {
<p>results : car.Make !</p>
}
** Here, an method Boolean Invoke(MVCproject2.Models.Cars_car, MVCproject2.Models.SearchForm) has no supported translation to sql error got thrown. That's weird, because that Expression method was added to provide a custom method to LINQ to SQL.
Or am i wrong ?

If you want method to be part of the SQL query, you need to create your method on SQL server, and then call it. Like so:
CREATE FUNCTION ReverseCustName(#string varchar(100))
RETURNS varchar(100)
AS
BEGIN
DECLARE #custName varchar(100)
-- Implementation left as exercise for users.
RETURN #custName
END
// and C# part
[Function(Name = "dbo.ReverseCustName", IsComposable = true)]
[return: Parameter(DbType = "VarChar(100)")]
public string ReverseCustName([Parameter(Name = "string",
DbType = "VarChar(100)")] string #string)
{
return ((string)(this.ExecuteMethodCall(this,
((MethodInfo)(MethodInfo.GetCurrentMethod())),
#string).ReturnValue));
}
You can now use ReverseCustName(string) in your "SQL" query.
Source: http://msdn.microsoft.com/en-us/library/vstudio/bb386973(v=vs.100).aspx
Alternatively you can download everything in to your C# client and execute Where there.
public static IEnumerable<CarDetails> SearchByForm(SearchForm form) {
Func<RetailCar_car, SearchForm> compiledLambda = ValidData.Compile();
// get db instance
_db = new MyDBDataContext();
var q = (from Cars_car c in _db.Cars_cars
join Cars_equipment e in _db.Cars_equipments on c.equipmentId equals e.id)
.ToList()
.Where(x => compiledLambda(c, form))
.Select(x => new CarDetails {});
return q;
}
Let's get this straight:when you are using DbContext, you're using IQueryable. The class is meant to generate SQL queries. If you combine your C# method with IQueryable, LINQSQL doesn't know how to translate it to SQL query.
Those two solutions correspond to: either create new SQL method in SQL server, or download everything to C# side using ToList(), which means that IQueryable will be switched to IEnumerable.

Can you please confirm whether the following LINQ is correct
var q = from Cars_car c in _db.Cars_cars
join Cars_equipment e in _db.Cars_equipments on c.equipmentId equals e.id
I can't understand why you are using alias to alias here. According to me, this snippet should be like
var q = from c in _db.Cars_cars
join e in _db.Cars_equipments on c.equipmentId equals e.id
May be this is not the reason for the error, but I wanted to confirm if my understanding is correct.

Related

Can't use .ToList() with IQueryable<T> [duplicate]

I'm migrating some stuff from one mysql server to a sql server but i can't figure out how to make this code work:
using (var context = new Context())
{
...
foreach (var item in collection)
{
IQueryable<entity> pages = from p in context.pages
where p.Serial == item.Key.ToString()
select p;
foreach (var page in pages)
{
DataManager.AddPageToDocument(page, item.Value);
}
}
Console.WriteLine("Done!");
Console.Read();
}
When it enters into the second foreach (var page in pages) it throws an exception saying:
LINQ to Entities does not recognize the method 'System.String
ToString()' method, and this method cannot be translated into a store
expression.
Anyone know why this happens?
Just save the string to a temp variable and then use that in your expression:
var strItem = item.Key.ToString();
IQueryable<entity> pages = from p in context.pages
where p.Serial == strItem
select p;
The problem arises because ToString() isn't really executed, it is turned into a MethodGroup and then parsed and translated to SQL. Since there is no ToString() equivalent, the expression fails.
Note:
Make sure you also check out Alex's answer regarding the SqlFunctions helper class that was added later. In many cases it can eliminate the need for the temporary variable.
As others have answered, this breaks because .ToString fails to translate to relevant SQL on the way into the database.
However, Microsoft provides the SqlFunctions class that is a collection of methods that can be used in situations like this.
For this case, what you are looking for here is SqlFunctions.StringConvert:
from p in context.pages
where p.Serial == SqlFunctions.StringConvert((double)item.Key.Id)
select p;
Good when the solution with temporary variables is not desirable for whatever reasons.
Similar to SqlFunctions you also have the EntityFunctions (with EF6 obsoleted by DbFunctions) that provides a different set of functions that also are data source agnostic (not limited to e.g. SQL).
The problem is that you are calling ToString in a LINQ to Entities query. That means the parser is trying to convert the ToString call into its equivalent SQL (which isn't possible...hence the exception).
All you have to do is move the ToString call to a separate line:
var keyString = item.Key.ToString();
var pages = from p in context.entities
where p.Serial == keyString
select p;
Cast table to Enumerable, then you call LINQ methods with using ToString() method inside:
var example = contex.table_name.AsEnumerable()
.Select(x => new {Date = x.date.ToString("M/d/yyyy")...)
But be careful, when you calling AsEnumerable or ToList methods because you will request all data from all entity before this method. In my case above I read all table_name rows by one request.
Had a similar problem.
Solved it by calling ToList() on the entity collection and querying the list.
If the collection is small this is an option.
IQueryable<entity> pages = context.pages.ToList().Where(p=>p.serial == item.Key.ToString())
Hope this helps.
Upgrading to Entity Framework Version 6.2.0 worked for me.
I was previously on Version 6.0.0.
Hope this helps,
Change it like this and it should work:
var key = item.Key.ToString();
IQueryable<entity> pages = from p in context.pages
where p.Serial == key
select p;
The reason why the exception is not thrown in the line the LINQ query is declared but in the line of the foreach is the deferred execution feature, i.e. the LINQ query is not executed until you try to access the result. And this happens in the foreach and not earlier.
If you really want to type ToString inside your query, you could write an expression tree visitor that rewrites the call to ToString with a call to the appropriate StringConvert function:
using System.Linq;
using System.Data.Entity.SqlServer;
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
using System;
namespace ToStringRewriting {
class ToStringRewriter : ExpressionVisitor {
static MethodInfo stringConvertMethodInfo = typeof(SqlFunctions).GetMethods()
.Single(x => x.Name == "StringConvert" && x.GetParameters()[0].ParameterType == typeof(decimal?));
protected override Expression VisitMethodCall(MethodCallExpression node) {
var method = node.Method;
if (method.Name=="ToString") {
if (node.Object.GetType() == typeof(string)) { return node.Object; }
node = Call(stringConvertMethodInfo, Convert(node.Object, typeof(decimal?));
}
return base.VisitMethodCall(node);
}
}
class Person {
string Name { get; set; }
long SocialSecurityNumber { get; set; }
}
class Program {
void Main() {
Expression<Func<Person, Boolean>> expr = x => x.ToString().Length > 1;
var rewriter = new ToStringRewriter();
var finalExpression = rewriter.Visit(expr);
var dcx = new MyDataContext();
var query = dcx.Persons.Where(finalExpression);
}
}
}
In MVC, assume you are searching record(s) based on your requirement or information.
It is working properly.
[HttpPost]
[ActionName("Index")]
public ActionResult SearchRecord(FormCollection formcollection)
{
EmployeeContext employeeContext = new EmployeeContext();
string searchby=formcollection["SearchBy"];
string value=formcollection["Value"];
if (formcollection["SearchBy"] == "Gender")
{
List<MvcApplication1.Models.Employee> emplist = employeeContext.Employees.Where(x => x.Gender == value).ToList();
return View("Index", emplist);
}
else
{
List<MvcApplication1.Models.Employee> emplist = employeeContext.Employees.Where(x => x.Name == value).ToList();
return View("Index", emplist);
}
}
I got the same error in this case:
var result = Db.SystemLog
.Where(log =>
eventTypeValues.Contains(log.EventType)
&& (
search.Contains(log.Id.ToString())
|| log.Message.Contains(search)
|| log.PayLoad.Contains(search)
|| log.Timestamp.ToString(CultureInfo.CurrentUICulture).Contains(search)
)
)
.OrderByDescending(log => log.Id)
.Select(r => r);
After spending way too much time debugging, I figured out that error appeared in the logic expression.
The first line search.Contains(log.Id.ToString()) does work fine, but the last line that deals with a DateTime object made it fail miserably:
|| log.Timestamp.ToString(CultureInfo.CurrentUICulture).Contains(search)
Remove the problematic line and problem solved.
I do not fully understand why, but it seems as ToString() is a LINQ expression for strings, but not for Entities. LINQ for Entities deals with database queries like SQL, and SQL has no notion of ToString(). As such, we can not throw ToString() into a .Where() clause.
But how then does the first line work? Instead of ToString(), SQL have CAST and CONVERT, so my best guess so far is that linq for entities uses that in some simple cases. DateTime objects are not always found to be so simple...
My problem was that I had a 'text' data type for this column (due to a migration from sqlite).
Solution: just change the data type to 'nvarchar()' and regenerate the table.
Then Linq accepts the string comparison.
I am working on retiring Telerik Open Access and replacing it with Entity Framework 4.0. I came across same issue that telerik:GridBoundColumn filtering stopped working.
I find out that its not working only on System.String DataTypes. So I found this thread and solved it by just using .List() at the end of my Linq query as follows:
var x = (from y in db.Tables
orderby y.ColumnId descending
select new
{
y.FileName,
y.FileSource,
y.FileType,
FileDepartment = "Claims"
}).ToList();
Just turn the LINQ to Entity query into a LINQ to Objects query (e.g. call ToArray) anytime you need to use a method call in your LINQ query.

error in calling and calculate function with int? as input

I have a function like that :
public int? calculateContractPrice(int? comid)
{
int? sum = 0;
var q = from i in dbconnect.tblMaterialGroups
where i.tenderId == _tenderId
select i.id;
foreach (int i in q )
{
var q2 = from g in dbconnect.tblMaterialTenderAnnouncePrices
where g.MaterialGroupId == i && g.companyId == comid
select g;
sum = q2.First().amount*q2.First().price + q2.First().amount*q2.First().PriceForElse + sum;
}
return sum ;
}
When i try to execute this :
List<presentationcontract> q = (from i in dbconnect.tblContracts
where i.tender == _tenderId
select new presentationcontract()
{
tax =(calculateContractPrice(i.companyId)*(6/100)).ToString()
}).ToList();
Tax is string .after executing i got this error :
couldn't translate expression calculateContractPrice(i.companyId)*(6/100),invoke(value(system.Func1[system.nullable1[system.Int32]]))).ToString() into SQL and could not treat it as a local expression
Your edit makes clear the issue. You're trying to do
tax =(calculateContractPrice(i.companyId)*(6/100)).ToString()
in a sql statement but calculateContractPrice is in c#! To understand what's going on you really need to understand a bit how LINQ works.
First of all, stop using the silly sql-style syntax for LINQ. It is less powerful than the lambda syntax and hides what is going on under the hood in a way that makes it hard to understand.
Second consider a LINQ statement
users.Where(u => u.Name == "George").ToList();
where users is IEnumerable<User>. What happens here is that the lambda part is of type Func<User, bool> and gets compiled to a method that gets run against every single instance of User.
Now consider this LINQ statement
db.Users.Where(u => u.Name == "George").ToList();
where db.Users is IQueryable<T>. This looks the same but what happens is VERY different. What happens is that lambda is actually of type Expression<Func<User, bool>> this doesn't get compiled to a method, instead it gets compiled into something called an expression tree. This gets passed to the LINQ provider (in your case Entity Framework I'm guessing) which examines it and converts that into a SQL statement
SELECT Id, Name, Address FROM users WHERE Name = 'George'
What is happening in your case is that it sees the call to calculateContractPrice and simply has no way of converting that to SQL.
What you should therefore do is ensure the query runs first, then use the IEnumerable<T> form of LINQ that runs in c# to call your method.
var contracts = dbconnect.tblContracts.Where(i => i.tender == _tenderId)
.ToList() //Query executes here, now you have IEnumerable<T>
.Select(i => new PresentationContract {
Tax = ...
}).ToList(); //this ToList is only necessary if you want to prevent multiple iteration
You will want to solve all the other problems everyone else pointed out as well of course.
A few other notes - you will want to read up on .Net naming conventions. Usually, anything public,protected, or internal (classes, fields, properties, etc.) is recommended to be PascalCase. Also you probably want to move the division portion into the PresentationContract class. This class that has a Tax property should probably be the one that knows how to generate it.
Try this:
int? ret = calculateContractPrice(i.companyId);
if(ret.HasValue)
{
tax =(ret.Value*(6/100)).ToString();
}
You should make sure that the function indeed returned a value and then you use that integer value in calculation.

Dynamic Linq - Joining a table with 1 to many relationship

I'm using Dynamic Linq as the backend to an in-app reporting tool and I've hit a problem that I can't get round where I have to access a table that has a 1:M relationship .
My simplified data structure is this:
If I were querying this in standard Linq I'd write the query as:
from a in context.Table_A
select new
{
a.RefNo,
val = from b in a.Table_B
where (b.A_ID == a.ID)
where (b.code == "A0001"
select(b.Value).FirstOrDefault()
}
This works without any problem. However, when I try the query using Dynamic Linq I can't get the join to work.
From the code below you can see what I'm getting at but obviously I can't use the "a." and the "a.Table_B" references in the query. What do I have to do to be able to access Table_B in this context?
string select = "new (Ref_No,
val = from b in a.Table_B
where (b.A_ID == a.ID)
where (b.code == \"A0001\"
select(b.Value).FirstOrDefault()";
var results = context.Table_A.Select(select);
Edit 1:
To answer #Hogan's comment - Why don't I use join: The reports system is dynamic and the select statement may or may not be joining on to Table_B (or indeed joining on to Table_B multiple times) so the join has to be optional. My other issue with this is that unlike the Select method where I can pass in a string as a parameter (allowing me to make it dynamic quite easily) the Join() method can't be called in that way. The closest thing I've found is a dynamic Linq join extention method, something I may have to consider using but I've a feeling that this will be cumbersome with the dynamic select().
Edit 2:
Based on Hogan's suggestions I've got this far:
delegate string searchTableA(Table_A a);
public void Search()
{
....
searchTableA sel = (a) =>
{
return (from b in context.Table_B
where (b.A_ID == a.ID)
select (b.Value)).FirstOrDefault();
};
var res = context.Table_A.Select(sel);
}
This gives the error: 'System.Data.Entity.DbSet<TestDynamicLinqJoins.Table_A>' does not contain a definition for 'Select' and the best extension method overload 'System.Linq.Dynamic.DynamicQueryable.Select(System.Linq.IQueryable, string, params object[])' has some invalid arguments
Hard to give exact code because I don't know the types of your elements, but something like this would work fine using delegates.
delegate string searchTableA(elementType a);
searchTableA sel = (a) =>
{
return from b in a.Table_B
where (b.A_ID == a.ID)
where (b.code == "A0001")
select(b.Value).FirstOrDefault();
};
var results = context.Table_A.Select(sel);

LINQ What does 'select new' do?

I've built the following LINQ query
var activeMembers = from m in context.py3_membershipSet
join c in context.ContactSet on m.py3_Member.Id equals c.ContactId
where m.statuscode.Value == 1
orderby m.py3_name
select m;
But I've since seen an example formatted as such:
var contacts =
(
from c in xrm.ContactSet
join a in xrm.AccountSet on c.ParentCustomerId.Id equals a.Id
where a.Name == "Acme Pty Ltd"
select new
{
Name = c.FullName,
DOB = c.BirthDate,
Gender = (c.FormattedValues.Contains("gendercode") ? c.FormattedValues["gendercode"] : "Ambiguous")
}
);
(I realise this is a different set of data) What does the inclusion of the 'select new' actually do in this case?
What are the benefits of it over my example in the first code block?
I realise some might find this a tedious question, but I a want to learn LINQ and need to learn it fast. But I also don't want to run something -that I don't fully understand- on a clients live CRM
LINQ returns a collection of anonymous objects, either way. select new let's you define the layout of that object and which properties/property names are included in that anonymous object.
You can also use select new ClassName { } to return a list of instances of an entity class that you define.
As previous answers have noted both methods return an anonymous type. To fully answer your question though: "What are the benefits of the second statement over the first?"
The first statement returns all of the fields of m as-is. If you have 7 "columns" then activeMembers will contain all of them and whatever data they contain.
In the second statement you're projecting the results into a custom anonymous object that has only 3 fields. Not only that but you can perform logic on the "source fields" before storing them in the anonymous object. This gives you much of the flexibility of storing them in a container class without you actually having to define that class in code.
You could also do select new SomeClass { } which would project your results into a predefined class container.
The following pseudo-code may or may not be helpful in understanding the difference:
var myQuery = from p in someContext.someTable
group p by p.someField into g
select new MyContainer {
Field1 = g.Sum(a => a.field1)
Field2 = g.Max(a => a.field2)
};
myQuery in the above is now a collection of MyContainer. If I had omitted the class MyContainer then it would be a collection of an Anonymous Type containing the fields that I specified. Obviously, the difference here is that MyContainer has to be defined elsewhere in your code whereas the anonymous class is built/defined for you at compile time. In:
var myQuery = from p in someContext.someTable
select p;
myQuery is an anonymous class containing all of the fields and their values in someTable.
You are using the Queryable.Select<TSource, TResult>(IQueryable<TSource>, Expression<Func<TSource, TResult>>) method where ContactSet is the TSource and the of anonymous object return type is the TResult. Your code could also be written as
...Select(r => new {
Name = c.FullName,
DOB = c.BirthDate,
Gender = (c.FormattedValues.Contains("gendercode") ? c.FormattedValues["gendercode"] : "Ambiguous")
})
where the select method is returning a collection of anonymous types.
Also, there is a bit more going on under the hood since it looks like you're querying the database. Your implementation of IQueryable looks at the Select method that you've written, and translates the expression you've provided into valid SQL so that it can retrieve all the necessary information for the anonymous object that you're returning. Notice that I said your implemenation of IQuerable translates the provided expression (not function) into sql. The Select extension method only accepts expressions, not functions because it can't translate functions to sql.

Linq Extension method for Join

I am in the process of learning LINQ, ASP.NET, EF, and MVC via online video tutorials. I would love some help understanding Joins in LINQ extension method syntax.
For simplification, I have two tables (these map to a SQL DB):
User Table:
public int userID{get;set;}
public string firstName{get;set;}
...
Address
public int ownerID{get;set;}
public int value{get;set;}
public string Nickname{get;set;}
public string street{get;set;}
public string zip{get;set;}
...
Let's say I want to find all the property that a particular user owns. I believe I can do something like this:
var test = db.User
.Join(db.Address, user => user.userID, add => add.ownerID, (user, add) => new { user, add });
Source: http://byatool.com/c/linq-join-method-and-how-to-use-it/
This should be equivalent to
SELECT * FROM User a JOIN Address b on a.userID = b.ownerID
Please confirm that this is correct.
Now, what if I wanted to find all property that a particular user owns that has a value greater than x. Let's take it a step further and say x is a result from another LINQ query. How do I force execution of x inside of a second query? Do I even have to consider this, or will LINQ know what to do in this case?
Thanks
EDIT:
When I try to use the result of a query as a parameter in another, I am required to use a greedy operator to force execution. Many people like to use .Count() or .ToList(). I only expect x (from example above) to return 1 string by using .Take(1). If I append ToList() to the end of my first query, I am required to use x[0] in my second query. This seems like a messy way to do things. Is there a better way to force execution of a query when you know you will only have 1 result?
If I understand your question, you're trying to do a conditional on a joined model?
var query = db.Users.Where(x => x.Addresses.Where(y => y.Value >= yourValue).Any());
That will return all users who have a property value greater than yourValue. If you need to return the addresses with the query, you can just add Include to your query. For example:
query.Include(x => x.Addresses);
You don't need to manually do that Join that you have in your example.

Categories

Resources