I have got a bit of an issue and was wondering if there is a way to have my cake and eat it.
Currently I have a Repository and Query style pattern for how I am using Linq2Sql, however I have got one issue and I cannot see a nice way to solve it. Here is an example of the problem:
var someDataMapper = new SomeDataMapper();
var someDataQuery = new GetSomeDataQuery();
var results = SomeRepository.HybridQuery(someDataQuery)
.Where(x => x.SomeColumn == 1 || x.SomeColumn == 2)
.OrderByDescending(x => x.SomeOtherColumn)
.Select(x => someDataMapper.Map(x));
return results.Where(x => x.SomeMappedColumn == "SomeType");
The main bits to pay attention to here are Mapper, Query, Repository and then the final where clause. I am doing this as part of a larger refactor, and we found that there were ALOT of similar queries which were getting slightly different result sets back but then mapping them the same way to a domain specific model. So take for example getting back a tbl_car and then mapping it to a Car object. So a mapper basically takes one type and spits out another, so exactly the same as what would normally happen in the select:
// Non mapped version
select(x => new Car
{
Id = x.Id,
Name = x.Name,
Owner = x.FirstName + x.Surname
});
// Mapped version
select(x => carMapper.Map(x));
So the car mapper is more re-usable on all areas which do similar queries returning same end results but doing different bits along the way. However I keep getting the error saying that Map is not able to be converted to SQL, which is fine as I dont want it to be, however I understand that as it is in an expression tree it would try to convert it.
{"Method 'SomeData Map(SomeTable)' has no supported translation to SQL."}
Finally the object that is returned and mapped is passed further up the stack for other objects to use, which make use of Linq to SQL's composition abilities to add additional criteria to the query then finally ToList() or itterate on the data returned, however they filter based on the mapped model, not the original table model, which I believe is perfectly fine as answered in a previous question:
Linq2Sql point of retrieving data
So to sum it up, can I use my mapping pattern as shown without it trying to convert that single part to SQL?
Yes, you can. Put AsEnumerable() before the last Select:
var results = SomeRepository.HybridQuery(someDataQuery)
.Where(x => x.SomeColumn == 1 || x.SomeColumn == 2)
.OrderByDescending(x => x.SomeOtherColumn)
.AsEnumerable()
.Select(x => someDataMapper.Map(x));
Please note, however, that the second Where - the one that operates on SomeMappedColumn - will now be executed in memory and not by the database. If this last where clause significantly reduces the result set this could be a problem.
An alternate approach would be to create a method that returns the expression tree of that mapping. Something like the following should work, as long as everything happening in the mapping is convertible to SQL.
Expression<Func<EntityType, Car>> GetCarMappingExpression()
{
return new Expression<Func<EntityType, Car>>(x => new Car
{
Id = x.Id,
Name = x.Name,
Owner = x.FirstName + x.Surname
});
}
Usage would be like this:
var results = SomeRepository.HybridQuery(someDataQuery)
.Where(x => x.SomeColumn == 1 || x.SomeColumn == 2)
.OrderByDescending(x => x.SomeOtherColumn)
.Select(GetCarMappingExpression());
Related
Hi All I am trying to do below ,I want to load an attribute value like this .
var date = db.GetTable<bbb>().Where(x => idList.Contains(x.MID))
.Select(x => x.ModifiedDate).FirstOrDefault;
var test = db.GetTable<nnn>().Where(x => xguy.Distinct().Contains(x.SID))
.LoadWith(x => x.Modified == lastPostDate);
exception:-
LinqToDB.LinqToDBException: 'Expression '(x.Modified == value(vv.x+<>c__DisplayClass25_1).lastPostDate)' is not an association.'
How can I do this?
I used the FirstOrDefault option to get one value, but I do not understand about Expression is not an association.
Your use of the "LoadWith" method is suspicious here.
LoadWith is a specialized function to load additional table data that is linked (e.g. via foreign key) to the current table row.
Based on your usage, it looks like you're just trying to set up another "Where" clause, so instead of
.LoadWith(x => x.Modified == lastPostDate);
you wanted
.Where(x => x.Modified == lastPostDate);
or alternatively, combine this with your prior Where statement to simplify things:
var test = db.GetTable<nnn>().Where(x => x.Modified == lastPostDate &&
xguy.Distinct().Contains(x.SID));
Let me know if this isn't what you intended. If this is the case, perhaps you have an SQL statement or similar that you are now trying to translate to C# LINQ, or can otherwise explain in plain English what this statement was meant to accomplish?
I have a database scheme with 3 tables. One for requisitions, one for hospitals, and one joining the two (many-to-many relationship).
I'd like to list all requisitions in the database that are linked to a selected hospital.
This is what I have so far:
var valgtSykehus = Db.Sykehus.Where(n => n.Navn == sykehus).Single(); //this gives me a variable with my current hospital. I want to list all requistions that contains this.
var Rekvisisjoner = Db.Rekvisisjoner
.Where(r => r.Arkivert == true) //get only archived requsitions
.Include(p1 => p1.Sykehus) //include hospitals
.ToList() //this generates a list of -all- requisitions with the hospitals they are attached to.
.Where(x => x.Created > DateTime.Now.AddYears(-3)) /only go 3 years back
.Where(x => x.Sykehus.Contains(valgtSykehus)); //here is the problem. I want to discard all requisitions that does NOT contain the hospital in the valgtSykehus variable
Anyway, this gives me zero requistions, but if I skip the last line, I get all archived requistions.
x.Sykehus.Contains(valgtSykehus) executes in LINQ to Objects context (due to the intermediate ToList call) and most likely uses reference equality, which normally should work as soon as you use tracking queries.
Still, it's safer and also more efficient to do the whole thing with a single db query using Any condition with primitive key. Something like this:
var Rekvisisjoner = Db.Rekvisisjoner
.Include(r => r.Sykehus) //include hospitals
.Where(r => r.Arkivert == true) //get only archived requsitions the hospitals they are attached to.
.Where(r => r.Created > DateTime.Now.AddYears(-3)) /only go 3 years back
.Where(r => r.Sykehus.Any(s => s.Navn == sykehus));
If there is an issues with using DateTime.Now.AddYears(-3) inside the query, just put into variable outside of the query and use it inside.
var minDate = DateTime.Now.AddYears(-3);
var Rekvisisjoner =
// ...
.Where(r => r.Created > minDate)
//...
The issue may lie in the implementation of Contains. Contains has to check equality somehow. Anyway, if your valgtSykehus object is logically contained in x.Sykehus (i.e. has the same data), but not exactly the same object (i.e. the same reference), it's possible that Contains fails to find it, due to the default implementation of == in reference types (== is true, if the objects are exactly the same reference, false otherwise, even though all the data is the same).
You could try the following:
var Rekvisisjoner = Db.Rekvisisjoner
.Where(r => r.Arkivert == true)
.Include(p1 => p1.Sykehus)
.ToList()
.Where(x => x.Created > DateTime.Now.AddYears(-3))
.Where(x => x.Sykehus.Any(sh => sh.Id == valgtSykehus.Id));
If Id (or whatever your ID property is named) is a value field (most likely) this will return true whenever the ID of an Sykehus matches the ID of valgtSykehus.
Oh my.
I just realised that none of the archived requisitions contains any connections to the hospitals, as they apparently are removed one-by-one when the requisition is processed in the program.
I figured this out while trying to reverse the query, so thanks for that tip.
//This works, but seems incorrect to me
Object selection = db.ExampleTable
.Where(s => s.Id == id)
.Select(s => new { s.Id, s.PropIWantToShow })
.SingleOrDefault();
//This seems correct, but does not work
Object selection = db.ExampleTable
.SingleOrDefault(s => s.Id == id)
.Select(s => new { s.Id, s.PropIWantToShow });
db is our Entity Framework data context.
My goal is to select a single entry matching the provided id in ExampleTable. If an entry is not found, this is to return null. However, EF doesn't seem to let me select a single object and then only return specific properties. How do I accomplish this or is the first example I provided correct?
I did check this question:
select properties of entity ef linq:
Unfortunately you cannot conditionally load properties of related entity - you either load whole door entity, or don't include that entity.
But the answer just doesn't seem right, but obviously "seems" is a very weak statement.
Your first method is correct:
//This works, but seems incorrect to me
Object selection = db.ExampleTable
.Where(s => s.Id == id)
.Select(s => new { s.Id, s.PropIWantToShow })
.SingleOrDefault();
Your second method gets you a single object, not an IQueryable<T> object that LINQ would work with. If you want to convert from one type of object to another, that isn't a LINQ thing. You can still, but it'll be more convoluted. Something like:
var selection =...;
var newselection=new { Id=selection.Id, PropIWantToShow=selection.PropIWantToShow };
but this is very bad because you DID retrieve the entire object from the DB, and then just threw away most of it. Your first method only returns 2 fields from the DB.
If you want your function to return null if condition doesn't match then use FirstorDefault() instead of SingleorDefalut(). So if you want to match an id and return an object then do it like this :
return db.ExampleTable.FirstorDefault(c=>c.Id == id);
I am trying to perform a query where I get return an object for one of the records in my SQL table. In this case, it is a DbImageModel object (see code below).
The code below works great to return a single entity based on the query criteria:
s.ImageTag == imageTag
but I need to find a way to return the entities before and after the current found entity as well. Or at least the imageTag values for the before and after entities. Is this possible?
My code is as follows:
using (UsersDbContext ctx = new UsersDbContext())
{
try
{
DbImageModel image = ctx.Images.Include(i => i.Application)
.OrderBy(c => c.ImageId)
.FirstOrDefault(s => s.ImageTag == imageTag);
// Do something with image
}
catch (Exception ex)
{
throw new NotImplementedException(); // For debugging
}
}
I should also mention that my database relationships are set up as many DbImageModel objects to a single DbApplicationModel. And there are many DbApplicationModel objects for a single DbUserModel object.
This doesn't sound like it should be a new issue for developers, but I haven't been able to find any references that discuss a way to do this.
UPDATE:
I added an order by. My question still applies.
If ImageId is always incremental, you could select the items within a set range of the discovered ImageId:
DbImageModel images =
from i in ctx.Images.Include(i => i.Application)
let match = ctx.Images.FirstOrDefault(s => s.ImageTag == imageTag)
where i.ImageId < match.ImageId + 1 &&
i.ImageId > match.ImageId - 1
order by i.ImageId
select i;
However, I imagine you can't always assume this to be the case. For example, if you ever allow an image to be deleted, there would be gaps. Chances are, your best bet is to use multiple round-trips. First find the image using the code you provided, then:
DbImageModel imageAfter = ctx.Images.Include(i => i.Application)
.OrderBy(i => i.ImageId)
.FirstOrDefault(i => i.ImageId > image.ImageId);
DbImageModel imageBefore = ctx.Images.Include(i => i.Application)
.OrderByDescending(i => i.ImageId)
.FirstOrDefault(i => i.ImageId < image.ImageId);
It's possible that you could come up with some kind of convoluted query to yield all of these in one round-trip, but its complexity could end up making it take longer than these three round-trips combined.
I'm going to answer my own question here. Because, YES, there is a way - at least with multiple queries - which I would like to avoid, but oh well.
using (UsersDbContext ctx = new UsersDbContext())
{
try
{
DbImageModel image = ctx.Images.Include(i => i.Application)
.OrderBy(c => c.ImageId)
.FirstOrDefault(s => s.ImageTag == imageTag);
DbImageModel previousImage = ctx.Images.OrderBy(c => c.ImageId)
.FirstOrDefault(s => s.ImageId < image.ImageId);
DbImageModel nextImage = ctx.Images.OrderBy(c => c.ImageId)
.FirstOrDefault(s => s.ImageId > image.ImageId);
// Do something with image(s)
}
catch (Exception ex)
{
throw new NotImplementedException(); // For debugging
}
}
Note: ImageId is the SQL auto incremented id for the record.
Let's ignore the fact that before / after make no sense in a query taht returns data in an undefined order (as any SQL does - the server dcan return the data in any order it wants unless you define an order, which the example does not).
No, no way.
You basically will have to implement your own extension method to do that. This is a rare enough request that they did not embed that into the standard SQL.
You HAVE to enumerate over all data (first can be done in the database with a TOP 1 - but that means you only get one row to the client) which will have a performance impact.
If you ahve before / afer in something like an ID - then you could use a join and get the bale join the one with ID -1 join the one with ID +1.... but that is a special case.
Consider the following Query :
var profilelst =
(
from i in dbContext.ProspectProfiles
where i.CreateId == currentUser
select new ProspectProfile
{
ProspectId = i.ProspectId,
Live = i.Live,
Name = i.Name,
ServiceETA = i.Opportunities.OrderByDescending(t => t.FollowUpDate)
.FirstOrDefault()
.ServiceETA.ToString(),
FollowUpDate = i.Opportunities.OrderByDescending(t => t.FollowUpDate)
.FirstOrDefault()
.FollowUpDate
}
)
.ToList();
return profilelst.OrderByDescending(c=>c.FollowUpDate)
.Skip(0).Take(endIndex)
.ToList();
Here in this query please take a look at FollowUpDate and ServiceType, these both i have fetched from Opportunity table, is there any other work around to get these both..
One to Many Relationship in tables is like: ProspectProfile -> Opportunities
Whether the query i have written is ok or is there any another work around that can be done in easier way.
The only thing you can improve is to avoid ordering twice by changing your code to this:
var profilelst
= dbContext.ProspectProfiles
.Where(i => i.CreateId == currentUser)
.Select(i =>
{
var opportunity
= i.Opportunities
.OrderByDescending(t => t.FollowUpDate)
.First();
return new ProspectProfile
{
ProspectId = i.ProspectId,
Live = i.Live,
Name = i.Name,
ServiceETA = opportunity.ServiceETA.ToString(),
FollowUpDate = opportunity.FollowUpDate
}
}).ToList();
return profilelst.OrderByDescending(c => c.FollowUpDate).Take(endIndex).ToList();
I made several changes to your original query:
I changed it to use method chains syntax. It is just so much easier to read in my opinion.
I removed the unnecessary Skip(0).
The biggest change is in the Select part:
I changed FirstOrDefault to First, because you are accessing the properties of the return value anyway. This will throw a descriptive exception if no opportunity exists. That's better than what you had: In your case it would throw a NullReferenceException. That's bad, NullReferenceExceptions always indicate a bug in your program and are not descriptive at all.
I moved the part that selects the opportunity out of the initializer, so we need to do the sorting only once instead of twice.
There are quite a few problems in your query:
You cannot project into an entity (select new ProspectProfile). LINQ to Entities only supports projections into anonymous types (select new) or other types which are not part of your entity data model (select new MySpecialType)
ToString() for a numeric or DateTime type is not supported in LINQ to Entities (ServiceETA.ToString())
FirstOrDefault().ServiceETA (or FollowUpdate) will throw an exception if the Opportunities collection is empty and ServiceETA is a non-nullable value type (such as DateTime) because EF cannot materialize any value into such a variable.
Using .ToList() after your first query will execute the query in the database and load the full result. Your later Take happens in memory on the full list, not in the database. (You effectively load the whole result list from the database into memory and then throw away all objects except the first you have Takeen.
To resolve all four problems you can try the following:
var profilelst = dbContext.ProspectProfiles
.Where(p => p.CreateId == currentUser)
.Select(p => new
{
ProspectId = p.ProspectId,
Live = p.Live,
Name = p.Name,
LastOpportunity = p.Opportunities
.OrderByDescending(o => o.FollowUpDate)
.Select(o => new
{
ServiceETA = o.ServiceETA,
FollowUpDate = o.FollowUpDate
})
.FirstOrDefault()
})
.OrderByDescending(x => x.LastOpportunity.FollowUpDate)
.Skip(startIndex) // can be removed if startIndex is 0
.Take(endIndex)
.ToList();
This will give you a list of anonymous objects. If you need the result in a list of your entity ProspectProfile you must copy the values after this query. Note that LastOpportunity can be null in the result if a ProspectProfile has no Opportunities.