The bounty expires in 1 hour. Answers to this question are eligible for a +100 reputation bounty.
eia92 wants to draw more attention to this question.
I recently upgraded my project to .NET Core 6 and now removing records from my look up tables is not working. I have a Risk object that has a collection of Users. Removing users from the risk object no longer works. Any ideas what I'm doing wrong?
My lookup table is called RiskItemUser, and it has two columns, RiskItemId and UserId.
Code:
var postSavedRisk = _riskService.Queryable().Include(c => c.AssignedTo).Where(w => w.Id == riskitem.Id).FirstOrDefault();
List<User> usersToRemove = postSavedRisk.AssignedTo.Where(c => userNamesToRemove.Contains(c.UserName)).ToList();
using (var db = new ApplicationDbContext())
{
var postSavedAssginedTo = db.RiskItemUser
.Where(w => w.RiskItemId == riskitem.Id)
.ToList();
foreach (var userToRemove in usersToRemove)
{
foreach (var riskAssignedTo in postSavedAssginedTo)
{
if(userToRemove.Id == riskAssignedTo.UserId)
db.RiskItemUser.Remove(riskAssignedTo);
await db.SaveChangesAsync().ConfigureAwait(false);
}
}
}
The code, as you show it, looks like it should work, although some parts are hidden. Therefore, it's hard to tell how to make it work. But there's room for simplification, which should result in working code.
You want to remove users whose names are specified by userNamesToRemove from a risk that's specified by riskitem.Id. Assuming that there's a navigation property RiskItemUser.User, removing these data could be done by essentially one line of code:
db.RiskItemUser.RemoveRange(
db.RiskItemUser.Where(ru => ru.RiskItemId == riskitem.Id
&& userNamesToRemove.Contains(ru.User.Name)));
await db.SaveChangesAsync().ConfigureAwait(false);
You tagged EFC 6, but as of EFC 7.0, there's support for bulk delete (and update) functions, allowing for single-statement deletion of multiple database records:
db.RiskItemUser
.Where(db.RiskItemUser.Where(ru => ru.RiskItemId == riskitem.Id
&& userNamesToRemove.Contains(ru.User.Name)))
.ExecuteDelete();
This will execute one delete statement, whereas the previous method will execute one statement per row.
Note that this bulk method is like executing raw SQL. There's no communication with EF's change tracker and EF can't coordinate the correct order of statements. I think the general advice should be to not mix these bulk methods with regular SaveChanges calls.
I have situation like this: in LearningStatuses table I have a composite primary key, made up of CourseCode and UserID.
CourseCode and UserID are also the PK of Course and ApplicationUser tables respectively.
The LearningStatuses table is basically working as a enroll table.
So if a user enroll in a course, his user ID and Course Code will be stored in this table.
Now I want to fetch all the enrolled courses of a particular user in his dashboard.
Firstly I'm looking for the courseCode that is there in the learningSatatuses table with his user ID, and storing it in a list of int.
Now I want the list of courses from the database, of those IDs as an IEnumerable to iterate over them in the view.
public ActionResult Dashboard()
{
string currentUserID = User.Identity.GetUserId();
List<int> CoursesEnrolled = db.LearningStatuses
.Where(l => l.UserID == currentUserID)
.Select(l => l.CourseCode)
.ToList();
List<Course> CoursesEnrolledCourse = new List<Course>();
foreach (int item in CoursesEnrolled)
{
Course c = (Course)db.Courses.Where(c => c.CourseCode == item);
CoursesEnrolledCourse.Add(c);
}
return View();
}
During the 2nd step, I find myself unable to cast this IQueryable to a list anymore.
I've tried few other approaches too. but at the end of it I'm not able to get the desired IEnumerable of course objects for a particular user.
N.B: I have an Identity Framework user table. So my UserID is an alias of ApplicationUserID, that's a string.
What is the right approach to get the expected result.
I am creating an application that will help our employees manage tasks. Employees are divided into regions in the country. I would like the employees from a given region to see tasks related only to this region.
Workflow:
A task is submitted through the form. One of the fields in the form is the "name of the region".
The task falls into view for region.
Employees can take over the task from the view for their region.
I have MAIN data model named Service where embedded is form for submitting a task.
I also have data dictionary modal, where I have imported data about employee and his region.
Questions:
How to create a view for employees from certain region?
Should I do some relation?
EDIT: I have tried to do datasource on Service modal and write some query doing filters, but I don't exactly understand how the query works.
EDIT 2: This is how modals look like:
Service:
DOT:
The way to go here probably would be to have a datasource under your Service model that filters your Service records on Province via a subquery of the DOT model. This can be accomplished via a query server script in this datasource.
Example code would be (recall that 'query' is a default variable for the current model server script so the subquery needs it's own variable):
var DOTmodelquery = app.models.DOT.newQuery();
DOTmodelquery.filters.email._equals = Session.getActiveUser().getEmail();
var result = DOTmodelquery.run();
if (result === undefined || result.length === 0) {
throw new app.ManagedError('Could not retrieve user to perform this query!');
} else if (result[0].Province === null) {
throw new app.ManagedError('User does not have a Province value to perform this query!');
} else {
query.filters.Province._equals = result[0].Province;
}
return query.run();
Not sure that a relationship between Service and DOT would be the way to go here but you could possibly consider establishing a Province model and then having a relationship between Province-Service and Province-DOT and then try running a query in Service like so query.filters.Province.DOT.email._equals = Session.getActiveUser().getEmail();. Hopefully this will get you what you were hoping for.
I have one row in database to count total user logins
I have tried to increase number by getting the row and adding +1 to it
And i'm not sure about concurrency after I have tried this, counter was increased by 1 and not by 2 as it "should" (if many users will login at the same time)
using(var db = new Database()) {
db.Settings.FirstOrDefault(x => x.Name == "Logins").Counter++;
using(var db2 = new Database()) {
db2.Settings.FirstOrDefault(x => x.Name == "Logins").Counter++;
db2.SaveChanges();
}
db.SaveChanges();
}
Why not make a single table for storing the number of people who have logged in increment the field when someone logs in successfully and decrease when the user logs out. For example for login:
_Users = context.Users.First(aa => aa.UserName.ToUpper() == _UserName.ToUpper() && aa.MDesktop == true);
if (_Users != null)
{
context.LogEntry.FirstOrDefault().Counter++;
context.SaveChanges();
}
This is old but it is still a relevant discussion for new EF developers and it deserves an explanation.
OP's example uses two different DBContext's, effectively OP has defined two different units of work, and importantly, neither of these is aware that the other exists at all.
Lets assume that the current value of the "Logins" setting is 5
For the purposes of this walkthrough lets save the two instances that are requested from Settings into variables outside of the scope of the DB contexts in question:
Setting setting1 = null;
Setting setting2 = null;
using(var db = new Database()) {
// DB: 5, Setting1: null, Setting2: null
// Load the value of setting1 from the database
setting1 = db.Settings.FirstOrDefault(x => x.Name == "Logins");
// DB: 5, Setting1: 5, Setting2: null
// Increment the value of setting1
setting1.Counter++;
// at this point, no changes have been saved yet, the DB still holds the original value for "Logins"
// DB: 5, Setting1: 6, Setting2: null
// Create a new context called DB2
using(var db2 = new Database()) {
// load setting2 from the DB
setting2 = db2.Settings.FirstOrDefault(x => x.Name == "Logins");
// right now setting2 still has a value of 5, the previous change was not yet committed
// DB: 5, Setting1: 6, Setting2: 5
setting2.Counter++;
// DB: 5, Setting1: 6, Setting2: 6
// Save the value of Setting2 back to the database
db2.SaveChanges();
// DB: 6, Setting1: 6, Setting2: 6
// At this point setting1, setting2, and the DB all agree the value is 6.
}
// The context is only aware that we previously set the value of setting1 to 6
// so it issues an update to the DB
db.SaveChanges();
// ultimately this update would not actually change anything.
}
Entity Framework, Unit of Work and Repository data access patterns all exhibit this behaviour, when you create a new DbContext IRepository or IUnitOfWork it is done so in isolation of any others that might exist at the same point in time, there is no difference between instantiating a new context in the same method, or a different thread or even executing on entirely different servers. If you need to implement counters or incremental values there is always a degree of uncertainty when we first cache the value of the field, then increment the value and later write that value back to the database.
To minimise the potential conflict, read the record and save it immediately after, then as a rule always re-query the value of this setting before you use it.
You can call .SaveChanges() multiple times in your logic, in this example simply saving before instantiating the second context, or at least before the second context loaded the record from the DB would have been enough to see the value incremented twice:
using(var db = new Database()) {
db.Settings.FirstOrDefault(x => x.Name == "Logins").Counter++;
db.SaveChanges(); // save it back as soon as we've made the change
using(var db2 = new Database()) {
db2.Settings.FirstOrDefault(x => x.Name == "Logins").Counter++;
db2.SaveChanges();
}
db.SaveChanges();
}
Where possible, you will find the code simpler if you can avoid a schema where an incrementing or counter fields is required, instead you could turn the count logic into a query based solution.
Counters are of course a special case, you could always make direct SQL calls to the database, both for read or increment to ensure that that we bypass any potential caching that might occur with the records through EF.
You could do this as a one liner to increment the value:
dbContext.Database.ExecuteSqlCommand("UPDATE Setting SET[Counter] = IsNull([Counter],0) + 1 WHERE[Name] = 'Logins'");
Or if you want to inspect the new value:
int newCount = dbContext.Database.SqlQuery<int>(#"
UPDATE Setting SET[Counter] = IsNull([Counter],0) + 1
OUTPUT inserted.[Counter]
WHERE [Name] = 'Logins'").First();
If you need to ge tthe current value, and know that it is the most up-to-date then you can simply query it from any context in the same way:
int logins = dbContext.Database.SqlQuery<int>(#"
SELECT [Counter] FROM Setting
WHERE [Name] = 'Logins'").First();
I hope this sheds some light on why your code only incremented the value once, its not a fault in EF, just something that we need to be aware of, once EF has read values form the DB, they are potentially already stale or out of date. If optimistic concurrency is not appropriate for your use case, then you will need to think outside of the box a little bit ;)
the easy approach?
then I'd suggest using a manual transaction in EF Core
ef core transaction docs
Be sure to add an unique constraint of some sort eb. (settings id + logins counter)
using(var transaction = _context.Database.BeginTransaction())
{
try
{
var totalLoginsCounter = _context.Settings.FirstOrDefault(x => x.Name == "Logins").Counter;
totalLoginsCounter += 1;
await _context.SaveChanges();
transaction.Commit();
}
catch
{
commit.RollBack();
}
}
should concurrency happen the request will fail. Because it would try to put duplicate keys which is not possible. then HIGHLY recommend you'd then implement a retry pattern to avoid people not being able to actually login because a number in your database didn't get updated.
Hi i have written one linq query to fetch records from entity model. I am getting perfect number of records but all are same.
here is my query
Entities.TEST.Where(a => a.ID.ToUpper().Equals(ID.ToUpper())).OrderBy(s => s.NAME).ToList();
Am I missing something?
You need to make sure your Entity Key in your Entity Data Model is unique.
So in your example, ID should be the entity key for your Test entity
Your query should work, i have a similar sample that works for northwind DB:
var ctx = new NorthwindEntities();
var emp = ctx.Employees.Where(e => e.TitleOfCourtesy.Equals("ms.", StringComparison.OrdinalIgnoreCase)).OrderBy(n => n.FirstName).ToList();
Please check your query in LinqPad. You will see the results and the generated SQL.
Replace Equals with == and you can go