In the below snipped i try to fetch data using Anonymous Projection and i would like do not track the entities that is fetched.
Note : i have already gone through existing stack question,yet unable to find a working solution for me
using (var db = new Entities())
{
db.Configuration.LazyLoadingEnabled = false;
db.Configuration.ProxyCreationEnabled = false;
var myprojection = db.Table1
.AsNoTracking()
.Include(gh=>gh.Table2) //Update
.Include(gh=>gh.Table3) //Update
.Select(x => new
{
table1= x,
table2= x.Table2.Where(g => Some Condition),
table3= x.Table3.Where(g=>Some Condition)
})
.ToList();
var result = myprojection.Select(g =>g.table1).FirstOrDefault();
}
When i useAsNoTracking() data from the inner tables (table2,3) is lost during the conversion at this line var result = myprojection.Select(g =>g.table1).FirstOrDefault();
Edit
If i remove AsNoTracking() everything works fine.
1) How to use projection and AsNoTracking in entity framework correctly ?
2) Any other option to remove tracking of this query?
Is there any possible workarounds?
Use ToList() for your navigation properties. Note that it will still projekt in the DB. (EF6)
// ..
table2 = x.Table2.Where(g => Some Condition).ToList(),
Update:
With EF4 you probably need to map table2 manually:
table2 = x.Table2.Where(g => CONDITION).Select(x => new {Foo = x.Bar}),
When i useAsNoTracking() data from the inner tables (table2,3) is lost during the conversion at this line var result = myprojection.Select(g =>g.table1).FirstOrDefault();
Your selecting Table1, into an anonymous type you will not have access to table2 or table3 although the navigation properties are they they will not map. Entities loses it relationships when you select into anonymous type you need to select table two and three or don't pull it into an anonymous type.
Just put your wheres directly in the table one where and then make your call
var myprojection = db.Table1.Join(db.Table2.Where(x => yourcondition),
t1 => t1.id, t2 => t2.t1id, (t1, t2) => t1 );
then you have to do another join for table3
but your probably better just making the call in two queries
First of all, it does not make sense to use
db.Configuration.ProxyCreationEnabled = false and AsNoTracking()
at the same time.
If you use db.Configuration.ProxyCreationEnabled = false change tracking will turn off for all queries.
But your problem with inner tables 2 and 3 is not caused by AsNoTracking().
db.Configuration.LazyLoadingEnabled = false;
db.Configuration.ProxyCreationEnabled = false;
Turn off lazy loading and i guess it's good for performance reason. If turn it on, you will get expected result, but Entity Framework will make additional SQL query to Data Base for every row in myprojection result. You should better do the following:
using (var db = new Entities())
{
db.Configuration.LazyLoadingEnabled = false;
db.Configuration.ProxyCreationEnabled = false;
var myprojection = db.Table1
.Include(x=>x.Table2).Include(x=>x.Table3)
.Select(x => new
{
table1= x,
table2= x.Table2.Where(g => Some Condition),
table3= x.Table3.Where(g=>Some Condition)
})
.ToList();
var result = myprojection.Select(g =>g.table1).FirstOrDefault();
}
Using .Include(x=>x.Table2).Include(x=>x.Table3) in your query will
force Entity Framerwork to load related Table2 and Table3 for one
query to Data Base.
Maybe you can try like this:
using (var db = new Entities())
{
db.Configuration.LazyLoadingEnabled = false;
db.Configuration.ProxyCreationEnabled = false;
var myprojection = db.Table1
//.AsNoTracking() //remove.AsNoTracking() from here
.Select(x => new
{
table1= x,
table2= x.Table2.Where(g => Some Condition),
table3= x.Table3.Where(g=>Some Condition)
})
.AsNoTracking()//add .AsNoTracking() here
.ToList();
var result = myprojection.Select(g =>g.table1).FirstOrDefault();
}
NOTE: I dont try this. Just an idea, i hope this helps to you
Related
This is a .NET Core Web API Task method. I have a flat table that I need to convert into a nested DTOs. The first DTO works but I can't seem to get the second DTO to nest after grouping.
I know I have done the grouping correctly. I am just not sure the second level nesting of the DTO is done correctly, it complains about not being able to translate to some type.
LINQ Query to put data in a nested object
Can someone point me in the right track?
public async Task<List<PointCardViewModel>> GetPointCards() {
var data = (from s in db.Students
join dc in db.DailyCards on s.StudentId equals dc.StudentId
join dcli in db.DailyCardLineItems on dc.CardId equals dcli.CardId
join dcob in db.DailyCardOtherBehaviors on dc.CardId equals dcob.CardId
select new
{
s.StudentName,
s.StudentGrade,
dc.CardId,
dc.CardDate,
dcli.ClassParticipationPoints,
dcli.AssignmentCompletionPoints,
dcli.BonusHomeworkPoints,
dcli.ClassPeriod,
dcob.PersonalAppearancePoints,
dcob.LunchPoints,
dcob.RecessOtherPoints,
dcob.AmHomeroomPoints,
dcob.PmHomeroomPoints
});
var queryPointCards = (data
.GroupBy(x => new
{
x.CardId,
x.StudentGrade,
x.StudentName,
x.CardDate,
x.PersonalAppearancePoints,
x.LunchPoints,
x.RecessOtherPoints,
x.AmHomeroomPoints,
x.PmHomeroomPoints
})
.Select(x => new PointCardViewModel()
{
CardId = x.Key.CardId,
StudentName = x.Key.StudentName,
Grade = x.Key.StudentGrade,
EvaluationDate = x.Key.CardDate,
PersonalAppearancePoints = x.Key.PersonalAppearancePoints,
LunchPoints = x.Key.LunchPoints,
RecessOtherPoints = x.Key.RecessOtherPoints,
AMHomeRoomPoints = x.Key.AmHomeroomPoints,
PMHomeRoomPoints = x.Key.PmHomeroomPoints,
//LineItems = null --> This works!! But not the below
LineItems = x.Select(c => new LineItemViewModel
{
ClassPeriod = c.ClassPeriod,
BonusHomeworkPoints = c.BonusHomeworkPoints,
ClassParticipationPoints = c.ClassParticipationPoints,
AssignmentCompletionPoints = c.AssignmentCompletionPoints
})
}
)
).ToListAsync();
if (db != null)
{
return await queryPointCards;
}
return null;
}
You have hit limitation of Grouping. After groping you cannot access to group items. Only fields from 'Key' and aggregation functions are allowed.
So just put data.AsEnumerable() and do grouping on the client side.
I am trying to write a query that filters fields in a parent table, includes a child table and also filters fields in the child table.
I have sort of got this working by putting ToList in the child selector but this just feels wrong to me. Is this the right way to do this?
Example:
var query = _context.Set<order_header>()
.Where(oh => oh.customer == accountNo)
.Include(oh => oh.route_details)
.Select(oh => new order_header()
{
customer = oh.customer,
order_no = oh.order_no,
//other columns omitted
route_details = oh.route_details
.Select(rd => new route_detail() { route_code = rd.route_code})
.ToList()//this is odd
});
return query.ToList();
Edit:
I've enabled a SQL trace and I can see this is doing a separate query to get the children for every parent row. So this is definitely the wrong way to do things.
I'm starting to think I'll have to select the results into an anonymous type and generate the EF models afterwards.
Edit2:
I have now removed the ToList in the sub-query select but SQL trace shows this as still running a query for every parent row.
Code:
var query = _context.Set<order_header>()
.Where(oh => oh.customer == accountNo)
.Include(oh => oh.route_details)
.Select(oh => new
{
customer = oh.customer,
order_no = oh.order_no,
//other columns omitted
route_details = oh.route_details.Select(rd => rd.route_code)
});
var result = query.ToList();
var list = new List<order_header>();
list.AddRange(result.Select(a =>
new order_header()
{
customer = a.customer,
order_no = a.order_no,
//other columns omitted
route_details = a.route_details.Select(rc => new route_detail() { route_code = rc }).ToList()
}));
return list;
Edit3
As requested, the SQL trace:
Parent query
exec sp_executesql N'SELECT [oph].[customer], [oph].[order_no], [oph].[customer_order_no], [oph].[date_received], [oph].[date_required], [oph].[date_despatched], [oph].[status], [oph].[to_reference], [oph].[from_reference], [oph].[nett_value]
FROM [scheme].[order_header] AS [oph]
WHERE [oph].[customer] = #__accountNo_0',N'#__accountNo_0 varchar(8)',#__accountNo_0='ACC_NO'
Child queries
exec sp_executesql N'SELECT [avl].[route_code]
FROM [scheme].[route_detail] AS [avl]
WHERE #_outer_order_no = [avl].[ldordno]',N'#_outer_order_no varchar(10)',#_outer_order_no='1A469499 '
imgur link
http://i.imgur.com/Q4ATQiU.png
note that the schema names are different in the image as I have been editing them for the question.
You can do a select with anonymous types which should then result in one query with appropriate joins, something like this (not sure I got your navigation properties right):
var query = from oh in _context.Set<order_header>()
where oh.customer == accountNo
select new
{
oh,
oh.route_details,
oh.customer,
// other navigation properties to include
route_details = from rd in oh.route_details
// your child table filtering here
select new
{
rd,
rd.route_code,
// other child nav properties to include
}
};
return query.AsEnumerable().Select(m => m.oh).ToList();
Well, im doing a linq query to get a list of results with the same column, and then i need to replace that column value with a new one.
First Code:
var db = GetContext();
var result = from f in GetContext().ProjectStateHistories
where f.ProjectId.Equals(oldProjectId)
select f;
foreach (var item in result)
{
var projectStateHistoryUpdate = db.ProjectStateHistories.Find(item.Id);
projectStateHistoryUpdate.ProjectId = newProjectId;
db.Entry(projectStateHistoryUpdate).State = EntityState.Modified;
}
db.SaveChanges();
I searched for some answers, and i found that i can use Select, and make a new object (Linq replace null/empty value with another value)
Second Code:
var result = (from f in GetContext().ProjectStateHistories
where f.ProjectId.Equals(oldProjectId)
select f).Select(d=> new { Id = d.Id, EventName = d.EventName, LogUser = d.LogUser, ProjectId = newProjectId, TimeStamp = d.TimeStamp });
And even, Third Code:
var db = GetContext();
var result = (from f in db.ProjectStateHistories
where f.ProjectId.Equals(oldProjectId)
select f).Select(d=> new { ProjectId = newProjectId});
But only the First Code works.
I wanted to ask what i am doing wrong, since i think it is better to change the value with a query, instead of using a foreach.
See code below:
var db = GetContext();
(from f in db.ProjectStateHistories
where f.ProjectId.Equals(oldProjectId)
select f)
.ToList()
.ForEach(i => i.ProjectId = newProjectId);
db.SaveChanges();
Alternatively:
var db = GetContext();
db.ProjectStateHistories
.Where(f => f.ProjectId.Equals(oldProjectId))
.ToList()
.ForEach(f => f.ProjectId = newProjectId);
db.SaveChanges();
The shortest way I know of to replace your code is this:
var db = getcontext();
db.ProjectStateHistories
.Where(f => f.ProjectId.Equals(oldProjectId))
.ToList()
.ForEach(f => f.ProjectId = newProjectId);
db.SaveChanges();
Other answers can be found here
I've just had a thought that could help you, I am just free coding here!
If you just put the for each as part of the select, and then save your changes will that work?
foreach (var source in db.ProjectStateHistories.Where(x => x.ProjectId == oldProjectId))
{
source.ProjectId= newProjectId;
db.Entry(source).State = EntityState.Modified;
}
db.SaveChanges();
I think this is a more efficient way of doing it.
Also the .Select() method is only really useful if you need to Project to a view Model, it won't change the variables in the database, just show them in the newly declared object.
Thanks,
Phill
Here's my code:
var myStrings = (from x in db1.MyStrings.Where(x => homeStrings.Contains(x.Content))
join y in db2.MyStaticStringTranslations on x.Id equals y.id
select new MyStringModel()
{
Id = x.Id,
Original = x.Content,
Translation = y.translation
}).ToList();
And I get the error that the specified LINQ expression contains references to queries that are associated with different contexts. I know that the problem is that I try to access tables from both db1 and db2, but how do I fix this?
MyStrings is a small table
Load filtered MyStrings in memory, then join with MyStaticStringTranslations using LINQ:
// Read the small table into memory, and make a dictionary from it.
// The last step will use this dictionary for joining.
var byId = db1.MyStrings
.Where(x => homeStrings.Contains(x.Content))
.ToDictionary(s => s.Id);
// Extract the keys. We will need them to filter the big table
var ids = byId.Keys.ToList();
// Bring in only the relevant records
var myStrings = db2.MyStaticStringTranslations
.Where(y => ids.Contains(y.id))
.AsEnumerable() // Make sure the joining is done in memory
.Select(y => new {
Id = y.id
// Use y.id to look up the content from the dictionary
, Original = byId[y.id].Content
, Translation = y.translation
});
You are right that db1 and db2 can't be used in the same Linq expression. x and y have to be joined in this process and not by a Linq provider. Try this:
var x = db1.MyStrings.Where(xx => homeStrings.Contains(xx.Content)).ToEnumerable();
var y = db2.MyStaticStringTranslations.ToEnumerable();
var myStrings = (from a in x
join b in y on x.Id equals y.id
select new MyStringModel()
{
Id = x.Id,
Original = x.Content,
Translation = y.translation
}).ToList();
Refer to this answer for more details: The specified LINQ expression contains references to queries that are associated with different contexts
dasblinkenlight's answer has a better overall approach than this. In this answer I'm trying to minimize the diff against your original code.
I also faced the same problem:
"The specified LINQ expression contains references to queries that are associated with different contexts."
This is because it's not able to connect to two context at a time so i find the solution as below.
Here in this example I want to list the lottery cards with the owner name but the Table having the owner name is in another Database.So I made two context DB1Context and DB2Context.and write the code as follows:
var query = from lc in db1.LotteryCardMaster
from om in db2.OwnerMaster
where lc.IsActive == 1
select new
{
lc.CashCardID,
lc.CashCardNO,
om.PersonnelName,
lc.Status
};
AB.LottryList = new List<LotteryCardMaster>();
foreach (var result in query)
{
AB.LottryList.Add(new LotteryCardMaster()
{
CashCardID = result.CashCardID,
CashCardNO = result.CashCardNO,
PersonnelName =result.PersonnelName,
Status = result.Status
});
}
but this gives me the above error so i found the other way to perform joining on two tables from diffrent database.and that way is as below.
var query = from lc in db1.LotteryCardMaster
where lc.IsActive == 1
select new
{
lc.CashCardID,
lc.CashCardNO,
om.PersonnelName,
lc.Status
};
AB.LottryList = new List<LotteryCardMaster>();
foreach (var result in query)
{
AB.LottryList.Add(new LotteryCardMaster()
{
CashCardID = result.CashCardID,
CashCardNO = result.CashCardNO,
PersonnelName =db2.OwnerMaster.FirstOrDefault(x=>x.OwnerID== result.OwnerID).OwnerName,
Status = result.Status
});
}
I have a linq query which is doing aggreagtion on db context entity.
var filterRange = from row in db.Venues
group row by true into r
select new
{
minRent = r.Min(x => x.Rent),
maxRent = r.Max(x => x.Rent),
minCapacity = r.Min(x => x.Capacity),
maxCapacity = r.Max(x => x.Capacity)
};
ViewBag.mr = filterRange.Select(x => x.minRent);
ViewBag.xr = filterRange.Select(x => x.maxRent);
During razor rendering mr and xr in viewbag are having the query instead of values.
View
<input type="hidden" id="mr" name="mr" value="#(ViewBag.mr)" />
What needs to be done to have the viewbag contaning values?
First (to avoid iterating the same sequence twice), you should 'materialize' the resultset from the general query:
var filterRange = (from row in db.Venues
group row by true into r
select new
{
minRent = r.Min(x => x.Rent),
maxRent = r.Max(x => x.Rent),
minCapacity = r.Min(x => x.Capacity),
maxCapacity = r.Max(x => x.Capacity)
}).ToList();
Next, you will also have to 'materialize' the resultsets from the derived queries:
ViewBag.mr = filterRange.Select(x => x.minRent).ToList();
ViewBag.xr = filterRange.Select(x => x.maxRent).ToList();
This will execute the 'big' query once, the 'small' ones then operate on the obtained resultset, so these ones are very cheap.
The LINQ query returns an IEnumerable and you need to call some method to enumerate that list in order to evaluate the query. In this case, you should add a ToArray call after each Select call.