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.
Related
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
I have a function to get data from my database with join on it. I want to take data from different tables, how can I achieve this? I want to take "libelle_motif" which is from the table "motif_deplacement"
My function right now:
public static List<personne> getPersonne_Deplacement(int numDeplacement)
{
List<personne> desP = new List<personne>();
var query = (from Per in db.personne.ToList()
join Dep in db.deplacement_personne.ToList() on Per.num_personne equals Dep.num_personne
join Mot in db.motif_deplacement.ToList() on Dep.id_motif equals Mot.id_motif
where Dep.id_deplacement == numDeplacement
select new personne
{
nom_personne = Per.nom_personne,
num_personne = Per.num_personne,
ref_personne = Per.ref_personne,
libelle_motif = Mot.libelle_motif,
});
desP = query.ToList();
return desP;
}
And this is how my database looks like :
You will have to create a new class which will act as the model with the properties you want. Construct one of those in your select based on the included relationships.
Also it much easier to manage this query if you model your relationships in your EF entities as opposed to writing out join statements in every query.
Also notice that I removed all the calls to ToList. Your previous code was materializing all entities from each table and then joining and filtering in memory which is extremely inefficient.
public static List<SomeModel> getPersonne_Deplacement(int numDeplacement)
{
var query = from Per in db.personne
join Dep in db.deplacement_personne on Per.num_personne equals Dep.num_personne
join Mot in db.motif_deplacement on Dep.id_motif equals Mot.id_motif
where Dep.id_deplacement == numDeplacement
select new SomeModel
{
nom_personne = Per.nom_personne,
num_personne = Per.num_personne,
ref_personne = Per.ref_personne,
libelle_motif = Mot.libelle_motif,
};
return query.ToList();
}
SomeModel.cs
public class SomeModel
{
public string nom_personne {get;set;}
public string num_personne {get;set;}
public string ref_personne {get;set;}
public string libelle_motif {get;set;}
// add additional properties as needed
}
I don't know if my answer is related to your question. But why don't you use .Include()
Ex:
var result = db.personne.Include(x => x.deplacement_personne).ThenInclude(x => x.motif_deplacement)
It will give you this structure:
obj personne { nom_personne, ... , obj deplacement_personne { ... , obj motif_deplacement }
You can also select any cols with .Select() , or filter it with .Where().
I have the enumeration:
public enum CmdType {
[Display(Name = "abc")]
AbcEnumIdentifier = 0,
[Display(Name = "xyz")]
XyzEnumIdentifier = 1,
...
}
I'd like to get the names of each enumeration into my query, but even using .WithTranslations() I'm getting this error:
LINQ to Entities does not recognize the method 'System.String
GetName(System.Type, System.Object)' method, and this method cannot be
translated into a store expression.
The query:
var joinedRecord =
(
from m in mTable
join b in bTable on m.Id equals b.aRefId
select new {
aId = a.Id,
aAttrib1 = a.Attrib1
...
bCmdType = Enum.GetName(typeof(CmdType), b.CmdType)
}
).WithTranslations();
How do I return a generated value using Enum.GetName(...) within the query?
LINQ to entities tries to translate your query to SQL and it fails to do so, because there is no equivalent of the Enum.GetName method in SQL.
You need to materialize the results of the query and convert the enum values to their name in the memory.
var joinedRecords = (
from m in mTable
join b in bTable on m.Id equals b.aRefId
select new {
aId = a.Id,
aAttrib1 = a.Attrib1
...
bCmdType = b.CmdType
}
).AsEnumerable() //Executes the query, further you have simple CLR objects
.Select(o => new {
aId = o.Id,
aAttrib1 = o.Attrib1
...
bCmdTypeName = Enum.GetName(typeof(CmdType), o.CmdType)
});
You are calling Enum.GetName(typeof(CmdType), b.CmdType) which cannot be translated to SQL as the Enum definition is not in the DB if you take a look at your rows you'll see there is an int instead of the name of the Enum value in question.
Try this:
var joinedRecord =
(
from m in mTable
join b in bTable on m.Id equals b.aRefId
select new {
aId = a.Id,
aAttrib1 = a.Attrib1
...
bCmdType = b.CmdType
}
)
.AsEnumerable() // or ToList()
.Select( // map to another type calling Enum.GetName(typeof(CmdType), b.CmdType) )
.WithTranslations();
What this does is that by calling AsEnumerable() or ToList() you are no longer processing an instance of IQueryable<T> (which is what your original query returns, on the bad side once you do this all returned objects will be on memory). So once you have objects in memory you can use them just as any other C# object which should allow you to use the methods you want.
Try casting to AsEnumerable() so you can use LINQ to Objects. LINQ to Entities will try to translate it to SQL for which there is no equivalent:
var joinedRecord =
(from m in mTable
join b in bTable on m.Id equals b.aRefId)
.AsEnumerable()
.Select(x => new {
aId = a.Id,
aAttrib1 = a.Attrib1
...
bCmdType = Enum.GetName(typeof(CmdType), b.CmdType)
})
.WithTranslations();
See http://www.lavinski.me/ef-linq-as-emumerable/
I have writting a LINQ query to fill a listview but it useses the .ToString() method which apparetly is not allowed. When I use the below code I get the error message:
Error: LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression
Is there a way to use the ToString() in LINQ or if that is not possible what is the solution to converting a DateTime to String in the query. Please knot that ReleaseDateName is a string and ReleaseDate is a DateTime
using (var db = new ReleaseInfo())
{
lvReleaseInfo.DataSource = (from r in db.MediaReleases
join rn in db.ReleaseNames
on new { MediaReleaseID = r.MediaReleaseID, CultureCodeID } equals new { rn.MediaReleaseID, rn.CultureCodeID }
join plat in db.MediaPlatforms
on new { MediaPlatformID = r.MediaPlatformID, CultureCodeID } equals new { plat.MediaPlatformID, plat.CultureCodeID }
join pub in db.MediaPublishers
on new { MediaPublisherID = r.MediaPublisherID, CultureCodeID } equals new { pub.MediaPublisherID, pub.CultureCodeID }
join c in db.Countries
on new { CountryID = r.CountryID, CultureCodeID } equals new { c.CountryID, c.CultureCodeID }
join rd in db.ReleaseDates
on new { MediaReleaseID = r.MediaReleaseID, CultureCodeID } equals new { rd.MediaReleaseID, rd.CultureCodeID }
join a in db.AffiliateLinks
on new { MediaReleaseID = r.MediaReleaseID, CultureCodeID } equals new { a.MediaReleaseID, a.CultureCodeID }
where r.SectionID == SectionID
select new
{
rn.ReleaseTitle,
plat.MediaPlatformName,
pub.MediaPublisherName,
c.CountryName,
ReleaseDate = (rd.ReleaseDate == null ? rd.ReleaseDateName : rd.ReleaseDate.ToString()),
a.AffiliateLinkAddress
}).ToList();
lvReleaseInfo.DataBind();
}
Since you are materializing your query to list anyway, you could do the conversion on the .NET side, rather than in the RDBMS, like this:
...
select new {
rn.ReleaseTitle,
plat.MediaPlatformName,
pub.MediaPublisherName,
c.CountryName,
rd.ReleaseDateName,
rd.ReleaseDate,
a.AffiliateLinkAddress
}).AsEnumerable() // <<== This forces the following Select to operate in memory
.Select(t => new {
t.ReleaseTitle,
t.MediaPlatformName,
t.MediaPublisherName,
t.CountryName,
ReleaseDate = t.ReleaseDateName ?? t.ReleaseDate.ToString()
t.AffiliateLinkAddress
}).ToList();
Since the ToString() is called on an element from IEnumerable<T>, it will no longer fail. Also note the use of ?? operator in place of a null-checking ? : conditional.
The problem is that you can't call ToString() on a field until it's been deserialized. So, rather than trying to call ToString() in the query, simply do it on the results afterwards.
In the database the value you're operating on has no notion of ToString() which is why you get the error. The query may look and feel like C# code but keep in mind that under the covers that is being transformed to a SQL query like any other. After you get the list back you can write a very simple LINQ query to solve the problem.
Im getting a "The method 'Join' is not supported" error... Funny thing is that i simply converted the 1st LINQ into the 2nd version and it doesnt work...
What i wanted to have was LINQ version #3, but it also doesnt work...
This works
var query_join9 = from s in orgSvcContext.CreateQuery(ServiceAppointment.EntityLogicalName)
join b in orgSvcContext.CreateQuery(bh_product.EntityLogicalName)
on s["bh_contract"] equals b["bh_contract"]
where ((EntityReference)s["bh_contract"]).Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
This doesn't
var query_join9 = from s in orgSvcContext.CreateQuery(ServiceAppointment.EntityLogicalName)
join b in orgSvcContext.CreateQuery(bh_product.EntityLogicalName)
on new { contractid = s["bh_contract"] }
equals new { contractid = b["bh_contract"] }
where ((EntityReference)s["bh_contract"]).Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
Also, this doesn't, which is a composite join and what i really aim for
var query_join9 = from s in orgSvcContext.CreateQuery(ServiceAppointment.EntityLogicalName)
join b in orgSvcContext.CreateQuery(bh_product.EntityLogicalName)
on new { contractid = s["bh_contract"], serviceid = s["serviceid"] }
equals new { contractid = b["bh_contract"], serviceid = s["serviceid"] }
where ((EntityReference)s["bh_contract"]).Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
I tried early binding and still doesnt work...
var query_join9 = from s in orgSvcContext.CreateQuery<ServiceAppointment>()
join b in orgSvcContext.CreateQuery<bh_product>()
on new { foo = s.bh_contract.Id }
equals new { foo = b.bh_Contract.Id }
where s.bh_contract.Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
stil not working
var query_join9 = from s in orgSvcContext.CreateQuery<ServiceAppointment>()
join b in orgSvcContext.CreateQuery<bh_product>()
on new { s.bh_contract.Id, s.ServiceId }
equals new { b.bh_Contract.Id, ServiceId = b.bh_Service }
where s.bh_contract.Id == Guid.Parse("09BDD5A9-BBAF-E111-A06E-0050568B1372")
select new
{
Events = s,
Products = b
};
But im simply trying to do the example(s) here How to do joins in LINQ on multiple fields in single join
What am i missing?
Thanks in advance
While I'm not entirely sure which CRM you're using, I think you're misunderstanding something.
In order for a LINQ query to work, there needs to be a LINQ provider for the underlying data source -- the bit of code responsible for translating chain of e.g. Join, Where, operator usage, etc, etc, into the query API of the data source. This might be SQL, some custom query language, or some chain of methods.
Two LINQ providers (such as, one for LINQ to DataSet and some custom provider you've written yourself) don't have to support the same methods and other code. The precise subset of LINQ methods (and/or other embedded statements) a LINQ provider supports is dependent on its implementation.
Looking at it like that, it's not that surprising that the LINQ provider you're using doesn't seem to comprehend the standard syntax for joins using multiple fields, or doesn't seem to comprehend the usage of anonymous types at all.
My advice is to search the documentation of the supplied LINQ provider to see which query operations it supports (perhaps there is a note about this specific mode of query not being supported). Failing that, you'll have to resort to some sort of other query -- one not involving an equijoin. Perhaps your best option is to perform the joins separately, and then intersect the two result groups. It really depends on the specifics of the case.
Have you looked at the MSDN samples. There are some multiple-column join examples there:
using (ServiceContext svcContext = new ServiceContext(_serviceProxy))
{
var list_join = (from a in svcContext.AccountSet
join c in svcContext.ContactSet
on a.PrimaryContactId.Id equals c.ContactId
where a.Name == "Contoso Ltd" && <<--- multiple join here
a.Address1_Name == "Contoso Pharmaceuticals"
select a).ToList();
foreach (var c in list_join)
{
System.Console.WriteLine("Account " + list_join[0].Name
+ " and it's primary contact "
+ list_join[0].PrimaryContactId.Id);
}
}
This other thread might be relevant