Entity Core and SaveChanges works only once - entity-framework-core

I have problem with Entity Core 3.1.2.
I have code like this:
SQL.Database.EnsureCreated();
var ThisCollector = SQL.CollectorServers
.Where(esa => esa.ServerName == ServerCollectorName)
.FirstOrDefault();
while (foo)
{
await SQL.Entry(ThisCollector)
.ReloadAsync();
DateTime dtTimeOut = DateTime.UtcNow.AddMinutes(-1);
//check status of current Worker
var CurrentLB = SQL.CollectorServers
.Where(esa => esa.isWorker == true
&& esa.LastSeenLB < dtTimeOut)
.FirstOrDefault();
if (CurrentLB!=null) //Current Worker is dead!
{
CurrentLB.isWorker = false;
ThisCollector.isWorker = true;
SQL.SaveChanges(); //This works allways
}
var Collectors = SQL.CollectorServers
.Where(Esa => Esa.isWorker == true);
if (Collectors.Count() == 0)
{
ThisCollector.isWorker = true;
SQL.SaveChanges();
}
if (Collectors.Count() >= 2)
{
foreach(CollectorServer cs in Collectors)
{
cs.isWorker = false;
//why this is requied?
SQL.Entry(cs).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
}
ThisCollector.isWorker = true;
//<--This works only once, without manually setting State to
// modified!!! Why? Values has been changed from external program.
//(Management studio in this case)
SQL.SaveChanges();
}
await Task.Delay(10000, stoppingToken);
}
Problem is that last SaveChanges works only first time it has been called without I set Entry state to Modified. After that it does not make SQL query (I can see that in SQL Profiler).
In this case this can be fixed by this way, but I'm trying to undestand why this happends. My software saves lot data to SQL, and I need to know can I trust to this code without opening all queries and adding this modified state.
I didin't have this kind of problems in full version (6) of Entity, this is something quite new for me.

The described behavior would make sense if the entities returned by CollectorServers are not tracked.
ThisCollector is attached on every loop by the call to SQL.Entry(ThisCollector) :
await SQL.Entry(ThisCollector) //Attaching
.ReloadAsync(); //Reloading
Any changes made to it would be tracked and saved by the first call to DbContext.SaveChanges().
On the other hand, the entities returned by the Collectors query :
var Collectors = SQL.CollectorServers
.Where(Esa => Esa.isWorker == true);
Would remain untracked, until they get reattached by the call to SQL.Entry(cs) :
foreach(CollectorServer cs in Collectors)
{
cs.isWorker = false;
SQL.Entry(cs).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
}
That's equivalent to calling DbContext.Update to attach and set the state to Modified. Update is easier to read though :
foreach(CollectorServer cs in Collectors)
{
cs.isWorker = false;
SQL.Update(cs);
}

Related

Why does this query always return ALL records?

I'm using WCF RIA in a Lightswitch project to create some query results. This query brings back all results regardless. I cannot make it filter the records based on the parameter passed (string Town).
public IQueryable<Enquiries> TestQuery(string Town)
{
List<Enquiries> riaenqs = new List<Enquiries>();
var enqs = this.Context.ClientEnquiries
.Include("Client")
.Include("Client.Town")
.OrderBy(enq => enq.Id);
if (Town != null)
{
enqs.Where(enq => enq.Client.Town.TownName == Town);
}
foreach (ClientEnquiry item in enqs.ToList())
{
Enquiries enq = new Enquiries();
enq.Id = item.Id;
enq.ClientName = item.Client.FirstName + " " + item.Client.Surname;
enq.Town = item.Client.Town != null ? item.Client.Town.TownName : null;
riaenqs.Add(enq);
}
return riaenqs.AsQueryable();
}
During debugging I can see that the Town is correctly populated and I can see that the query is built accordingly if Town is not null. However, when I hit the foreach statement where the linq to ef query is executed I always get all the results. I just cannot figure out where I'm slipping up.
The LINQ methods like the Where do not modify the collection/expression but always returning a new one.
So you need to reassign the result of the Where to your original variable enqs:
if (Town != null)
{
enqs = enqs.Where(enq => enq.Client.Town.TownName == Town);
}

declare variable to store linq entity for conditional statements

