LINQ won't let me create a custom attribute (like Id + Name) - c#

What I'm trying to do is to create a custom type, with a custom attribute that will store both Id and Name from a record. (Something like "223 - Robert Smith"). This is what I'm doing:
return (from c in db.Credores
where c.Status == true
select new CredorCompleto
{
Id = c.Id,
Nome = c.Nome,
NomeCompleto = c.Id + c.Nome,
CNPJ = c.CNPJ
}).ToList();
Update: Definition for 'CredorCompleto'
public class CredorCompleto
{
public string NomeCompleto { get; set; }
public string CNPJ { get; set; }
public string Nome { get; set; }
public int Id { get; set; }
}
This is what I'm getting:
Unable to cast the type System.Int32 to type System.Object. LINQ to Entities only supports casting Entity Data Model primitive types.

Your comment on #Moon's answer provides an important clue:
"LINQ to Entities does not recognize the method System.String Format(System.String, System.Object, System.Object) method, and this method cannot be translated into a store expression."
The problem might be that db.Credores is an IQueryable, and not just an IEnumerable. So when your LINQ to SQL provider tries to analyse your original query, it comes upon a bit that it does not recognise, and does not know how to translate to a SQL query.
I suppose the LINQ to SQL provider has problems converting your concatenation c.Id + c.Nome into a valid SQL statement, possibly because the former is an int and the latter a string.
What's for sure is that it definitely doesn't know how to transform a call to string.Format() to SQL (which is not surprising, since SQL doesn't have that function).
So you could try to execute the SQL query before you perform .NET-specific logic on it. Try this:
return db
.Credores
.Where(c => c.Status == true)
.AsEnumerable() // <-- this should trigger the execution of the SQL query
.ToList() // <-- and if it does not, then this certainly will
.Select(c => new CredorCompleto
{
…
})
.ToList();
The call to .AsEnumerable() — and a call to .ToList() is probably required also, IIRC — will trigger the execution of the SQL query. Everything after that operates on an in-memory IEnumerable, not on a IQueryable. This means that after the .ToList(), LINQ will do no more smart code analysis, or attempt to transform the remaining operators to SQL.

Assuming .ToString() of c.Id and c.Nome returns proper value, you can do this:
return (from c in db.Credores
where c.Status == true
select c).AsEnumerable()
.select(x => new CredorCompleto()
{
Id = c.Id.ToString(),
Nome = c.Nome,
NomeCompleto = string.Format("{0} - {1}", c.Id, c.Nome),
CNPJ = c.CNPJ
}).ToList();
As suggested by Victor, "LINQ to Entities does not recognize the method System.String Format(System.String, System.Object, System.Object)"
For this reason, use AsEnumerable()to force evaluation of that part with Linq to Objects.

Your trying to concatenate an int and a string. Try...
NomeCompleto = c.Id.ToString() + " - " + c.Nome,

You asked
Something like "223 - Robert Smith").
Use this
return (from c in db.Credores
where c.Status == true
select new CredorCompleto
{
Id = c.Id,
Nome = c.Nome,
NomeCompleto = c.Id + " - " + c.Nome,
CNPJ = c.CNPJ
}).ToList();
EDIT: After seeing your class structure I guess your error is some where else which is not given in question.

try
return (from c in db.Credores
where c.Status == true
select new CredorCompleto
{
Id = c.Id,
Nome = c.Nome,
DescricaoCompleta = c.Id+"-"+c.Nome,
CNPJ = c.CNPJ
}).ToList();

Related

L2S System.DateTime has no supported translation to SQL

