How to compare the value between the one from database and the client .Net Core 2 - mvvm

We have this edit Razor pages (edit.cshtml) which is extended from the following page model and it's very basic only include the PopulateRolesDropDownList:
public class RoleNamePageModel : PageModel
{
public SelectList RoleNameSL { get; set; }
public void PopulateRolesDropDownList(ApplicationDbContext _context,
object selectedRole = null)
{
var rolesQuery = from d in _context.Roles
orderby d.Name // Sort by name.
select d;
RoleNameSL = new SelectList(rolesQuery,
"RoleId", "Name", selectedRole);
}
}
Also in this Edit page, we added:
<input type="hidden" asp-for="User.UserRoles.ElementAt(0).RoleId" name="User.Current.RoleId" />
We also do the [BindProperty] in the code behind
public ApplicationUser User { get; set; }
We need to find out whether there is a change on this model. What is the approach to do this?
ENVIRONMENT:
.NET Core 2.2
Razor Pages
UPDATE - 1:
On the PostAsync, we made another call to the database:
var userRoleToUpdate = await _context.UserRoles
.FirstOrDefaultAsync(m => m.UserId == id.ToString());
We just need to compare this value with the change on a drop-down list or not. We could not work how.
UPDATE - 2:
We did change as per recommend by #NevilleNazerane below:
public class AssignClubUserViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public Guid SelectedRoleID { get; set; }
}
[BindProperty]
public AssignClubUserViewModel AssignClubUser { get; set; }
and added OnGetAsync:
public async Task<IActionResult> OnGetAsync(Guid? id)
{
if (id == null)
return NotFound();
var user = await _context.Users
.Include(u => u.ClubApplicationUsers)
.FirstOrDefaultAsync(m => m.Id == id.ToString());
AssignClubUser.FirstName = user.FirstName;
AssignClubUser.LastName = user.LastName;
AssignClubUser.UserName = user.UserName;
AssignClubUser.SelectedClubID =
user.ClubApplicationUsers.ElementAt(0).ClubID;
....
Is this right? I got the error: NullReferenceException: Arg_NullReferenceException on line AssignClubUser.FirstName = user.FirstName;
UPDATE - 3:
Fixed by creating a ModemView and then on the OnGetAsync() for query ensure to mapped with the ModelView:
var user = await _context.Users
.Include(u => u.ClubApplicationUsers)
.Where(t => t.Id == id.ToString())
.Select(t => new AssignClubUserViewModel<ApplicationUser>
{
FirstName = t.FirstName,
LastName = t.LastName,
UserName = t.UserName,
SelectedClubID = t.ClubApplicationUsers.ElementAt(0).ClubID
}).SingleAsync();

Since you have a view model, I recommend you simplify your bindings and let your behind code handle the other functionalities. You can first make a SelectedRoleId property:
public int SelectedRoleId { get; set; }
In your view model, you can assign this property's default value to User.UserRoles.ElementAt(0).RoleId in either your constructor or your OnGet, based on how you need it set up. This way the drop down is bound to a simple property.
For binding dropdowns (HTML selects) .NET Core provides the asp-items tag helper.
<select asp-for="SelectedRoleId" asp-items="Model.RoleNameSL"></select>
In your OnPostAsync, you can use SelectedRoleId to access the selected value.

Related

What is the proper way of updating navigation properties in EF Core?

In my EF Core solution I have the following model:
public class Deal
{
public string Id { get; set; }
public ResponsiblePerson ResponsiblePerson1 { get; set; }
public ResponsiblePerson ResponsiblePerson2 { get; set; }
public ResponsiblePerson ResponsiblePerson3 { get; set; }
}
public class ResponsiblePerson
{
public string Id { get; set; }
public string Name { get; set; }
}
When I am trying to update Deal navigations properties:
private void UpdateResponsiblePersons(string dealId, string person1Id, string person2Id, string person3Id)
{
var existingdeal = _dbContext.Deals
.Include(d => d.ResponsiblePerson1)
.Include(d => d.ResponsiblePerson2)
.Include(d => d.ResponsiblePerson3)
.Single(d => d.Id == dealId);
existingDeal.ResponsiblePerson1 = new ResponsiblePerson { Id = person1Id };
existingDeal.ResponsiblePerson2 = new ResponsiblePerson { Id = person2Id };
existingDeal.ResponsiblePerson3 = new ResponsiblePerson { Id = person3Id };
_dbContext.Entry(deal.ResponsiblePerson1).State = EntityState.Unchanged;
_dbContext.Entry(deal.ResponsiblePerson3).State = EntityState.Unchanged;
_dbContext.Entry(deal.ResponsiblePerson3).State = EntityState.Unchanged;
_dbContext.SaveChanges();
}
EF often fails with
System.InvalidOperationException: The instance of entity type 'ResponsiblePerson' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
That is because sometimes existingdeal already contains the link to ResponsiblePerson with one of provided IDs in either ResponsiblePerson1 of ResponsiblePerson2 or ResponsiblePerson3 Navigation properties.
I know that one of possible solutions will be first to get ResponsiblePersons used for update from dbContext like
existingDeal.ResponsiblePerson1 = _dbContext.ResponsiblePersons.Find(person1Id)
But that means extra DB roundtrips.
Another solution is to expose foreign keys instead of navigation properties but it would make Deal model quite ugly.
Please advice me what is the best way of updating such references?

.NET Core MongoDB. Find by guid returns null

I have the following setup:
The document:
[BsonCollection("Users")] // I get the collection name with a custom extension
[BsonIgnoreExtraElements]
public class UserDocument
{
[BsonId]
public Guid Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public UserSettingsModel UserSettings { get; set; }
}
public class UserSettingsModel
{
// ...
}
The repository:
public class UserRepository
{
private readonly IMongoCollection<UserDocument> _collection;
private readonly ILogger<UserRepository> _logger;
public UserRepository(IMongoDatabase database, ILogger<UserRepository> logger)
{
// returns "Users"
var collectionName = typeof(UserDocument).GetCollectionName();
_collection = database.GetCollection<UserDocument>(collectionName);
}
// ...
public async Task<UserDocument> GetById(Guid id)
{
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, id);
var user = await _collection.FindAsync(filter);
// var user = await _collection.FindAsync(x => x.Id == id); - doesn't work either
var request = filter.Render(
_collection.DocumentSerializer,
_collection.Settings.SerializerRegistry).ToString();
_logger.LogDebug(request);
return user.FirstOrDefault();
}
}
And I initialize the client this way:
// ...
BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
var client = new MongoClient(connectionString);
var database = client.GetDatabase(dbName);
services.AddSingleton(c => database);
// convention pack and registries
// ...
// if moved here doesn't work either
// BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
The filter generated in GetById is still the following: { "_id" : CSUUID("459f165a-4a91-4f39-906c-dc7401ee2468") } when I expect it to be UUID instead of CSUUID.
So, the query doesn't find anything and returns null. In the database the document I'm searching for has _id: UUID('459f165a-4a91-4f39-906c-dc7401ee2468')
What am I doing wrong?
I was able to fixing by this trick:
var mongoConnectionUrl = new MongoUrl(connectionString);
var mongoClientSettings = MongoClientSettings.FromUrl(mongoConnectionUrl);
// before initializing client
mongoClientSettings.GuidRepresentation = GuidRepresentation.Standard;
However setting the GuidRepresentation in client settings is obsolete, which is quite confusing. Also, the query generated still has CSUUID instead of UUID. I was able to log the query the following way:
mongoClientSettings.ClusterConfigurator = cb =>
{
cb.Subscribe<CommandStartedEvent>(e => logger.LogDebug($"{e.CommandName} - {e.Command.ToJson()}"));
};
If anyone finds a better way and post it here it would be appreciated.