I am trying to look up record using if I have the key then use Find if not use Where
private ApplicationDbContext db = new ApplicationDbContext();
public bool DeactivatePrice(int priceId = 0, string sponsorUserName = "")
{
var prices = db.BeveragePrices;
// if we have an id then find
if (priceId != 0)
{
prices = prices.Find(priceId);
}
else
{
prices = prices.Where(b => b.UserCreated == sponsorUserName);
}
if (prices != null)
{
// do something
}
return true;
I get the following error for
prices = prices.Find(priceId);
Cannot convert app.Model.BeveragePrices from system.data.entity.dbset
I am copying the pattern from this answer but something must be different.
Seems you forgot to put a predicate inside the Find function call. Also you need to do ToList on the collection. The second option is a lot more efficient. The first one gets the whole collection before selection.
Another note commented by #Alla is that the find returns a single element. So I assume another declaration had been made for 'price' in the first option I state down here.
price = prices.ToList.Find(b => b.PriceId == priceId);
Or
prices = prices.Select(b => b.PriceId == priceId);
I assume the field name is PriceId.

Result not ready when used, works in debugger but not in runtime

In the following image you can see where i put the breakpoint and then debugged two step. You can also see that both assignments worked great they have the same count and are the same.
However if I do the following. Run the exact same call but only break on the third line directly then this happnes
set.QuestionSet.Questions should have count of 8 BEFORE the assigment, so it seems it's not properly assigned for some reason. I suspect this has something to do with how I fetch my data from DB.
Question and QuestionSet are normal POCOs and here is the code for the entire method.
public IEnumerable<QuestionSet> SearchAndFilterQuestionsAndSets(string searchString, int nrPerPage, int page, out int totalSearchCount)
{
searchString = searchString.ToLower();
List<QuestionSet> finalList = new List<QuestionSet>();
var result = ActiveContext.QuestionSets
.Select(x => new
{
QuestionSet = x,
Questions = x.Questions.Where(
y =>
y.Description.ToLower().Contains(searchString)
).OrderBy(
z => z.Description
)
})
.ToList();
foreach (var set in result)
{
//If our search matched the set itself we load all questions
if (set.QuestionSet.Name.ToLower().Contains(searchString))
{
//we dont bring empty sets
if (set.QuestionSet.Questions.Count() > 0)
{
set.QuestionSet.Questions = set.QuestionSet.Questions.ToList<Question>().OrderBy(x => x.Description).ToList<Question>();
finalList.Add(set.QuestionSet);
}
}
//We had one or more questions matching the search term
else if (set.Questions.Count() > 0)
{
var b = set.Questions.ToList<Question>();
set.QuestionSet.Questions = set.Questions.ToList<Question>();
finalList.Add(set.QuestionSet);
}
}
totalSearchCount = finalList.Count();
return finalList.Skip((page - 1) * nrPerPage).Take(nrPerPage);
}
UPDATE
If I do this instead in the failing else if
var a = new QuestionSet();
a.Id = set.QuestionSet.Id;
a.Name = set.QuestionSet.Name;
a.Questions = set.Questions.ToList<Question>();
finalList.Add(a);
Then it works, so the problem lies within the anonymous object, but why does it work when i step through with debugger and not otherwise?? call me puzzled.
Could be something to do with Late binding of anonymous types

Is there an Update Object holder on Entity Framework?

I'm currently inserting/updating fields like this (if there's a better way, please say so - we're always learning)
public void UpdateChallengeAnswers(List<ChallengeAnswerInfo> model, Decimal field_id, Decimal loggedUserId)
{
JK_ChallengeAnswers o;
foreach (ChallengeAnswerInfo a in model)
{
o = this.FindChallengeAnswerById(a.ChallengeAnswerId);
if (o == null) o = new JK_ChallengeAnswers();
o.answer = FilterString(a.Answer);
o.correct = a.Correct;
o.link_text = "";
o.link_url = "";
o.position = FilterInt(a.Position);
o.updated_user = loggedUserId;
o.updated_date = DateTime.UtcNow;
if (o.challenge_id == 0)
{
// New record
o.challenge_id = field_id; // FK
o.created_user = loggedUserId;
o.created_date = DateTime.UtcNow;
db.JK_ChallengeAnswers.AddObject(o);
}
else
{
// Update record
this.Save();
}
}
this.Save(); // Commit changes
}
As you can see there is 2 times this.Save() (witch invokes db.SaveChanges();)
when Adding we place the new object into a Place Holder with the AddObject method, in other words, the new object is not committed right away and we can place as many objects we want.
But when it's an update, I need to Save first before moving on to the next object, is there a method that I can use in order to, let's say:
if (o.challenge_id == 0)
{
// New record
o.challenge_id = field_id;
o.created_user = loggedUserId;
o.created_date = DateTime.UtcNow;
db.JK_ChallengeAnswers.AddObject(o);
}
else
{
// Update record
db.JK_ChallengeAnswers.RetainObject(o);
}
}
this.Save(); // Only save once when all objects are ready to commit
}
So if there are 5 updates, I don't need to save into the database 5 times, but only once at the end.
Thank you.
Well if you have an object which is attached to the graph, if you modify values of this object, then the entity is marked as Modified.
If you simply do .AddObject, then the entity is marked as Added.
Nothing has happened yet - only staging of the graph.
Then, when you execute SaveChanges(), EF will translate the entries in the OSM to relevant store queries.
Your code looks a bit strange. Have you debugged through (and ran a SQL trace) to see what is actually getting executed? Because i can't see why you need that first .Save, because inline with my above points, since your modifying the entities in the first few lines of the method, an UPDATE statement will most likely always get executed, regardless of the ID.
I suggest you refactor your code to handle new/modified in seperate method. (ideally via a Repository)
Taken from Employee Info Starter Kit, you can consider the code snippet as below:
public void UpdateEmployee(Employee updatedEmployee)
{
//attaching and making ready for parsistance
if (updatedEmployee.EntityState == EntityState.Detached)
_DatabaseContext.Employees.Attach(updatedEmployee);
_DatabaseContext.ObjectStateManager.ChangeObjectState(updatedEmployee, System.Data.EntityState.Modified);
_DatabaseContext.SaveChanges();
}

