Linq to Sql Enums and Conditional Operator - c#

Whichever way I do it, it seems something goes wrong when using the conditional operator on enum values in Linq to Sql. For example
var ret = from listings in db.Listings
select new Listing
{
ID = listings.ID,
//etc
OrderStatus = listings.OrderItems.Count > 0
? listings.OrderItems.First().Order.OrderStatus : OrderStatus.NotCheckedOut
};
System.Data.SqlClient.SqlException:
Conversion failed when converting the
nvarchar value 'Charged' to data type
int..
When I leave the enum field as nvarchar mapped to a string I get similar conversion errors. How to workaround this ?

This works, maybe there's a better way though ?
var ret = from listings in db.Listings
let ordS = listings.OrderItems.Count > 0 ?
listings.OrderItems.First().Order.OrderStatus.ToString() : OrderStatus.NotCheckedOut.ToString()
select new Listing
{
ID = listings.ID,
//etc
OrderStatus = (OrderStatus)Enum.Parse(typeof(OrderStatus), ordS)
};

Related

Unable to create a null constant value of type XX.XX Only entity types, enumeration types or primitive types are supported in this context

I am using EF. A parent table CUSTOMER and a relational table CUSTOMER_PAYMENT_TERM.
I want to get result with following approach.
var result = (from x in EntitiesContext.CUSTOMERS
select new
{
CustomerId = x.CUSTOMERID,
PaymentTerms = x.PAYMENTTERMID > 0 ?
new PaymentTermBo
{
PaymentTermId = (decimal)x.PAYMENTTERMID,
Discount = (decimal)x.CUSTOMER_PAYMENT_TERM.DISCOUNT
}
: null,
}).ToList();
I got following error for above code.
Unable to create a null constant value of type PaymentTermBo Only entity types, enumeration types or primitive types are supported in this context.
I really want to use this approach "Want to put if condition for object to fill".
But when use following approach, it works.
var result = (from x in EntitiesContext.CUSTOMERS
select new
{
CustomerId = x.CUSTOMERID,
PaymentTerms = new PaymentTermBo
{
PaymentTermId = x.PAYMENTTERMID > 0 ? (decimal)x.PAYMENTTERMID : 0,
Discount = x.PAYMENTTERMID > 0 ? (decimal)x.CUSTOMER_PAYMENT_TERM.DISCOUNT : 0
}
}).ToList();
But i want to use first approach as i have complex tables and many columns, do not want to fill all the values by putting if condition for each value as it will slow down the query.

Linq query parameterised operator and column name [duplicate]

