Entity Framework Core translation capabilities [duplicate] - linq

This question already has an answer here:
Can I reuse code for selecting a custom DTO object for a child property with EF Core?
(1 answer)
Closed 1 year ago.
I have a rather theoretical issue with Entity Framework Core on SQLite.
I have an entity - Person { ID, FirstName, LastName, ... }, class PersonReference { ID, Representation : string }, extension method with argument of type Person that composes reference out of Person like this:
public static PersonReference ComposeReference(this Person from) => new PersonReference
{
ID = from.ID,
Representation = from.FirstName + " " + from.LastName
};
I need to compose references on the sql side. So I do the following:
var result = dbContext.People.Select(p => p.ComposeReference());
Result is IQueriable and program goes beyond that line and materializes the collection successfully. But when I look at the query I see it selects everything of Person and then query text ends.
If I rewrite EF expression to direct
var result = dbContext.People.Select(p => new PersonReference
{
ID = from.ID,
Representation = from.FirstName + " " + from.LastName
});
it gives me the satisfying expression with compact select and string concatenations.
Is there a way to keep the composition logic in extension method but still do calculations on the SQL side?

The trick is about using System.Linq.Expressions.Expression.
I met it at work and didn't get what it was for at first, but it is designed right for the purpose I required.
Declaration:
Expression<Func<Person, PersonReference>> ComposeReference => from => new
PersonReference
{
ID = from.ID,
Representation = from.FirstName + " " + from.LastName
};
Usage:
var result = dbContext.People.Select(ComposeReference);
Pay attention, that expressions can be compiled, but for this case never do it, or it will treat your DbSet as IEnumerable.
The answer from Svyatoslav's comment referred to some libraries, but I think vanilla EF does well enough on its own.

Related

How Could I Convert Text to Entity Frameworks Query

I need to store user queries in a database. The queries will then run against an entity frameworks structure. So an example would be :
using(AdventureWorksDB aw = new
AdventureWorksDB(Settings.Default.AdventureWorks)) {
var newSalesPeople = from p in aw.SalesPeople
where p.HireDate > hireDate
orderby p.HireDate, p.FirstName
select new { Name = p.FirstName + " " + p.LastName,
HireDate = p.HireDate };
foreach(SalesPerson p in newSalesPeople) {
Console.WriteLine("{0}\t{1}", p.FirstName, p.LastName);
}
}
How could I convert the above, if it was stored as text, to become an entity framework query that gets run? Also is there another recommended way of storing the query eg xml
Thanks for reading
It appears ObjectContext.CreateQuery is the best solution. As it says it enables for queries to be added to a context at runtime - http://msdn.microsoft.com/en-us/library/bb339670(v=vs.110).aspx . Also, storing the queries as a string in a database would be perfectly adequate.

How to search with dynamic entity names with linq

Basically all I'm looking to do is something like the following:
string EntityFrameworkType = "Product";
string searchField = "ProductName";
string searchValue = "My Product";
using( var context = new entitycontext())
{
var result = (from x in context.EntityFrameworkType.Where(l=>l.searchField == searchValue) select x).FirstOrDefault();
}
of course this syntax won't work because context does not contain an entity called "EntityFrameworkType"...
Is it possible to do this another way??? What I'm looking to do in generalize my database duplicate check. In this example, I'm searching for any Product with the Name "My Product". But I'd like to be able to pass in these string for say, ProductCategory with ProductCategoryId = 1.... etc...
you can have a look here to get the idea of how it is done.
You'll need to learn about Expression

LINQ to Entities does not recognize the method 'System.String Format(System.String, System.Object, System.Object)'