.Net Entity Framework SaveChanges is adding without add method

I'm new to the entity framework and I'm really confused about how savechanges works. There's probably a lot of code in my example which could be improved, but here's the problem I'm having.
The user enters a bunch of picks. I make sure the user hasn't already entered those picks.
Then I add the picks to the database.
var db = new myModel()
var predictionArray = ticker.Substring(1).Split(','); // Get rid of the initial comma.
var user = Membership.GetUser();
var userId = Convert.ToInt32(user.ProviderUserKey);
// Get the member with all his predictions for today.
var memberQuery = (from member in db.Members
where member.user_id == userId
select new
{
member,
predictions = from p in member.Predictions
where p.start_date == null
select p
}).First();
// Load all the company ids.
foreach (var prediction in memberQuery.predictions)
{
prediction.CompanyReference.Load();
}
var picks = from prediction in predictionArray
let data = prediction.Split(':')
let companyTicker = data[0]
where !(from i in memberQuery.predictions
select i.Company.ticker).Contains(companyTicker)
select new Prediction
{
Member = memberQuery.member,
Company = db.Companies.Where(c => c.ticker == companyTicker).First(),
is_up = data[1] == "up", // This turns up and down into true and false.
};
// Save the records to the database.
// HERE'S THE PART I DON'T UNDERSTAND.
// This saves the records, even though I don't have db.AddToPredictions(pick)
foreach (var pick in picks)
{
db.SaveChanges();
}
// This does not save records when the db.SaveChanges outside of a loop of picks.
db.SaveChanges();
foreach (var pick in picks)
{
}
// This saves records, but it will insert all the picks exactly once no matter how many picks you have.
//The fact you're skipping a pick makes no difference in what gets inserted.
var counter = 1;
foreach (var pick in picks)
{
if (counter == 2)
{
db.SaveChanges();
}
counter++;
}
I've tested and the SaveChanges doesn't even have to be in the loop.
The below code works, too.
foreach (var pick in picks)
{
break;
}
db.SaveChanges()
There's obviously something going on with the context I don't understand. I'm guessing I've somehow loaded my new picks as pending changes, but even if that's true I don't understand I have to loop over them to save changes.
Can someone explain this to me?
Here's updated working code based on Craig's responses:
1) Remove the Type then loop over the results and populate new objects.
var picks = (from prediction in predictionArray
let data = prediction.Split(':')
let companyTicker = data[0]
where !(from i in memberQuery.predictions
select i.Company.ticker).Contains(companyTicker)
select new //NO TYPE HERE
{
Member = memberQuery.member,
Company = db.Companies.Where(c => c.ticker == companyTicker).First(),
is_up = data[1] == "up", // This turns up and down into true and false.
}).ToList();
foreach (var prediction in picks)
{
if (includePrediction)
{
var p = new Prediction{
Member = prediction.Member,
Company = prediction.Company,
is_up = prediction.is_up
};
db.AddToPredictions(p);
}
}
2) Or if I don't want the predictions to be saved, I can detach the predictions.
foreach (var prediction in picks) {
if (excludePrediction)
{
db.Detach(prediction)
}
}
The reason is here:
select new Prediction
{
Member = memberQuery.member,
These lines will (once the IEnumerable is iterated; LINQ is lazy) :
Instantiate a new Prediction
Associate that Prediction with an existing Member, *which is attached to db.
Associating an instance of an entity with an attached entity automatically adds that entity to the context of the associated, attached entity.
So as soon as you start iterating over predictionArray, the code above executes and you have a new entity in your context.