I have problem with creating complex query with entity framework. I would like to fetch additional data into my linq entity based on parameters given during construction of such query. Here is example with where:
if (featureEnabled)
{
query = query.Where(n => *condition*);
}
I have complex object created like that:
n => new Entity{
Property = n.Something
\* ... *\
PropertyN = n.SomethingN,
}
and I want to load additional data into entity if feature is enabled (just like in where example):
public DoSomething(bool featureEnabled, feature2Enabled, etc.)
{
return n => new Entity{
Property = n.Something,
\* ... *\
PropertyN = n.SomethingN,
Feature = (featureEnabled) ? *fetch some data from navigation property* : 0,
Feature2 = (feature2Enabled) etc.
}
}
In above example parameters (featureNEnabled) will be translated into sql parameters. How to perform such operation at query construction time?
did you mean inside the initializer you want use the if condition?
if its so i will have its not possible. To use the if condition you have to put it outside the initializer
var a = new MyClass{
prop1 = n.prop1,
prop2 = n.prop2,
prop3 = n.prop3,
};
a.propN = boolCondition ? n.PropN : 0;
I finally found answer to my question on this blog
With this code you may call expression.Merge(expression2) and initialization lists of two objects will be merged into one query.
Related
I have some complex business definitions that I'd like to define once and reuse them in Linq to Entities and also use them in other expressions that build upon it.
My attempt is below, and this worked when I was originally passing the ConvertOrderDetailsToViewModel a List, but I want to perform this on an IQueryable, which results in the error:
LINQ to Entities does not recognize the method 'System.Decimal Invoke(OrderDetail)' method, and this method cannot be translated into a store expression.
Is there a way to achieve this natively (without 3rd party libraries)?
Looks like from this answer, you can define these functions in the database itself, and then call them from C#, but again, looking to do this solely in C# code if possible.
Also came across this answer which looks like it works for a Where expression, but when I try to implement it for my select expression, I get the this error, which I get, because I am trying to assign the expression to a decimal.
Cannot implicitly convert type Expression<Func<OrderDetail, decimal>> to 'decimal'
Here's the expression function:
public static Expression<Func<OrderDetail, decimal>> calcQtyNeedOD = (od) => (od.OrderedQuantity - od.PickedQuantity);
View model creation method that calls this function:
public List<AllocationNeedViewModel> ConvertOrderDetailsToViewModel(IQueryable<OrderDetail> qryShipOrderDtls, List<int> itemIdsToExclude, bool boolGroupViewModelByItemAndRemnant)
{
var qryAllocNeedVMTEST =
from od in qryShipOrderDtls
select new AllocationNeedViewModel()
{
WorkReleaseHeaderId = od.OrderHeader.WorkReleaseHeaderId.Value,
OrderHeaderId = od.OrderHeaderId,
OrderDetailId = od.Id,
ItemId = od.ItemId.Value,
ItemDescription = od.Item.Description,
IsInventoryTracked = od.Item.TrackInventory,
QtyNeed = calcQtyNeedOD(od),
QtyRemain = 0,
QtyAllocated = 0,
IsAllocated = false
}
;
return qryAllocNeedVMTEST.ToList();
}
To Make this more complex, there are other properties that I also want to have a reusable expression for, which would also use this first expression...i.e.
public static readonly Expression<Func<OrderDetail, decimal>> calcQtyRemainOD =
(od) => calcQtyNeedOD.Compile().Invoke(od) - calcQtyAllocatedOD.Compile().Invoke(od);
UPDATE #1
SEE UPDATE #2...this solution does NOT work!
Though so far no one has been able to provide a native way to reuse select expressions across queries, I did find a partial solution that works in being able to reuse them within the same query. Once you project/assign an expression to an entity's property, you can (unlike in T-SQL) then specify that property as part of another subsequent property expression.
Example - this shows the full expressions for each property projection. In this example, QtyRemain is essentially just QtyNeed - QtyAllocated. I was re-specifying those again in the QtyRemain assignment:
public List<AllocationNeedViewModel> ConvertOrderDetailsToViewModel(IQueryable<OrderDetail> qryShipOrderDtls, List<int> itemIdsToExclude, bool boolGroupViewModelByItemAndRemnant)
{
var qryAllocNeedVM = qryShipOrderDtls
.Select(od => new AllocationNeedViewModel() //Get all Work Order Detail Needs for Work Release
{
QtyNeed = (od.OrderedQuantity - od.PickedQuantity),
QtyAllocated = (od.AllocatedInventories.Count == 0 ? 0 : od.AllocatedInventories.Where(ai => ai.StatusId < _AllocatedInventoryProcessedStatus).Sum(ai => ai.AllocatedQty)),
QtyRemain = (od.OrderedQuantity - od.PickedQuantity) - (od.AllocatedInventories.Count == 0 ? 0 : od.AllocatedInventories.Where(ai => ai.StatusId < _AllocatedInventoryProcessedStatus).Sum(ai => ai.AllocatedQty))
}
);
return qryAllocNeedVM.ToList();
}
Instead, you can simply use the already defined properties in that QtyRemain property assignment, like so:
public List<AllocationNeedViewModel> ConvertOrderDetailsToViewModel(IQueryable<OrderDetail> qryShipOrderDtls, List<int> itemIdsToExclude, bool boolGroupViewModelByItemAndRemnant)
{
var qryAllocNeedVM = qryShipOrderDtls
.Select(od => new AllocationNeedViewModel() //Get all Work Order Detail Needs for Work Release
{
QtyNeed = (od.OrderedQuantity - od.PickedQuantity),
QtyAllocated = (od.AllocatedInventories.Count == 0 ? 0 : od.AllocatedInventories.Where(ai => ai.StatusId < _AllocatedInventoryProcessedStatus).Sum(ai => ai.AllocatedQty)),
QtyRemain = this.QtyNeed - this.QtyAllocated
}
);
return qryAllocNeedVM.ToList();
}
Though this is not a full solution to my original question, it is a partial solution that gets you some of the benefits desired.
UPDATE #2
I was wrong on UPDATE #1. Though this works and compiles and seems to generate SQL, it is not correct. The returned value of this.QtyNeed when being used in subsequent expressions always results to 0. :(
Have you considered the following?:
public static Func<OrderDetail, decimal> calcQtyNeedOD = (od) => (od.OrderedQuantity - od.PickedQuantity);
public List<AllocationNeedViewModel> ConvertOrderDetailsToViewModel(IQueryable<OrderDetail> qryShipOrderDtls, List<int> itemIdsToExclude, bool boolGroupViewModelByItemAndRemnant)
{
return qryShipOrderDtls
.ToArray()
.Select(new AllocationNeedViewModel
{
WorkReleaseHeaderId = od.OrderHeader.WorkReleaseHeaderId.Value,
OrderHeaderId = od.OrderHeaderId,
OrderDetailId = od.Id,
ItemId = od.ItemId.Value,
ItemDescription = od.Item.Description,
IsInventoryTracked = od.Item.TrackInventory,
QtyNeed = calcQtyNeedOD(od),
QtyRemain = 0,
QtyAllocated = 0,
IsAllocated = false
}).ToList();
}
The original query was trying to execute the Func against the actual database (which won't understand your expression). Since you are not filtering your query (no where clause), return the entire set so that you can project using your local Func.
I'm looking for a way to do a GroupBy on a complex object, instead of just one property. The trouble is that I want to do this on an IQueryable, because getting all the data from the table is a really bad idea in this case.
We're using Entity Framework 6.1.
The class looks like this:
public class Pin {
public Guid Id {get;set;}
public Guid PageId {get;set;} /* this is the foreign key to our Pages-table */
public PageClass Page {get;set;} /* this is a relation */
}
I need to report on the times a certain page has been "pinned", printing the name of the page as well.
Right now my code looks like this:
var pinnedPages = GetAll().GroupBy(x => x, comparer);
foreach (var pinnedPage in pinnedPages)
{
var numberOfTimesPinned = pinnedPage.Count();
var pin = pinnedPage.Key;
//write a line to the report
}
But if I group on PageId, the pinnedPage.Key returns a Guid, obviously, while I need the whole Page object for my reporting needs.
I have tried implementing a custom comparer as well, but this cannot be translated to SQL, obviously which is why this doesn't work either.
GetAll().GroupBy(x => x.pageId).Select(_ => new {key = _.Key, page = _.FirstOrDefault().Page, count = _.Count()});
This will group by on the pageId, however the select will create a new anonymous object which will contain the key (pageId) and select the first PageClass object
You don't need any grouping if you query the pages directly and use a navigation property that I assume exist (or else should be added):
var pinnedPages = context.Pages
.Select(p => new
{
Page = p
Pins = p.Pins.Count()
});
foreach (var pinnedPage in pinnedPages)
{
var numberOfTimesPinned = pinnedPage.Pins;
var pin = pinnedPage.Page;
//write a line to the report
}
I use context.Pages because the source of the statement should be IQueryable. GetAll returns IEnumerable (apparently, otherwise the GroupBy overload with a comparer wouldn't work).
I want to be able to use one particular query in several other functions, I have a class that just creates a specialized QueryOver object for a particular domain.
But that function uses alias objects to create the joins. How can I access those aliases from another function?
For example say I have Course entities that each have a collection of students.
And I always want to only get Active ( a bool value) courses
public class QueryHelperClass
{
public QueryOver<Course, Course> GetQuery()
{
Address studentAlias = null;
QueryOver<Course, Course> query = QueryOver.Of<Course>(() => courseAlias)
.JoinAlias(x => cus.Student, () => studentAlias)
.Where(x => courseAlias.IsActive);
return query;
}
}
That works fine if all I need to do is GetExecutableQuery and return the results, but what do I do if I need to modify the query by accessing studentAlias?
Example:
public class SomeOtherClass
{
public List<Course> GetActiveCourseSummary(QueryOver<Course, Course> queryOver)
{
var query = queryOver.Where(a=> studentAlias.Name = "Bob");
...
}
}
From the SomeOtherClass.GetActiveCourseSummary I want to modify the query to only get courses where "Bob" is enrolled. But I can't access the studentAlias because it was defined in another function.
What can I do here, or am I setting this up all completely hard-core incorrectly?
In fact, we can re-declare the same variable in SomeOtherClass.
public List<Course> GetActiveCourseSummary(QueryOver<Course, Course> queryOver)
{
Address studentAlias = null;
var query = queryOver.Where(() => studentAlias.Name == "Bob");
...
}
The point is, that the name studentAlias (of the local variable Address) is the same as in the method GetQuery().
This will work, because what we pass in the .Where() method is the Expression. It is parsed and its string part "studentAlias" is used the same way as before, in GetQuery().
BUT
I would say, that this is not the way I would use. It is not clear what is passed into SomeOtherClass, how the query was built. There already could be an alias, but also it could be just a simple QueryOver<Course, Course> queryOver.
My approach is to do it different way. Collect all restrictions all the way down. Once there is e.g. set of restrictions IList<ICriterion>, call the DAO method, create query and append these restrictions at one place. But it is different story
If we would like to get some more checks into SomeOtherClass: we can use the Criteria API. Down side is that we have to usestring representation of properties "Student" and "Code" (not so clean as QueryOver API)
public List<Course> GetActiveCourseSummary(QueryOver<Course, Course> queryOver)
{
var criteria = query.UnderlyingCriteria;
var rootAlias = criteria.Alias; // will return "courseAlias"
var path = rootAlias + ".Student"; // the path
var student = criteria.GetCriteriaByPath(path)
?? criteria.CreateCriteria(path, path);
var studentAlias = student.Alias; // finally we do have existing alias
queryOver.And(Restrictions.Eq(studentAlias + ".Name ", "Bob"));
...
I saw this code work with LINQ to SQL but when I use Entity Framework, it throws this error:
LINQ to Entities does not recognize the method 'System.Linq.IQueryable'1[MyProject.Models.CommunityFeatures] GetCommunityFeatures()' method, and this method cannot be translated into a store expression.`
The repository code is this:
public IQueryable<Models.Estate> GetEstates()
{
return from e in entity.Estates
let AllCommFeat = GetCommunityFeatures()
let AllHomeFeat = GetHomeFeatures()
select new Models.Estate
{
EstateId = e.EstateId,
AllHomeFeatures = new LazyList<HomeFeatures>(AllHomeFeat),
AllCommunityFeatures = new LazyList<CommunityFeatures>(AllCommFeat)
};
}
public IQueryable<Models.CommunityFeatures> GetCommunityFeatures()
{
return from f in entity.CommunityFeatures
select new CommunityFeatures
{
Name = f.CommunityFeature1,
CommunityFeatureId = f.CommunityFeatureId
};
}
public IQueryable<Models.HomeFeatures> GetHomeFeatures()
{
return from f in entity.HomeFeatures
select new HomeFeatures()
{
Name = f.HomeFeature1,
HomeFeatureId = f.HomeFeatureId
};
}
LazyList is a List that extends the power of IQueryable.
Could someone explain why this error occurs?
Reason:
By design, LINQ to Entities requires the whole LINQ query expression to be translated to a server query. Only a few uncorrelated subexpressions (expressions in the query that do not depend on the results from the server) are evaluated on the client before the query is translated. Arbitrary method invocations that do not have a known translation, like GetHomeFeatures() in this case, are not supported.
To be more specific, LINQ to Entities only support Parameterless constructors and Initializers.
Solution:
Therefore, to get over this exception you need to merge your sub query into the main one for GetCommunityFeatures() and GetHomeFeatures() instead of directly invoking methods from within the LINQ query. Also, there is an issue on the lines that you were trying to instantiate a new instance of LazyList using its parameterized constructors, just as you might have been doing in LINQ to SQL. For that the solution would be to switch to client evaluation of LINQ queries (LINQ to Objects). This will require you to invoke the AsEnumerable method for your LINQ to Entities queries prior to calling the LazyList constructor.
Something like this should work:
public IQueryable<Models.Estate> GetEstates()
{
return from e in entity.Estates.AsEnumerable()
let AllCommFeat = from f in entity.CommunityFeatures
select new CommunityFeatures {
Name = f.CommunityFeature1,
CommunityFeatureId = f.CommunityFeatureId
},
let AllHomeFeat = from f in entity.HomeFeatures
select new HomeFeatures() {
Name = f.HomeFeature1,
HomeFeatureId = f.HomeFeatureId
},
select new Models.Estate {
EstateId = e.EstateId,
AllHomeFeatures = new LazyList<HomeFeatures>(AllHomeFeat),
AllCommunityFeatures = new LazyList<CommunityFeatures>(AllCommFeat)
};
}
More Info: Please take a look at LINQ to Entities, what is not supported? for more info.
Also check out LINQ to Entities, Workarounds on what is not supported for a detailed discussion on the possible solutions.
(Both links are the cached versions because the original website is down)
I'm having a problem trying to make my LINQ to SQL queries and the mapping to my domain objects DRY without incurring the cost of multiple round trips to the db. Given this example:
var query1 = from x in db.DBProducts
select new MyProduct
{
Id = x.ProductId,
Name = x.ProductName,
Details = new MyProductDetail
{
Id = x.DBProductDetail.ProductDetailId,
Description = x.DBProductDetail.ProductDetailDescription
}
}
The query will make ONE round trip to the DB. Great! However, the problem I see with this is that eventually, I'll also have a 'GetProductDetails' method which will also need to do some of the SAME "data object -> domain object" mapping, very similar to that above.
To alleviate some of the mapping, I thought it might be a cool idea to extend the partial data object classes to do the mapping for me, like so:
public partial class DBProduct
{
MyProduct ToDomainObject()
{
return new MyProduct
{
Id = this.ProductId,
Name = this.ProductName,
Details = this.DBProductDetails.ToDomainObject()
};
}
}
public partial class DBProductDetail
{
MyProductDetail ToDomainObject()
{
return new MyProductDetail
{
Id = this.ProductDetailId,
Description = this.ProductDetailDescription
};
}
}
Nice! Now, I could simply rewrite query1 as follows:
var query1 = from x in db.DBProducts
select x.ToDomainObject();
This makes the code more DRY and more readable. Additionally, other queries that need to do the same type of mapping can simply use the ToDomainObject() method for the mapping. It works, but with a cost. While watching via Profiler, the first query would call the db ONCE, joining tables where necessary. The second query doesn't join appropriately, thus making multiple calls to the DB. Is there a way to accomplish what I'm trying to do: refactor LINQ to SQL queries so that the mapping to domain objects is DRY (no code duplication)?
Use AutoMapper. Once you've tried it, it's unlikely you will ever see code like this:
new MyProduct
{
Id = x.ProductId,
Name = x.ProductName,
Details = new MyProductDetail
{
Id = x.DBProductDetail.ProductDetailId,
Description = x.DBProductDetail.ProductDetailDescription
}
}