I have this linq query:
private void GetReceivedInvoiceTasks(User user, List<Task> tasks)
{
var areaIds = user.Areas.Select(x => x.AreaId).ToArray();
var taskList = from i in _db.Invoices
join a in _db.Areas on i.AreaId equals a.AreaId
where i.Status == InvoiceStatuses.Received && areaIds.Contains(a.AreaId)
select new Task {
LinkText = string.Format(Invoice {0} has been received from {1}, i.InvoiceNumber, i.Organisation.Name),
Link = Views.Edit
};
}
It has issues though. I'm trying to create tasks. For each new task when I set the link text to a constant string like "Hello" it is fine. However above I'm trying to build the property linktext using properties of the invoice.
I get this error:
base {System.SystemException} = {"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."}
Anyone know why? Anyone know an alternative way of doing this to make it work?
Entity Framework is trying to execute your projection on the SQL side, where there is no equivalent to string.Format. Use AsEnumerable() to force evaluation of that part with Linq to Objects.
Based on the previous answer I have given you I would restructure your query like this:
int statusReceived = (int)InvoiceStatuses.Received;
var areaIds = user.Areas.Select(x=> x.AreaId).ToArray();
var taskList = (from i in _db.Invoices
where i.Status == statusReceived && areaIds.Contains(i.AreaId)
select i)
.AsEnumerable()
.Select( x => new Task()
{
LinkText = string.Format("Invoice {0} has been received from {1}", x.InvoiceNumber, x.Organisation.Name),
Link = Views.Edit
});
Also I see you use related entities in the query (Organisation.Name) make sure you add the proper Include to your query, or specifically materialize those properties for later use, i.e.:
var taskList = (from i in _db.Invoices
where i.Status == statusReceived && areaIds.Contains(i.AreaId)
select new { i.InvoiceNumber, OrganisationName = i.Organisation.Name})
.AsEnumerable()
.Select( x => new Task()
{
LinkText = string.Format("Invoice {0} has been received from {1}", x.InvoiceNumber, x.OrganisationName),
Link = Views.Edit
});
IQueryable derives from IEnumerable, the main resemblance is that when you make your query it is posted to the database engine in it's language, the thin moment is where you tell C# to handle the data on the server(not client side) or to tell SQL to handle data.
So basically when you say IEnumerable.ToString(), C# gets the data collection and calls ToString() on the object.
But when you say IQueryable.ToString() C# tells SQL to call ToString() on the object but there is no such method in SQL.
The drawback is that when you handle data in C# the whole collection that you are looking through must be built up in memory before C# applies the filters.
Most efficient way to do it is to make the query as IQueryable with all the filters that you can apply.
And then build it up in memory and make the data formatting in C#.
IQueryable<Customer> dataQuery = Customers.Where(c => c.ID < 100 && c.ZIP == 12345 && c.Name == "John Doe");
var inMemCollection = dataQuery.AsEnumerable().Select(c => new
{
c.ID
c.Name,
c.ZIP,
c.DateRegisterred.ToString("dd,MMM,yyyy")
});
While SQL does not know what to do with a string.Format it can perform string concatenation.
If you run the following code then you should get the data you are after.
var taskList = from i in _db.Invoices
join a in _db.Areas on i.AreaId equals a.AreaId
where i.Status == InvoiceStatuses.Received && areaIds.Contains(a.AreaId)
select new Task {
LinkText = "Invoice " + i.InvoiceNumber + "has been received from " + i.Organisation.Name),
Link = Views.Edit
};
Once you actually perform the query this should be marginally faster than using AsEnumerable (at least that's what I found in my own code after having the same original error as you). If you are doing something more complex with C# then you will still need to use AsEnumerable though.

At least one one object must implement Icomparable

I am attempting to get unique values in a list of similar value distinguished only by a one element in a pipe delimited string... I keep getting at least one object must implement Icomparable. I don't understand why I keep getting that. I am able to groupBy that value... Why can't I find the max... I guess it is looking for something to compare it with. If I get the integer version will it stop yelling at me? This is the last time I am going to try using LINQ...
var queryResults = PatientList.GroupBy(x => x.Value.Split('|')[1]).Select(x => x.Max());
I know I can get the unique values some other way. I am just having a hard time figuring it out. In that List I know that the string with the highest value amongst its similar brethren is the one that I want to add to the list. How can I do that? I am totally drawing a blank because I have been trying to get this to work in linq for the last few days with no luck...
foreach (XmlNode node in nodeList)
{
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(node.OuterXml);
string popPatInfo = xDoc.SelectSingleNode("./template/elements/element[#name=\"FirstName\"]").Attributes["value"].Value + ", " + xDoc.SelectSingleNode("./template/elements/element[#name=\"LastName\"]").Attributes["value"].Value + " | " + DateTime.Parse(xDoc.SelectSingleNode("./template/elements/element[#name=\"DateOfBirth\"]").Attributes["value"].Value.Split('T')[0]).ToString("dd-MMM-yyyy");
string patientInfo = xDoc.SelectSingleNode("./template/elements/element[#name=\"PatientId\"]").Attributes["value"].Value + "|" + xDoc.SelectSingleNode("./template/elements/element[#name=\"PopulationPatientID\"]").Attributes["enc"].Value;// +"|" + xDoc.SelectSingleNode("./template/elements/element[#name=\"AdminDate\"]").Attributes["value"].Value;
int enc = Int32.Parse(patientInfo.Split('|')[1]);
if (enc > temp)
{
lastEncounter.Add(enc, patientInfo);
temp = enc;
}
//lastEncounter.Add(Int32.Parse(patientInfo.Split('|')[1]));
PatientList.Add( new SelectListItem { Text = popPatInfo, Value = patientInfo });
}
I was thinking about using some kind of temp variable to find out what is the highest value and then add that string to the List. I am totally drawing a blank however...
Here I get the IDs in an anonymous type to make it readable.
var patientEncounters= from patient in PatientList
let PatientID=Int32.Parse(patient.Value.Split('|')[0])
let EncounterID=Int32.Parse(patient.Value.Split('|')[1])
select new { PatientID, EncounterID };
Then we group by UserID and get the last encounter
var lastEncounterForEachUser=from pe in patientEncounters
group pe by pe.PatientID into grouped
select new
{
PatientID=grouped.Key,
LastEncounterID=grouped.Max(g=>g.EncounterID)
};
Linq doesn't know how to compare 2 Patient objects, so it can't determine which one is the "greatest". You need to make the Patient class implement IComparable<Patient>, to define how Patient objects are compared.
// Compare objets by Id value
public int CompareTo(Patient other)
{
return this.Id.CompareTo(other.Id);
}
Another option is to use the MaxBy extension method available in Jon Skeet's MoreLinq project:
var queryResults = PatientList.GroupBy(x => x.Value.Split('|')[1])
.Select(x => x.MaxBy(p => p.Id));
EDIT: I assumed there was a Patient class, but reading your code again, I realize it's not the case. PatientList is actually a collection of SelectListItem, so you need to implement IComparable in that class.

Can't combine "LINQ Join" with other tables

The main problem is that I recieve the following message:
"base {System.SystemException} = {"Unable to create a constant value of type 'BokButik1.Models.Book-Author'. Only primitive types ('such as Int32, String, and Guid') are supported in this context."}"
based on this LinQ code:
IBookRepository myIBookRepository = new BookRepository();
var allBooks = myIBookRepository.HamtaAllaBocker();
IBok_ForfattareRepository myIBok_ForfattareRepository = new Bok_ForfattareRepository();
var Book-Authors =
myIBok_ForfattareRepository.HamtaAllaBok_ForfattareNummer();
var q =
from booknn in allBooks
join Book-Authornn in Book-Authors on booknn.BookID equals
Book-Authornn.BookID
select new { booknn.title, Book-AuthorID };
How shall I solve this problem to get a class instance that contain with property title and Book-AuthorID?
// Fullmetalboy
I also have tried making some dummy by using "allbooks" relation with Code Samples from the address http://www.hookedonlinq.com/JoinOperator.ashx. Unfortunately, still same problem.
I also have taken account to Int32 due to entity framework http://msdn.microsoft.com/en-us/library/bb896317.aspx. Unfortunatley, still same problem.
Using database with 3 tables and one of them is a many to many relationship. This database is used in relation with entity framework
Book-Author
Book-Author (int)
BookID (int)
Forfattare (int)
Book
BookID (int)
title (string)
etc etc etc
It appears that you are using two separate linq-to-sql repositories to join against. This won't work. Joins can only work between tables defined in a single repository.
However, if you are happy to bring all of the data into memory then it is very easy to make your code work. Try this:
var myIBookRepository = new BookRepository();
var myIBok_ForfattareRepository = new Bok_ForfattareRepository();
var allBooks =
myIBookRepository.HamtaAllaBocker().ToArray();
var Book_Authors =
myIBok_ForfattareRepository.HamtaAllaBok_ForfattareNummer().ToArray();
var q =
from booknn in allBooks
join Book_Authornn in Book_Authors
on booknn.BookID equals Book_Authornn.BookID
select new { booknn.title, Book_AuthorID = Book_Authornn.Book_Author };
Note the inclusion of the two .ToArray() calls.
I had to fix some of you variable names and I made a bit of a guess on getting the Author ID.
Does this work for you?
I would suggest only having a single repository and allowing normal joining to occur - loading all objects into memory can be expensive.
If you are making custom repositories you make also consider making a custom method that returns the title and author IDs as a defined class rather than as anonymous classes. Makes for better testability.

Resources