I have a table in my database with four columns:
string: year
string: weeknr
int: number
In other tables I combine these columns into yywwnnn as a string.
The number columns is a identity column.
Now I want to retrieve some records from a table that I want to join with the mentioned table.
something like:
from R in db.Requests
join RN in db.RequestNumbers on R.RequestNumber equals (RN.Year + RN.Weeknr + RN.Number)
But of course RN.Number is a integer and I need it to be a 3 digit string.
so:
16 07 1 ==> 1607001
16 07 10 ==> 1607010
16 07 100 ==> 1607100
I have tried this:
from R in db.Requests
join RN in db.RequestNumbers on R.RequestNumber equals (RN.Year + RN.Weeknr + (RN.Number.toString().PadLeft(3,char.Parse("0")))
But PadLeft is not recognized.
Is there any other solution to this?
[EDIT]
This is the full method:
public List<RequestList> getMyRequests(string userID)
{
var result = (from R in db.Requests
join MT in db.MainTorsList on R.Category equals MT.MainTorsListID into MTL
from MT in MTL.DefaultIfEmpty()
join ST in db.SubTorsList on R.RequestType equals ST.SubTorsListID into STL
from ST in STL.DefaultIfEmpty()
join S in db.StatusList on R.RequestStatus equals S.StatusListID into SL
from S in SL.DefaultIfEmpty()
join RN in db.RequestNumber on R.RequestNumber equals RN.Year + RN.Week + (RN.Number.ToString().PadLeft(3, char.Parse("0"))) into RNL
from RN in RNL.DefaultIfEmpty()
where R.CreatedBy == userID && RN.Removed == false
select new
{
RequestID = R.RequestID,
RequestDate = R.CreatedOn,
RequestNumber = R.RequestNumber,
Category = MT.Name,
RequestType = ST.Name,
Description = R.RequestDescription,
Status = S.Name,
Options = ""
}
);
List<RequestList> requests = (List<RequestList>)result.ToList().ToNonAnonymousList(typeof(RequestList));
return requests;
}
The error message:
Additional information: LINQ to Entities does not recognize the method 'System.String PadLeft(Int32, Char)' method, and this method cannot be translated into a store expression.
The trick is to use DbFunctions.Right like this
DbFunctions.Right("00" + RN.Number, 3)
i.e. prepend enough zeros at the beginning and take the exact length needed from the end.
All the used methods are supported by LINQ to Entities (at least in the latest at the moment EF6.1.3).
When you creates a linq expression pointing to a sql-database this it is translated into a sql query and there are functions that cannot be translated to sql (such as string.Format(), object.ToString()). When an unsupported function is used, an exception like yours is raised.
'SqlFunctions' and 'EntityFunctions' classes provides 'CRL methods' that you can use in Linq to Entities expressions.
SqlFunctions.StringConvert() converts an integer to its string representation, allowing you specify the desired length (filling with leading spaces).
You can use this function and call string.Replace(string, string) method (yes, it's available) to replace spaces to zeros.
This is the query:
from R in db.Requests
join RN in db.RequestNumbers on R.RequestNumber equals
(RN.Year + RN.Weeknr + SqlFunctions.StringConvert((double)RN.Number, 3).Replace(" ", "0"))
Hope this helps.
Did you try:
Rn.number.value.ToString("000", System.Globalization.CultureInfo.InvariantCulture)
?
You can create the string in the needed format and then use it in your expression. Here's a quick example I did to pad '0' depending on the length of the number after the fourth character. You wouldn't need to assign var mystring = "your string" and so forth, I was just doing that so I didn't need to type in all the scenarios each time to run it:
var exampleA = "16071";
var exampleB = "160710";
var exampleC = "1607100";
var mystring = exampleB;
var afterFourthCharacter = mystring.Substring(4);
var result = mystring.Substring(0,4) + afterFourthCharacter.PadLeft(mystring.Length + (3 - mystring.Length), '0');
Create dynamic keys with the value and let Linq sort it out.
Something like this:
db.Requests // Outer
.Join(db.RequestNumber // Inner
R => new { R.Year, R.Weeknr, R.Number }, // Outer key
RN => new { RN.Year, RN.Weeknr, RN.Number }, // Inner key
(R, RN) => new
{
R.Year,
RN.Number,
....
}
);
Note because this is EF (or Linq To SQL) you may have to bring down the whole table (use .ToList()) on the Requests and RequestNumber together.
Or better yet if you have access to the database table, simply create a computed column whose heuristics will combine all three columns into a unique identity key. Then in Linq it can join on that id from the computed column.
Related
I'm a newcomer in Linq C#.
I have a scenario where I need to check part of a sentence is equal to another value of the field.
I use IndexOf to get part of the sentence in the left join condition. The result is good when any data match between 'a' and 'c'. But when data does not exist in 'c', then the value of 'test' is all data of table dbData.Data3.
Can anyone know what I'm missing here?
var test = (from a in dbData.Data2
let COLODescIndexOfSpace = a.LongKeywordDesc .IndexOf(' ') < 0 ? 0 : a.LongKeywordDesc .IndexOf(' ')
join c in dbData.Data3 on
new
{
KeywordDesc = a.LongKeywordDesc .Substring(0, COLODescIndexOfSpace),
Stsrc = true
}
equals new
{
KeywordDesc = c.KeywordDesc,
Stsrc = (c.Stsrc != AppConstant.StrSc.Deactive)
}
into c_leftjoin
from c in c_leftjoin.DefaultIfEmpty()
where lmsCourseOutlineIds.Contains(a.CourseOutlineID)
select new
{
data = a.KeywordID,
data2 = c.KeywordID,
data3 = c.KeywordDesc,
}).ToList();
this is some example of data
here
and this is what I expect as result
here
Here is one way to solve the problem (I will leave it to my Linq betters figure out a solution purely with Linq). In short, just create a SQL View in the database and call it to get your desired results.
For example, in the database:
create view dbo.GetSentencesByKeywords
as
select
d2.ID,
d2.longKeywordDesc,
d3.keywordDesc,
d3.AdditionalInfo
from
dbo.Data2 d2
left join
(
select
ID,
keyWordDesc,
AdditionalInfo,
len(keyWordDesc) as keyWordLength
from dbo.Data3 data3
) d3
on substring(d2.longKeywordDesc, 0, d3.keyWordLength + 1) = d3.keywordDesc
Now the linq is nice and simple (assuming you can re-scaffold the dbContext to add it to your models, or otherwise can get it in there one way or another):
var x = dbData.GetSentencesByKeywords.Select(c => c);
I have the following 2 tables.
Table1:
Number Table2ID Count
====== ======== =====
1 1 3
1 2 5
1 4 2
1 5 4
2 1 6
2 3 2
2 2 4
2 5 3
Table2:
ID Code Sequence
== ==== ========
1 AA 1
2 BB 2
3 CCC 3
4 D 4
5 EE 5
Using these tables, I want to get the following result:
Number Codes
====== =====
1 AA, BB, D, EE
2 AA, BB, CCC, EE
For this purpose, I wrote the following query (according to this answer):
from tempResult in (from t1 in Table1
join t2 in Table2
on t1.Table2ID equals t2.ID
select new
{
Number = t1.Number,
Code = t2.Code,
Sequence = t2.Sequence
})
group tempResult by tempResult.Number into groupedTempResult
select new
{
Number = groupedTempResult.Key,
Codes = string.Join(", ", groupedTempResult.OrderBy(x => x.Sequence).Select(x => x.Code))
}
Upon executing this query, I faced the following exception (as mentioned in the comment of that answer):
LINQ to Entities does not recognize the method 'System.String Join(System.String, System.String[])' method, and this method cannot be translated into a store expression.
To fix this exception, I made the following correction according to this answer:
from tempResult in (from t1 in Table1
join t2 in Table2
on t1.Table2ID equals t2.ID
select new
{
Number = t1.Number,
Code = t2.Code,
Sequence = t2.Sequence
}).AsEnumerable()
group tempResult by tempResult.Number into groupedTempResult
select new
{
Number = groupedTempResult.Key,
Codes = string.Join(", ", groupedTempResult.OrderBy(x => x.Sequence).Select(x => x.Code))
}
After this modification, I started getting the following exception while executing the query:
Error: Unable to create a constant value of type 'Anonymous type'. Only primitive types or enumeration types are supported in this context.
To understand in which select clause I am having problem, I declared a class for each of the select clause - for this reason the query got modified to this:
from tempResult in (from t1 in Table1
join t2 in Table2
on t1.Table2ID equals t2.ID
select new TempResult
{
Number = t1.Number,
Code = t2.Code,
Sequence = t2.Sequence
}).AsEnumerable() // converted the result as enumerable
group tempResult by tempResult.Number into groupedTempResult
select new Result
{
Number = groupedTempResult.Key,
Codes = string.Join(", ", groupedTempResult.OrderBy(x => x.Sequence).Select(x => x.Code))
}
After this modification, I got the following exception:
Error: Unable to create a constant value of type 'Namespace.Name.Result'. Only primitive types or enumeration types are supported in this context.
So from my understanding, the last select clause is where the exception is happening.
I tried to follow the answers of the following 1, 2 questions as much as I could - resulting a new version of the query.
from tempResult in (from t1 in Table1
join t2 in Table2
on t1.Table2ID equals t2.ID
select new TempResult
{
Number = t1.Number,
Code = t2.Code,
Sequence = t2.Sequence
}).AsEnumerable()
.GroupBy(x => x.Number)
.Select(x => new Result { Number = x.Key, Codes = string.Join(", ", x.OrderBy(y => y.Sequence).Select(y => y.Code)) })
select tempResult
This did not solve the exception mentioned earlier.
At this point, I am very much out of ideas about how can I get my desired result.
Any help regarding this issue is highly appreciated.
A point to note that I have to join the result of this query / operation with another query. So breaking this query to multiple statements / operations is not what I am looking for.
Edit: Let me try to clarify on how I am trying to use this query actually.
from otherResult1 in resultFromAnotherQuery1
join result in (from tempResult in (from t1 in Table1
join t2 in Table2
on t1.Table2ID equals t2.ID
select new TempResult
{
Number = t1.Number,
Code = t2.Code,
Sequence = t2.Sequence
}).AsEnumerable()
.GroupBy(x => x.Number)
.Select(x => new Result { Number = x.Key, Codes = string.Join(", ", x.OrderBy(y => y.Sequence).Select(y => y.Code)) })
select tempResult).ToList()
on otherResult1.Number equals result.Number
join otherResult2 in resultfromAnotherQuery2
on otherResult1.ColumnA equals otherResult2.ColumnB
.....
select new FinalResult
{
.......
Codes = result.Codes,
.......
}
If I skip this joining with result and otherResult1 and ignore populating the Codes field in FinalResult class - just work with otherResult1 and otherResult2, there is no problem to execute the query. But when I try to do this join, I face the exception mentioned in the question.
In the end, it all boils down to a construction like
from q in resultFromAnotherQuery1
join o from listOfObjects on ...
select new { }
Here listOfObjects is everything between from tempResult in and ToList() in the last code snippet. The join with resultfromAnotherQuery2 isn't important for the answer.
I wish the creators of EF would have come up with a somewhat more palatable exception message for this frequently occurring error. Something like:
EF is trying to translate the entire statement [statement] into SQL, but the local sequence 'listOfObjects' can only be translated if it contains primitive values.
Unfortunately, the part that created the local sequence initially threw another exception ("LINQ to Entities does not recognize the method..."). After you solved that, you bumped into this second exception. Since you were totally focused on the local sequence and weren't helped by the cryptic message, you kept looking for a solution there. But the exception had now silently moved to the entire statement.
The solution is to compose everything until select new FinalResult from IQueryables. Only then add AsEnumerable() and then .Select(x => new FinalResult { ... }) where you can do stuff that EF doesn't support.
That means that you have to suspend the string.Join part until the very end:
.Select(x => new FinalResult
{
.......
Codes = string.Join(", ", x.Codes))
.......
})
...where x.Codes is composed by something like...
Codes = groupedTempResult.OrderBy(x => x.Sequence).Select(x => x.Code)
In short: what you need here is the forced execution of the first part of the query. Switch the .AsEnumerable() to .ToList() to force the execution at that point.
A bit longer: the first query fails because it fails to translate String.Join to a SQL equivalent. Introducing the AsEnumerable doesn't force the immediate execution and in this case just moves the problem down the line (I'm not sure about the details, but the look at the implementation of GroupBy should explain the reason). Hence the ToList to force the execution.
Alternatively you could look at ToLookup instead of GroupBy.
(based on this older answer, but also see documentation and also this answer might shed some light on the differences between AsEnumerable and ToList)
SELECT
FW1.id, count(*)
FROM
firmware FW1
LEFT JOIN
firmware FW2 ON FW1.firmware_group_id = FW2.firmware_group_id
AND FW1.br_date < FW2.br_date
AND FW2.[public]= '1'
GROUP BY
FW1.id
I am looking to convert into linq query. As I know less than symbol cannot be converted into Linq query. Please suggest how to do it. I have a string date and I need to compare into linq.
As you said, Linq does not support other types of join outside of EquiJoin. Docs is pretty clear on what you can do to bypass this:
you could use a simple cross join (a cartesian product), then applying in the where clause the conditions for your non-equijoin.
Or you could use a temporary variable to store a new table with only the attributes you need for your query and, like before, applying the conditions in the where clause.
In your case, a possible Linq query could be this one:
from f1 in firmwares
from f2 in firmwares
let f1_date = DateTime.Parse(f1.Dt)
let f2_date = DateTime.Parse(f2.Dt)
where f1.Group_Id == f2.Group_Id && f1_date < f2_date
group f1 by f1.Id into fres
select new {
Id = fres.Key,
Count = fres.Count()
};
However I am still thinking how to emulate the LEFT JOIN without casting it to a group join.
Of course the < symbol can be used. Just use method syntax instead of query syntax!
Every FW1 has zero or more FW2s. Every FW2 belongs to exactly one FW1. This one-to-many is implemented using foreign key firmware_group_id.
Apparently you want all FW1s, each with the number of its FW2s, that have a property public with a value equal to 1 and a property br-date larger than the value of the br-date of the FW1.
Whenever you want an item with its many sub-items (using a foreign key), like s School with its Students, a Customer with his Orders, a Book with his Pages, you'll need Enumerable.GroupJoin
var result = FW1.GroupJoin(FW2, // GroupJoin FW1 and FW2
fw1 => fw1.firmware_group_id, // from fw1 take the primary key firmware_group_id
fw2 => fw2.firmware_group_id, // from fw2 take the foreing key firmware_group_id
(fw1, fw2s) => new // from every fw1, with all its matching fw2s
{ // make one new object containing the following properties:
Id = fw1.Id,
// Count the fw2s of this fw1 that have public == 1
// and a date larger than fw1.date
Count = fw2.Where(fw2 => fw2.Public == 1 && fw1.br_date < fw2.br_date)
.Count(),
});
Note:
I 'm trying to do a search for a contact. For example value "Café " which is stored in the name field , but when I search like "cafe" does not return any record .
I tried to do the following
using (ServiceContext svcContext = new ServiceContext(_serviceProxy))
{
var query_where3 = from c in svcContext.ContactSet
join a in svcContext.AccountSet
on c.ContactId equals a.PrimaryContactId.Id
where c.FullName.Normalize(NormalizationForm.FormD).Contains("Café")
select new
{
account_name = a.Name,
contact_name = c.LastName
};
}
and appear the Exception with message saying "Invalid 'where' condition. An entity member is invoking an invalid property or method"
You can't use that functions on LinQ-CRM, the correct way to do the query is:
c.FullName == "someString" or c.FullName.equals("someString").
This is because you can't use functions or transformations on the left condition. You must use the attribute itself.
Your query will look like:
using (ServiceContext svcContext = new ServiceContext(_serviceProxy))
{
var query_where3 = from c in svcContext.ContactSet
join a in svcContext.AccountSet
on c.ContactId equals a.PrimaryContactId.Id
where c.FullName == "Café" || c.FullName == "Cafe"
select new
{
account_name = a.Name,
contact_name = c.LastName
};
}
You can't really deal with the accents with Linq to SQL in general ... and you are even more limited with what you can do with Linq to CRM. You cant modify the DB; unless you don't care about being supported. Then you could do something like : MAD suggested and to a db alter.
ALTER TABLE Name ALTER COLUMN Name [varchar](100) COLLATE SQL_Latin1_General_CP1_CI_AI
I personally would not recommend that.
The best that I can come up with is getting the data as close as you can and filtering it from there inside a list or something similar.
I have to do it all the time and it is a pain (and adds more overhead) but there is not really another workaround that I have found.
//declare a dictionary
Dictionary<string, string> someDictionary = new Dictionary<string, string> ();
using (ServiceContext svcContext = new ServiceContext(_serviceProxy))
{
var query_where3 = from c in svcContext.ContactSet
join a in svcContext.AccountSet
on c.ContactId equals a.PrimaryContactId.Id
where c.FullName.Contains("Caf")
select new
{
account_name = a.Name,
contact_name = c.LastName
};
}
//then
foreach(var q in query_where3)
{
if(string.IsNullOrEmpty(account_name)==false && string.IsNullOrEmpty(contact_name)==false)
{
someDictionary.Add(account_name, contact_name);
}
}
//then you can add the .Normalize(NormalizationForm.FormD) to your dictionary
Hope that helped.
Its all about
.Normalize(NormalizationForm.FormD)
, probably EF does not knows how to handle this method. Remove it and test just with
c.FullName.Contains("Café")
------------------------------------------------- Added in 2015-01-30 --------------------------------------------------
So man, the unique solution i can think about is list before you do the where condition. This way the you can use the normalize once this will be handled by linq 2 objects. try:
(from c in svcContext.ContactSet join a in svcContext.AccountSet
on c.ContactId equals a.PrimaryContactId.Id
select new {a=a,c=c} ).ToList()
.Where(c=>c.FullName.Normalize(NormalizationForm.FormD).Contains("Café"))
.Select( x=> select new {
account_name = x.a.Name,
contact_name = x.c.LastName
};)
But that way can cause some overhead given that linq 2 obejects runs in application server memory, not in database server.
CRM's LINQ translator cannot handle the .Equals() method.
on c.ContactId equals a.PrimaryContactId.Id
Change the above line to below line.
on c.ContactId == a.PrimaryContactId.Id
string test = "/";
var results = from table1 in data2.AsEnumerable()
join table2 in data1.AsEnumerable()
on ((string)(table1["ANo"]) + test + (string)table1["MNo"]) equals (string)table2["File_Code"]
where (float)table1["c1"] != 0
&& (string)table1["Case_Status"] == "Open"
select new
{
ACode = (int)table1["ACode"],
ANo = (int)table1["ANo"],
c1 = (int)table1["c1"]
};
Getting a error:
Specified Cast is invalid on ((string)(table1["ANo"]) + test + (string)table1["MNo"]) equals (string)table2["File_Code"].
So in my linq I am trying to match ANo/MNo in one database to File_Code in another where ANo & MNo are different columns in the first database, any ideas?
(string)(table1["ANo"]) is actually a call to a conversion function in .NET, and there is no corresponding function in the underlying storage engine. Therefore, when LINQ provider is about to actualize the query in form of a particular storage-specific expression tree, the operation fails because LINQ provider cannot find an appropriate function which to use.
If item such as ANo is not already a string (e.g. varchar or something similar), then you probably need to call specific provider's method such as SqlFunctions.StringConvert(table1["ANo").
Typical examples where .NET code cannot be converted by the LINQ provider are date/time functions (e.g. DateTime.AddSeconds, etc.).
When you've got code like
object a = "1";
var a2 = (string)a;
You're only changing the compile-time type of a from object to string, which is called casting. This only works because a already is a string (the actual type). If you do
object a = 1;
var a2 = (string)a;
You run into a runtime exception (InvalidCastException) because an integer can't act as (cast to) a string. That's what's going on in your code. At least one of the objects you try to cast to string is not actually a string.
The remedy is simple: use ToString(). This converts the object to a string. Conversion converts the actual type of an object.
var results1 = from table1 in data2.AsEnumerable()
select new
{
A_M = table1["ANo"] + "\" + table1["MatterNo"],
ANo = table1["ANo"],
c1 = table1["c1"].Equals(DBNull.Value) ? 0 : Convert.ToInt32(table1["c1"]),
//Case_Status = Convert.ToString(table1["Case_ Status"])
};
var results = from table1 in results1
join table2 in data1.AsEnumerable()
on table1.A_M equals (string)table2["File_Code"]
where table1.c1 != 0
&& (string)table2["Case_Status"] == "Open"
orderby table1.ANo
select new
{
cCode = table2["acAccountCode"],
ANo = table1.ANo,
c1 = table1.c1
};