This question already has answers here:
C# Dynamic database filtering with Linq Expression
(3 answers)
Closed 5 months ago.
I am looking to implement a system whereby a use that 'build' conditions and then return the resulting data back from the database. At present, there is a stored procedure which generates SQL on the fly and executes it. This is a particular issue that I want to remove.
My problem is coming from the fact that I can have multiple fields within my criteria, and for each of these fields, there could be 1 or more values, with different potential operators.
For example,
from t in Contacts
where t.Email == "email#domain.com" || t.Email.Contains ("mydomain")
where t.Field1 == "valuewewant"
where t.Field2 != "valuewedontwant"
select t
The field, criteria and operator are stored in the database (and List<FieldCriteria>) and would be some thing like this (based on above);
Email, Equals, "email#domain.com"
Email, Contains, "mydomain" Field1,
Equals, "valuewewant" Field2,
DoesNotEqual, "valuewedontwant"
or
new FieldCriteria
{
FieldName = "Email",
Operator = 1,
Value = "email#mydomain.com"
}
So using the information that I have, I want to be able to build a query with any number of conditions. I have seen previous links to Dynamic Linq and PredicateBuilder, but am not able to visualise this as a solution to my own problem.
Any suggestions would be appreciated.
Update
Following on from the suggestion about Dynamic Linq, I came up with a very basic solution, using a Single Operator, with 2 Fields and multiple Criteria. A little crude at the moment as coded in LinqPad, but the results are exactly what I wanted;
enum Operator
{
Equals = 1,
}
class Condition
{
public string Field { get; set; }
public Operator Operator { get; set;}
public string Value { get; set;}
}
void Main()
{
var conditions = new List<Condition>();
conditions.Add(new Condition {
Field = "Email",
Operator = Operator.Equals,
Value = "email1#domain.com"
});
conditions.Add(new Condition {
Field = "Email",
Operator = Operator.Equals,
Value = "email2#domain.com"
});
conditions.Add(new Condition {
Field = "Field1",
Operator = Operator.Equals,
Value = "Chris"
});
var statusConditions = "Status = 1";
var emailConditions = from c in conditions where c.Field == "Email" select c;
var field1Conditions = from c in conditions where c.Field == "Field1" select c;
var emailConditionsFormatted = from c in emailConditions select string.Format("Email=\"{0}\"", c.Value);
var field1ConditionsFormatted = from c in field1Conditions select string.Format("Field1=\"{0}\"", c.Value);
string[] conditionsArray = emailConditionsFormatted.ToArray();
var emailConditionsJoined = string.Join("||", conditionsArray);
Console.WriteLine(String.Format("Formatted Condition For Email: {0}",emailConditionsJoined));
conditionsArray = field1ConditionsFormatted.ToArray();
var field1ConditionsJoined = string.Join("||", conditionsArray);
Console.WriteLine(String.Format("Formatted Condition For Field1: {0}",field1ConditionsJoined));
IQueryable results = ContactView.Where(statusConditions);
if (emailConditions != null)
{
results = results.Where(emailConditionsJoined);
}
if (field1Conditions != null)
{
results = results.Where(field1ConditionsJoined);
}
results = results.Select("id");
foreach (int id in results)
{
Console.WriteLine(id.ToString());
}
}
With an SQL generated of;
-- Region Parameters
DECLARE #p0 VarChar(1000) = 'Chris'
DECLARE #p1 VarChar(1000) = 'email1#domain.com'
DECLARE #p2 VarChar(1000) = 'email2#domain.com'
DECLARE #p3 Int = 1
-- EndRegion
SELECT [t0].[id]
FROM [Contacts].[ContactView] AS [t0]
WHERE ([t0].[field1] = #p0) AND (([t0].[email] = #p1) OR ([t0].[email] = #p2)) AND ([t0].[status] = #p3)
And Console Output:
Formatted Condition For Email: Email="email1#domain.com"||Email="email2#domain.com"
Formatted Condition For Field1: Field1="Chris"
Just need clean this up and add the other Operators and it is looking good.
If anyone has any comments on this so far, any input would be appreciated
The trick with LINQ would be to build an Expression from the data. As an example, to illustrate the example shown:
var param = Expression.Parameter(typeof(MyObject), "t");
var body = Expression.Or(
Expression.Equal(Expression.PropertyOrField(param, "Email"), Expression.Constant("email#domain.com")),
Expression.Call(Expression.PropertyOrField(param, "Email"), "Contains", null, Expression.Constant("mydomain"))
);
body = Expression.AndAlso(body, Expression.Equal(Expression.PropertyOrField(param, "Field1"), Expression.Constant("valuewewant")));
body = Expression.AndAlso(body, Expression.NotEqual(Expression.PropertyOrField(param, "Field2"), Expression.Constant("valuewedontwant")));
var lambda = Expression.Lambda<Func<MyObject, bool>>(body, param);
var data = source.Where(lambda);
In particular, note how AndAlso can be used to compose the various operations (the same as multiple Where, but simpler).
I think Dynamic LINQ will be one of option. DLINQ allows you to specify part of the LINQ query as "string" and DLINQ then compiles that string to Expression tree so that be passed to the underlying LINQ provider. Your need is also same i.e you need to create Expression trees at runtime.
I would suggest you to make the property Operator in FieldCriteria as an Enum which represent all the required operations (equals, less then etc). Then you will need to write a function that takes a list of FieldCriteria and return a "expression" string which then can be fed into DLINQ to get the expression tree.
This sounds very similar to a problem I solved recently. In my case, I had to filter the objects into different categories based on a complex filters that were defined in Sql.
I have created a Nuget package DynamicFilter.Sql to dynamically generate the lambda expression from a sql based filter. The package is open source and available on github.
you can simply use it like so,
var filter = FilterExpression.Compile<User>("(Email = 'email#domain.com' or Email like '%#%mydomain.com') and deleted <> true ");
bool match = filter(new User {Email="alice#mydomain.com", Deleted=false}); //Matches true
This can be simply done by Linq where you attach additional operators to the query object. Here is an example.
query = db.Contacts.Where( ... );
query = query.Where( ... );
query = query.Where( ... );
This is a more simpler and short solution.

LINQ and nullable parameter

I have a nullable field in DB and nullable parameter in model. I try the following LINQ to Entities query:
EditPersonViewModel model = (from i in db.Person
where i.PersonID == id.Value
select new EditPersonViewModel()
{
PersonID = i.PersonID,
Fullname = i.Fullname,
Comment = i.Comment,
Username = (i.UserId != null) ? i.AspNetUsers.UserName : String.Empty,
// this is parameter has type "int?"
PersonStatusID = (i.PersonStatus!=null) ? i.PersonStatus.PersonStatusID : null
}).FirstOrDefault();
I get the compilation error:
Error 1 Type of conditional expression cannot be determined because
there is no implicit conversion between 'int' and ''
it works fine with
Username = (i.UserId != null) ? i.AspNetUsers.UserName : String.Empty,
but does not work with "int?" type. Why and how to do it correctly?
Documentation of Conditional Operator says:-
Either the type of first_expression and second_expression must be the
same, or an implicit conversion must exist from one type to the other.
Since i.AspNetUsers.UserName & String.Empty are string types its working fine for you. Now, your problem is self explanatory because null cannot be casted to integer type, you need this instead:-
PersonStatusID = (i.PersonStatus!=null) ? i.PersonStatus.PersonStatusID : 0;
Or the other way around if you need that as Nullable of integer:-
PersonStatusID = (i.PersonStatus!=null) ? (int?)i.PersonStatus.PersonStatusID : null;
PersonStatusID should be of type int? in this case.

error in Linq query

I have following Linq:
var ownerRegistryId = 731752693037116688;
var excludeTypes = new[]
{
"CA00", "CA01", "CA03", "CA04", "CA02",
"PA00", "PA01", "PA02", "PA03", "PA04"
};
var maxStateChangeMonth = 4;
var excludeStatusId = 999;
var includeMortgage = new[] { "CL10", "CL11", "PL10", "PL11" };
var sum = (
from account in context.Accounts
from owner in account.AccountOwners
where owner.AccountOwnerRegistryId == ownerRegistryId
where !excludeTypes.Contains(account.AccountType)
where account.StateChangeDate == null ||
(account.StateChangeDate.Month - DateTime.Now.Month)
<= maxStateChangeMonth
where includeMortgage.Contains(account.AccountType) ||
account.AccountType.Contains("Mortgage")
where account.AccountStatusId != excludeStatusId
select account.MinimumInstallment)
.Sum(minimumInstallment => Math.Abs(minimumInstallment));
but I get the error:
The cast to value type 'Decimal'
failed because the materialized value
is null. Either the result type's
generic parameter or the query must
use a nullable type.
this error comes as soon as I add this:
where (includeMortgage.Contains(account.AccountType) ||
account.AccountType.Contains("Mortgage"))
If I remove this from above query, it works.
The query is translation of following SQL:
SELECT Sum(ABS([Minimum Installment])) AS SumOfMonthlyPayments FROM tblAccount
INNER JOIN tblAccountOwner ON tblAccount.[Creditor Registry ID] = tblAccountOwner.
[Creditor Registry ID] AND tblAccount.[Account No] = tblAccountOwner.[Account No]
WHERE (tblAccountOwner.[Account Owner Registry ID] = 731752693037116688)
AND (tblAccount.[Account Type] NOT IN
('CA00', 'CA01', 'CA03', 'CA04', 'CA02', 'PA00', 'PA01', 'PA02', 'PA03', 'PA04'))
AND (DATEDIFF(mm, tblAccount.[State Change Date], GETDATE()) <=
4 OR tblAccount.[State Change Date] IS NULL AND ((tblAccount.[Account Type] IN ('CL10','CL11','PL10','PL11')) OR
tblAccount.[Account Type] LIKE 'Mortgage')) AND (tblAccount.[Account Status ID] <> 999)
I'd try to rewrite the last two lines of your query like so:
var sum = (
...
select account)
.Sum(a => Math.Abs(a.MinimumInstallment));
That's how I interprete this part of the exception "...or the query must use a nullable type". By using the projection select account.MinimumInstallment you have a non-nullable type, namely decimal which is the type of account.MinimumInstallment.
Not sure though, just a guess.
Edit
The problem might actually be the final assignment var sum = .... Since you don't specify the result type explicitely the compiler will here infer the type to decimal because MinimumInstallment is decimal. The query can actually return null when the selected recordset was empty so the cast to decimal is impossible.
So, let's help the compiler to infer the result type of the query to decimal?:
var sum = (decimal?)(from ... ) ?? 0;
(Replace from ... by your original query or maybe by my modified version above.)
Edit 2
OK, the first Edit didn't work (according to comment in another question). Indeed I could reproduce the issue in a similar example. But the following worked in my example:
var sum = (
...
select account)
.Sum(a => (decimal?)Math.Abs(a.MinimumInstallment))
.GetDefaultOrValue();
Try using:
Math.Abs((decimal)(minimumInstallment.HasValue ? minimumInstallment : 0));
How about:
Math.Abs((decimal)(minimumInstallment!= null ? minimumInstallment : 0));

Problem with LINQ to Entities query using Sum on child object property

Given this query:
from s in services
select new
{
s.Id,
s.DateTime,
Class = s.Class.Name,
s.Location,
s.Price,
HeadCount = s.Reservations.Sum(r => r.PartySize), // problem here. r.PartySize is int
s.MaxSeats
}
If the service doesn't have any reservations, this exception is thrown:
System.InvalidOperationException: The cast to value type 'Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
I get it, but how should I deal with it? My intention is if there are no reservations, then HeadCount be assigned 0.
There's an even simpler solution:
from s in services
select new
{
s.Id,
s.DateTime,
Class = s.Class.Name,
s.Location,
s.Price,
HeadCount = (int?)s.Reservations.Sum(r => r.PartySize),
s.MaxSeats
}
Note the cast. This may also produce simpler SQL than #Ahmad's suggestion.
Essentially, you're just helping out type inference.
You should check for it:
HeadCount = s.Reservations != null ? s.Reservations.Sum(r => r.PartySize) : 0,
This should resolve your problem:
Try to cost the int to int?
from s in services
select new
{
s.Id,
s.DateTime,
Class = s.Class.Name,
s.Location,
s.Price,
HeadCount = s.Reservations.Sum(r => (int?) r.PartySize),
s.MaxSeats
};
HeadCount = HeadCount ?? 0;
A simple ternary operator should fix the problem nicely...
something like this:
HeadCount = (s.Reservations != null && s.Reservations.Any()) ? s.Reservations.Sum(r => r.PartySize) : 0;
This will handle for both null and empty situations

Categories