My Data Service is has method of type IQueryable and my controller is trying to convert the date time but I am getting this error. Any help would me great.
Error
Method 'System.String ToCommonDateString(System.Nullable`1[System.DateTime])' has no supported translation to SQL.
data Service
public IQueryable<TemplatesJoinAgent> GetTemplateAgentKeyDiseaseId(Guid? agentKey, Guid? diseaseId)
{
//Common part
var TemplatesJoinAgent = (from t in UnitOfWork.GetRepository<Template>().Get(t => t.IsCurrentVersion && t.Status == (short)TemplateMode.Published)
join r in UnitOfWork.GetRepository<Regimen>().Get() on t.Id equals r.TemplateId
join rp in UnitOfWork.GetRepository<RegimenPart>().Get() on r.Id equals rp.RegimenId
join re in UnitOfWork.GetRepository<RegimenEntry>().Get() on rp.Id equals re.RegimenPartId
join a in UnitOfWork.GetRepository<Agent>().Get() on re.AgentVersionKey equals a.VersionKey
select new TemplatesJoinAgent
{
TemplateId = t.TemplateId,
TemplateTitle = t.Title,
GroupTitle = t.GroupTitle,
GuideLineTitle = t.GuideLineTitle,
ExternalDiseaseId = t.ExternalDiseaseId,
DiseaseName = t.DiseaseName,
VersionKey = t.VersionKey,
AgentRxNormTallMan = a.RxNormTallMan,
AgentNccnTallMan = a.NccnTallMan,
AgentName = a.Name,
AgentVersionKey = a.VersionKey,
Added = t.Added,
Modified = t.Modified,
Indication = t.Indications,
});
TemplatesJoinAgent = TemplatesJoinAgent.Distinct();
return TemplatesJoin
}
controller
PublishedDate = (t.Modified ?? t.Added).ToCommonDateString(),
public static string ToCommonDateString(this DateTime? d)
{
return (d.HasValue ? d.Value.ToCommonDateString() : "N/A");
}
The engine does not know how to translate your custom function to SQL. The simplest way to get around that is to add an AsEnumerable() before your projection that uses the custom function. That changes the context from SQL to in-memory and allows custom functions.
The risk is that you want to make sure you have executed as many of your filters as you can before calling AsEnumerable(), otherwise you'll be pulling back more data than you need and filtering in-memory. Of course, if your filters require custom functions then you'll either have to accept that or change your filter to be SQL-compatible.

Convert C# Entity Framework linq query to SQL