entity framework 5 take and order by in include

I want to retrieve an object plus its filtered/ordered collection property using EF 5. However, my current code throws an exception:
The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties
Here is the class of the object I want to retrieve:
public class EntryCollection
{
[Key]
public int Id { get; set; }
public ICollection<Entry> Entries { get; set; }
...
}
And here is the definition of Entry:
public class Entry
{
[Key]
public int Id { get; set; }
public DateTime Added { get; set; }
...
}
I wanted to retrieve the EntryCollection which contains only the most recent entries, so here is the code I tried:
using (var db = new MyContext())
{
return db.EntryCollections
.Include(ec => ec.Entries.OrderByDescending(e => e.Added).Take(5))
.SingleOrDefault(ec => ec.Foo == "bar');
}
Any ideas?
You cant use OrderBy inside an include.
what about the following
using (var db = new MyContext())
{
return db.EntryCollections
.Where(ec => ec.Foo == "bar")
.Select(ec=> new Something{Entries = ec.Entries.OrderByDescending(e => e.Added).Take(5) }, /*some other properties*/)
.SingleOrDefault();
}
or do it in two seperate queries

Cannot get relationship to update for navigation properties in entity framework

I am currently using EF4.3 and Code First. Creation of my objects works (via my views - just using the auto-generated Create), but when I attempt to edit an object, it does not save any changes that, utlimately, tie back to my navigation properties. I have been reading on relationships, but I don't understand how to tell my context that the relationship has changed.
Here is some example code of my implementation.
#* Snippet from my view where I link into my ViewModel. *#
<div class="row">
<div class="editor-label">
#Html.LabelFor(model => model.ManagerID)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.ManagerID, ViewBag.Manager as SelectList, String.Empty)
#Html.ValidationMessageFor(model => model.ManagerID)
</div>
</div>
Here is my Controller implementation (POST of my Edit):
[HttpPost]
public ActionResult Edit(ProjectViewModel projectViewModel)
{
if (ModelState.IsValid)
{
Project project = new Project();
project.ProjectID = projectViewModel.ProjectID;
project.Name = projectViewModel.Name;
project.ProjectManager = repository.GetUser(projectViewModel.ManagerID);
repository.InsertOrUpdateProject(project);
repository.Save();
return RedirectToAction("Index");
}
ViewBag.Manager = new SelectList(repository.GetUsers(), "UserID", "FullName", projectViewModel.ManagerID);
return View(projectViewModel);
}
Within my Project object:
public class Project
{
public int ProjectID { get; set; }
[Required]
public string Name { get; set; }
// Navigation Properties
public virtual User Manager { get; set; }
}
Here is the corresponding method from the repository (where my context resides):
public void InsertOrUpdateProject(Project project)
{
if (program.ProjectID == default(int))
{
context.Projects.Add(project);
}
else
{
context.Entry(project).State = EntityState.Modified;
}
}
Just to be clear, this does work to update my properties, but it does not update my navigation properties (in this case, Manager). Appreciate any help.
Setting the state to Modified only marks scalar properties as modified, not navigation properties. You have several options:
A hack (you won't like it)
//...
else
{
var manager = project.Manager;
project.Manager = null;
context.Entry(project).State = EntityState.Modified;
// the line before did attach the object to the context
// with project.Manager == null
project.Manager = manager;
// this "fakes" a change of the relationship, EF will detect this
// and update the relatonship
}
Reload the project from the database including (eager loading) the current manager. Then set the properties. Change tracking will detect a change of the manager again and write an UPDATE.
Expose a foreign key property for the Manager navigation property in your model:
public class Project
{
public int ProjectID { get; set; }
[Required]
public string Name { get; set; }
public int ManagerID { get; set; }
public virtual User Manager { get; set; }
}
Now ManagerID is a scalar property and setting the state to Modified will include this property. Moreover you don't need to load the Manager user from the database, you can just assign the ID you get from your view:
Project project = new Project();
project.ProjectID = projectViewModel.ProjectID;
project.Name = projectViewModel.Name;
project.ManagerID = projectViewModel.ManagerID;
repository.InsertOrUpdateProject(project);
repository.Save();
There are several options here, I will list 3 of them:
Option 1: Using GraphDiff
*This needs the Configuration.AutoDetectChangesEnabled of your context set to true.
Just install GraphDiff with NuGet
Install-Package RefactorThis.GraphDiff
Then
using (var context = new Context())
{
var customer = new Customer()
{
Id = 12503,
Name = "Jhon Doe",
City = new City() { Id = 8, Name = "abc" }
};
context.UpdateGraph(customer, map => map.AssociatedEntity(p => p.City));
context.Configuration.AutoDetectChangesEnabled = true;
context.SaveChanges();
}
For more details about GraphDiff look here.
Option 2: Find and Edit
Searching your entity with EF to track it to the context. Then edit the properties.
*This needs the Configuration.AutoDetectChangesEnabled of your context set to true.
var customer = new Customer()
{
Id = 12503,
Name = "Jhon Doe",
City = new City() { Id = 8, Name = "abc" }
};
using (var context = new Contexto())
{
var customerFromDatabase = context.Customers
.Include(x => x.City)
.FirstOrDefault(x => x.Id == customer.Id);
var cityFromDataBase = context.Cities.FirstOrDefault(x => x.Id == customer.City.Id);
customerFromDatabase.Name = customer.Name;
customerFromDatabase.City = cityFromDataBase;
context.Configuration.AutoDetectChangesEnabled = true;
context.SaveChanges();
}
Option 3: Using a scalar property
In a matter of performance this is the best way, but it mess your class with database concerns. Because you will need to create a scalar (primitive type) property to map the Id.
*In this way there is no need to set the Configuration.AutoDetectChangesEnabled to true. And also you won't need to do a query to the database to retrieve the entities (as the first two options would - yes GraphDiff does it behind the scenes!).
var customer = new Customer()
{
Id = 12503,
Name = "Jhon Doe",
City_Id = 8,
City = null
};
using (var contexto = new Contexto())
{
contexto.Entry(customer).State = EntityState.Modified;
contexto.SaveChanges();
}
I am not sure exactly what you mean by navigation properties? Do you mean like a foreign key relationship? If so then try the following data annotation:
public class Project
{
public int ProjectID { get; set; }
[Required]
public string Name { get; set; }
[ForeignKey("YourNavigationProperty")]
public virtual UserManager { get; set; }
}
Update your EF Context, and see what happens?
UPDATE
public class Project
{
public int ProjectID { get; set; }
[Required]
public string Name { get; set; }
[ForeignKey("ManagerId")]
public ManagerModel UserManager { get; set; }
}
public class ManagerModel
{
[Key]
public int ManagerId { get; set; }
public String ManagerName { get; set; }
}
See if that works?

Using Entity Framework 4.0 with Code-First and POCO: How to Get Parent Object with All its Children?

I'm new to EF 4.0, so maybe this is an easy question. I've got VS2010 RC and the latest EF CTP. I'm trying to implement the "Foreign Keys" code-first example on the EF Team's Design Blog, http://blogs.msdn.com/efdesign/archive/2009/10/12/code-only-further-enhancements.aspx.
public class Customer
{
public int Id { get; set;
public string CustomerDescription { get; set;
public IList<PurchaseOrder> PurchaseOrders { get; set; }
}
public class PurchaseOrder
{
public int Id { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
public DateTime DateReceived { get; set; }
}
public class MyContext : ObjectContext
{
public RepositoryContext(EntityConnection connection) : base(connection){}
public IObjectSet<Customer> Customers { get {return base.CreateObjectSet<Customer>();} }
}
I use a ContextBuilder to configure MyContext:
{
var builder = new ContextBuilder<MyContext>();
var customerConfig = _builder.Entity<Customer>();
customerConfig.Property(c => c.Id).IsIdentity();
var poConfig = _builder.Entity<PurchaseOrder>();
poConfig.Property(po => po.Id).IsIdentity();
poConfig.Relationship(po => po.Customer)
.FromProperty(c => c.PurchaseOrders)
.HasConstraint((po, c) => po.CustomerId == c.Id);
...
}
This works correctly when I'm adding new Customers, but not when I try to retrieve existing Customers. This code successfully saves a new Customer and all its child PurchaseOrders:
using (var context = builder.Create(connection))
{
context.Customers.AddObject(customer);
context.SaveChanges();
}
But this code only retrieves Customer objects; their PurchaseOrders lists are always empty.
using (var context = _builder.Create(_conn))
{
var customers = context.Customers.ToList();
}
What else do I need to do to the ContextBuilder to make MyContext always retrieve all the PurchaseOrders with each Customer?
You could also use:
var customers = context.Customers.Include("PurchaseOrders").ToList();
Or enable LazyLoading in the ContextOptions :
context.ContextOptions.LazyLoadingEnabled = true;
Just be careful with deferred loading if you are serializing the objects or you may end up querying the entire database.
Well the solution turned out to be simple, as I suspected it might. I called the context.LoadProperty() method for each individual customer:
using (var context = _builder.Create(_conn))
{
var customers = context.Customers.ToList();
foreach (var customer in customers)
{
context.LoadProperty<Customer>(customer, c => c.PurchaseOrders);
}
return customers;
}