I am converting the C# linq query to SQL, LINQ never return the values.
But the same query I wrote in SQL returns some values. Can anyone help me find out what the issue is for my SQL query?
LINQ query:
var query = from l in _DbContext.Licenses
join lp in _DbContext.LicenseParts on l.PartNumber equals lp.PartNumber
join lpc in _DbContext.LicensePartConfigurations on lp.Id equals lpc.LicensePartId
join p in _DbContext.Products on lp.ProductId equals p.Id
join lsn in _DbContext.LicenseSerialNumbers on l.Id equals lsn.LicenseId
join lact in _DbContext.LicenseActivations on new { a = lsn.Id, b = lp.ProductId } equals new { a = lact.LicenseSerialNumberId, b = lact.ProductId }
where lact.AccountId == AccountId && JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.SubscriptionKey") !=
" " && (JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.IsConverted") == null || JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.IsConverted") == "0" || JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.IsConverted") == "false") && p.Name == "ClearPass Legacy"
select new SubscriptionKeys { SubscriptionKey = JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.SubscriptionKey"), CustomerMail = JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.CustomerMail"), CustomerName = JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.CustomerName") };
response.PageSize = pageSize;
response.PageNumber = pageNumber;
response.Model = await query.Distinct().ToListAsync();
response.ItemsCount = response.Model.Count();
SQL query:
SELECT
l.AccountId,CustomerMail,
JSON_VALUE(ActivationInfo, '$.SubscriptionKey')
FROM
Licenses l
JOIN
LicenseParts lp ON l.PartNumber = lp.PartNumber
JOIN
LicensePartConfigurations lpc ON lp.Id = lpc.LicensePartId
JOIN
Products p ON lp.ProductId = p.Id
JOIN
LicenseSerialNumbers lsn ON l.Id = lsn.LicenseId
JOIN
LicenseActivations lact ON lsn.Id = lact.LicenseSerialNumberId
AND lp.ProductId = lact.ProductId
WHERE
lact.AccountId = 'QWNjb3VudDoxNTMwNDAzMi00MWM2LTExZTktOWYzMy1kMzQxZjE5OWZlYjM='
AND JSON_VALUE(lact.ActivationInfo, '$.SubscriptionKey') != ' '
AND (JSON_VALUE(lact.ActivationInfo, '$.IsConverted') = NULL OR
JSON_VALUE(lact.ActivationInfo, '$.IsConverted') = 0 OR
JSON_VALUE(lact.ActivationInfo, '$.IsConverted') = 'false')
AND p.Name = 'ClearPass Legacy'
To start from a valid point, execute the code where it fires this linq query and use sql profiler to catch it up. That is a good way to find the exact equivalent sql statement it finally produces and executes. You need to set up a trace to sql profiler prior to execute the linq. Get the statement and then you can compare with the sql you already have.
this sql:
(JSON_VALUE(lact.ActivationInfo, '$.IsConverted') = NULL
is not equal to:
(JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.IsConverted") == null
as in first case you compare to database NULL value using '=' and this requires ansi_nulls off to work properly and it is not a good practice.
ORMs like EF Core are meant to Map Objects to Relational constructs. Instead of trying to write SQL in C# through LINQ, you should try to Map the attributes you need to entity properties.
In this case the SubscriptionKey and IsConverted fields should appear in the table itself, either as proper fields or computed columns. If that's not possible, you could use computed columns to map the SubscriptionKey and IsConverted attributes to entity properties so you can use them in queries.
In your LicenseActivation class add these properties:
public bool? IsConverted {get;set;}
public string? SubscriptionKey {get;set;}
public string? CustomerEmail {get;set;}
In your DbContext, you can specify computed columns with HasComputedColumnSql:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<LicenseActivation>()
.Property(b => b.SubscriptionKey)
.HasComputedColumnSql("JSON_VALUE(ActivationInfo, '$.SubscriptionKey')");
modelBuilder.Entity< LicenseActivations >()
.Property(b => b.IsConverted)
.HasComputedColumnSql("JSON_VALUE(ActivationInfo, '$.IsConverted')");
modelBuilder.Entity< LicenseActivations >()
.Property(b => b.CustomerEmail)
.HasComputedColumnSql("JSON_VALUE(ActivationInfo, '$.CustomerEmail')");
}
This will allow you to use the properties in LINQ queries.
You shouldn't have to use all those JOINs either. It's the ORM's job to generate JOINs from the relations between objects. If you add proper relations between the entities the query could be simplified to :
var binValue='QWNjb3VudDoxNTMwNDAzMi00MWM2LTExZTktOWYzMy1kMzQxZjE5OWZlYjM=';
var query=_dbContext.LicenseActivations
.Where(lact=>
lact.AccountId == binValue
&& (lact.IsConverted==null || !lact.IsConverted))
.Select(lact=>new {
lact.AccountId,
lact.SubscriptionKey,
lact.CustomerEmail});
or, if the AccountId fields don't hold the same data :
.Select(lact=>new {
AccountId =lact.LicensePart.License.AccountId,
lact.SubscriptionKey,
lact.CustomerEmail
});
EF Core will generate the appropriate SQL and JOIN clauses to get from LicenseActivation to License based on the relations between the entities

How to count the number of items that exist in a table efficiently with EntityFramework?

I have a C# project in which several items are stored in different tables, for example to count how many elements a table contains I do something similar to the following:
public int getLengthListProducts(int idCompany)
{
try
{
using (var context = new ccoFinalEntities())
{
return context.products.Where(p => true == p.status && idCompany == p.idCompany).ToList().Count;
}
}
catch
{
return -1;
}
}
So far it has worked very well, but when the amount starts to be 1000 items, on some PCs it starts to take a while to get this number.
I suspect that context.products places all items in RAM and then begins to extract and count those that meet the following conditions and that is why the application is frozen until the count is finished.
My question is: Is there a way to do it better?
For example I thought that I should resort directly to SQL statements instead of using EntityFramework to get that number, but I don't know if that would be a good idea, or if there is a more efficient way with EntityFramework.
Any comments or suggestions are welcome.
context.products.Where(p => true == p.status && idCompany == p.idCompany).ToList().Count;
In this Linq query, ToList() will generate the SQL Query :
SELECT ...
FROM Products
WHERE status = 1 and idCompagny = #idCompany
This query is executed on your database and can return a lot of rows.
All elements is loaded in client's memory in .Net Collection and Count return final result.
With Entity Framework, you can use aggregate Linq query (Count, Sum, Avg, ...) :
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/linq/aggregate-queries
Example :
context.products.Where(p => true == p.status && idCompany == p.idCompany).Count();
Count() will generate the SQL Query :
SELECT COUNT(*)
FROM Products
WHERE status = 1 and idCompagny = #idCompany
The query is executed on your database and return scalar result.
context.products.Count(p => p.status == true && idCompany == p.idCompany);
or
context.products.Where(p => p.idCompany == idCompany)
.Count(p => p.status == true);
(for readability)
will be enough.
For SQL-Server you can do this
string cmd = #"SELECT
t.NAME AS TableName,
s.Name AS SchemaName,
p.rows AS RowCounts,
SUM(a.total_pages) * 8 AS TotalSpaceKB,
SUM(a.used_pages) * 8 AS UsedSpaceKB,
(SUM(a.total_pages) - SUM(a.used_pages)) * 8 AS UnusedSpaceKB
FROM
sys.tables t
INNER JOIN
sys.indexes i ON t.OBJECT_ID = i.object_id
INNER JOIN
sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN
sys.allocation_units a ON p.partition_id = a.container_id
LEFT OUTER JOIN
sys.schemas s ON t.schema_id = s.schema_id
WHERE
t.NAME NOT LIKE 'dt%'
AND t.is_ms_shipped = 0
AND i.OBJECT_ID > 255
GROUP BY
t.Name, s.Name, p.Rows
"
db.Database.SqlQuery(cmd).ToList<Statistic>();
with
public class Statistic
{
public string TableName { get; set; }
public string SchemaName { get; set; }
public long RowCounts { get; set; }
public long TotalSpaceKB { get; set; }
public long UsedSpaceKB { get; set; }
public long UnusedSpaceKB { get; set; }
}
Then you get a pretty fast overview of all your tables, here including size in memory.
I know hard coded SQL ist not to be prefered, but since its relating to System-Tables only, and wherefore you can assume, they never change, it's acceptable.

Can't reference variable from anonymous type

I am grabbing a value and want it to appear in the BatchId of every anonymous type created via a linq statement.
Here is the code:
var batchId = context.Request["batchid"];
using (var db = new StarterSiteEntities())
{ // Get data
var transactions = (from t in db.Transactions
join td in db.TransactionDetails on t.TransactionID equals td.TransactionID
join p in db.Products on td.ProductID equals p.ProductID
where t.Exported == false
select new
{
BatchId = batchId,
t.FirstName,
t.LastName,
t.Address1,
t.Address2,
t.City,
t.State,
t.Zip_Code,
t.Email,
t.Phone,
t.TotalAmount,
t.MonthlyGift,
t.DateCreated,
p.Fund,
ProductFirstName = p.FirstName,
ProductLastName = p.LastName,
ProductUniversity = p.University,
ProductState = p.State,
ProductEmail = p.Email,
ProductAmount = td.Amount
}).ToList();
}
When I do this, I get the error message:
"A parameter is not allowed in this location. Ensure that the '#' sign is in a valid location or that parameters are valid at all in this SQL statement."
How do I reference the batchId variable from within the anonymous type declaration, or should I accomplish this another way?
It looks like you ran into a known bug in the SQL Server CE data access libraries. You should be able to fix it by applying this hotfix to the machine(s) that are accessing the database.
While I think Adam Maras answered my question. Because I did not want to install a hot-fix on the server, I ended up solving the problem using a different method.
Since the Linq query would not allow me to use a string variable and I could not edit the property value of an anonymous type. I stopped using an anonymous type and created an entity class to hold my "transaction summary" data.
Once I have a collection of TransactionSummary objects, I can use the Select() method to update the BatchId property value in each record.
Here is the resulting code:
// Define a custom type to hold the data
private class TransactionSummary
{
public string BatchId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
//...removed lines for brevity...
}
//...here is the updated code snippet...
using (var db = new StarterSiteEntities())
{ // Get data
var transactions = (from t in db.Transactions
join td in db.TransactionDetails on t.TransactionID equals td.TransactionID
join p in db.Products on td.ProductID equals p.ProductID
where t.Exported == false
select new TransactionSummary
{
FirstName = t.FirstName,
LastName = t.LastName,
//...removed lines for brevity...
}).ToList();
// The client would like a batchID added to each record that we return.
var batchId = context.Request["batchid"];
transactions.Select(t => { t.BatchId = batchId; return t; }).ToList();
}

LINQ cast int64 issue

I need this linq query to work but linq is complaining about customercontact and phone being int64s and I also need to concat the second column but I'm afraid that isn't working for the same reason. If I add a tostring() it just says linq doesn't recognize it.
base {System.SystemException} = {"Unable to cast the type
'System.Int64' to type 'System.Object'. LINQ to Entities only supports
casting EDM primitive or enumeration types."}
var tempCustomers =
from c in db.Customers
let cc = db.CustomerContacts.FirstOrDefault(x => x.CustomerID == c.CustomerID)
select new{cc.CustomerContactID, CustomerValue = c.CustomerName + "   " + cc.Phone};
This error is coming from LINQ to entities. Here is one solution:
var tempCustomers =
from c in db.Customers.ToArray()
let cc = db.CustomerContacts
.FirstOrDefault(x => x.CustomerID == c.CustomerID)
select new
{
cc.CustomerContactID,
CustomerValue = string.Format("{0}   {0}",
c.CustomerName, cc.Phone)
};
The above will hit the database before it tries to do the string concatenation. If that is not acceptable, please note so in your question.
Why it's not working
LINQ to Entities uses deferred SQL execution, meaning that your LINQ query will not hit the database until you iterate over the IQueryable using a foreach, or call a method like ToList or ToArray on the IQueryable. You can use any code you want inside a LINQ predicate expression, but it will fail at runtime if LINQ to Entities can't figure out how to translate it into SQL. Your code is failing because LINQ to Entities can't figure out how to concatenate CustomerName, your custom string, and the PhoneNumber while running the SQL query. The above works because it gets the data from the database first and then does the string concatenation in memory.
Update
To expand on the better solution which #JeffMercado beat me to, you really should be using a navigation property to join Customer and CustomerContacts. That would eliminate the need for the let clause and the First or FirstOrDefault call:
public class Customer
{
public long CustomerID { get; set; }
public string CustomerName { get; set; }
public virtual ICollection<CustomerContact> Contacts { get; set; }
}
public class CustomerContact
{
public long CustomerContactID { get; set; }
public long CustomerID { get; set; }
public virtual Customer Owner { get; set; }
public long Phone { get; set; } // I agree this should be a string
}
You should then be able to query out data like this:
var query = db.CustomerContacts
.Include(x => x.Owner) // eager load to avoid multiple separate SQL queries
.Select(x => new {
CustomerContactID = x.CustomerContactID,
CustomerName = x.Owner.CustomerName,
Phone = x.Phone,
});
From here, you can use AsEnumerable, ToArray, or ToList to execute the query and format your special CustomerValue property.
var results = query
.ToArray() // or .AsEnumerable(), or .ToList(), all will execute the SQL query
.Select(x => new {
CustomerContactId = x.CustomerContactID,
CustomerValue = string.Format("{0}   {1}",
x.CustomerName, x.Phone)
});
The kinds of operations you can perform within a query are limited in EF, conversions are one of them. You need to move that part out of the query just getting the data then use AsEnumerable() so you're working with LINQ to Objects. Then you can do whatever you want with the data.
var query=
from c in db.Customers
let cc = c.CustomerContacts.FirstOrDefault() // better to use the navigation properties instead
select new // select the fields you need
{
cc.CustomerContactId,
c.CustomerName,
Phone = (long?)cc.Phone, // cc could potentially be null this long must also be nullable
};
var tempCustomers =
from x in query.AsEnumerable() // LINQ to Objects
select new // now you can do what you want to the data
{
x.CustomerContactId,
CustomerValue = x.CustomerName + "   " + x.Phone,
};
I broke in out into separate statements for readability but you could combine them if you'd like.

Categories

Resources