My current employer is developing a mobile app using Xamarin.Forms and Asp.net mvc on the backend. I suggested to use realm in the mobile app. My manager want to see a POC(Proof of concept) app using realm with backlink feature before allowing it to be used in the app. I am working on the POC on GitHub . The documentation is very limiting and the GitHub repo of realm-dotnet don’t have good sample.
I completed the project. But unable to implement backlink. The sample app I have developed allow user to create assignees(employees) in the first page. The user can delete or edit the employees using context menu. When the user clicks on the employee name the app navigates to the ToDoListPage of that particular employee. Here the user can create ToDoItems. On this ToDoList page I want to show the ToDoItems that where assigned to that employee only.
The models were as follows:
public class Assignee : RealmObject
{
public Assignee()
{
ToDoItems = Enumerable.Empty<ToDoItem>().AsQueryable();
}
[PrimaryKey]
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Name { get; set; }
public string Role { get; set; }
[Backlink(nameof(ToDoItem.Employee))]
public IQueryable<ToDoItem> ToDoItems { get; }
}
public class ToDoItem : RealmObject
{
[PrimaryKey]
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Name { get; set; }
public string Description { get; set; }
public bool Done { get; set; }
public Assignee Employee { get; set; }
}
I am adding employee to each ToDo Item:
Item.Employee = Employee;
_realm.Add(Item);
Now I want to access the ToDoItems for the Employee:
Items = _realm.All<Assignee>().Where(x => x.Id == EmployeeId).FirstOrDefault().ToDoItems;
But this does not work. I will be grateful if someone can help me out by preferably writing code in my sample app or write the correct code in the reply.
Thank you
Firstly, Realm .NET doesn't currently support traversing properties (x.Employee.Id). Due to this, when I start the app and try to go to the ToDoListPage, the app crashes with the exception:
The left-hand side of the Equal operator must be a direct access to a persisted property in Realm
Realm supports object comparison, so we can fix this like so:
var employee = _realm.Find<Assignee>(EmployeeId);
Items = _realm.All<ToDoItem>().Where(x => x.Employee == employee);
Secondly, everything seemed fine in your code, so I dug a bit deeper and saw why it isn't working. The issue is that when we try to get all items with the code above, the EmployeeId parameter is null. Since the EmployeeId is being populated after the load logic has been triggered, we don't need to load the data in the ctor. So you can remove this code.
Finally, since you won't be loading the data in the ctor, and instead in the SetValues method, the UI needs to know, when the data has been updated, what exactly to redraw. Thus, you need to mark the collection to be Reactive too:
[Reactive]
public IEnumerable<ToDoItem> Items { get; set; }
Then, you need to change the SetValues method to use object comparison, instead of traversing:
async Task SetValues()
{
Employee = _realm.Find<Assignee>(EmployeeId);
Title = Employee.Name;
Items = _realm.All<ToDoItem>().Where(x => x.Employee == Employee);
}
To sum up - you don't need to try and load the data in the ctor, since you don't know when the EmployeeId will be set. You are already tracking when the property will change and inside the SetValues command you simply need to change the expression predicate.
Related
In my database I have two entities: DbStatus and DbTask
public class DbStatus
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<DbTask> Tasks { get; set; }
}
public class DbTask
{
public int Id { get; set; }
public string Title { get; set; }
public bool Done { get; set; }
public int StatusId { get; set; }
public virtual DbStatus Status { get; set; }
}
In the OnModelCreating method, I establish the relation with the following code:
modelBuilder.Entity<DbStatus>()
.HasMany(s => s.Tasks)
.WithOne(t => t.Status)
.HasForeignKey(t => t.StatusId);
I also add some sample data in this method, setting the StatusId of newly created DbTasks.
Problem is, when I try to access the Status name of the DbTask using
task.Status.Name
I get a NullReferenceException.
Can anyone help me how to set up the relation properly?
IMPORTANT
For anyone reading this, the quickest solution (and the one fulfilling task-specific criterias) for this was provided Rob. However, you should read and implement the solution provided by Steve Py, for the reasons they also describe in their answer!
When getting your list of DbTasks from the database, you need to tell it to include the child Status objects.
Try something like this:
var tasks = dbContext.DbTasks
.Include(t => t.Status)
.ToList();
Setting a FK on an entity does not automatically cause that related entity to be loaded. When working with navigation properties I recommend avoiding declaring FK fields in entities and using shadow properties to avoid issues like this.
To update a status on a DbTask:
public ActionResult MarkTaskComplete(int taskId)
{
var completeStatus = _context.Statuses.Single(x => x.StatusId = Statuses.Complete);
// TODO: Validation that user can update task etc.
var task = _context.Tasks
.Include(x => x.Status)
.Single(x => x.TaskId == taskId);
task.Status = completeStatus;
_context.SaveChanges();
return Json(new { success = true; status = task.Status.Name } );
}
The issue with FK fields is that the behaviour can differ depending on whether you use the navigation property or the FK, and whether the navigation property is eager loaded or not. From the perspective of the Task, there are two sources of truth for the current Status, some code might check task.StatusId while others use task.Status.StatusId. These values could differ depending on one being updated without the other.
While this can mean a trip to the DB to fetch a status, fetching rows by ID is extremely fast, and also provides a validation that your methods are only using legal values.
I have set up a large form with lots of entries. It populates my model and then saves to the database. I also need to be able to pull this information out of the database, put it into the model, and populate a bunch of fields with it for review. How do I do this?
Using ASP.NET MVC 4 Razor.
var db = new TechProjPlansContext();
TechProjPlan model = new TechProjPlan();
I can set up my data context and model, but where do I go from here to populate the model with a data entry chosen by ID?
You can search by givenId and if found return result type of TechProjPlan otherwise null
var resultFound = db.TechProjPlans.Where(e=>e.Id = givenId).FirstOrDefault();
I strongly recommend following this tutorial step by step. From the tutorial to answer your question:
Write a Model class like:
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
Add a DbSet to your Context class: (TechProjPlansContext in your project)
public class MovieDBContext : DbContext
{
public DbSet<Movie> Movies { get; set; }
}
Make sure you can connect to the database server, check your connectionStrings in your Web.config file.
As you wanted to filter only one Entity by using ID, you need a Controller class:
public ActionResult Details(int id = 0) // here id is set to 0 if it's null
{
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
As you see, you'll be returning View(movie), that means you return Details page with the Entity (a movie) you got from the database. And have a View, that renders to actual HTML code the client will see. At the top of it, add the line:
#model MvcMovie.Models.Movie // (something TechProjPlans.Models.Movie in your project)
#Html.LabelFor(model => model.Title)
This will create a simple text showing the movie's title.
It's not logical to go deeper within an answer, so rest is up to you with that tutorial. Just keep in mind that the code above is only one example and you can use endless variations within each level for your situation.
You can right click on the directory Controllers, select Add New and select your Model class and Context class at the page. That will produce a Controller and Views (index, edit, delete, details, insert) tied to it which will be a good way to start your MVC study.
I have a Shop model which contains several fields. One of which is a virtual User one. Whenever I try to edit one entry I get an error saying that User field is required.
public class Shop
{
//..
public virtual ApplicationUser User { get; set; }
//..
}
My workaround is this:
shop.User = shop.User; //re-set the value
shop.Active = true;
db.Entry(restaurant).State = EntityState.Modified;
db.SaveChanges();
And I have to do this for all the fields. Is this the standard approach for this or is there a better way?
Change your model to this:
public class Shop
{
//..
public int UserId {get; set; }
public virtual ApplicationUser User { get; set; }
//..
}
Entity Framework will automatically detect that UserId is the foreign key for object User. You had this problem because User is virtual (lazy loaded). When changing the model without accessing or setting this property EF thinks it's empty (I assume). The foreign key UserId is not virtual, and will be fetched together with the other properties of model Shop, so you don't have to re-set the value when saving the model.
To set a new user, you now have to do for example:
myShop.UserId = 1; // instead of setting myShop.User
For more information, see this article: http://msdn.microsoft.com/en-us/data/jj713564.aspx
Part of my project is to persist data from another source. In this case we have an SAP data source that we will need to pull data from. I need to take the data from SAP and map it to entities I have in my application. Here is an example of an entity I have in my application:
public class Project : BaseEntity
{
public string Name { get; set; }
public string ProjectNumber { get; set; }
public string Description { get; set; }
public string CreatedBy { get; set; }
public string ModifiedBy { get; set; }
public string Currency { get; set; }
#region Navigation Properties
public virtual Address Address { get; set; }
public virtual CompanyCode CompanyCode { get; set; }
public virtual ICollection<Contact> TeamMembers { get; set; }
#endregion
}
As you can see, I have child objects that I map from SAP as well. I need some advice on the best way to insert and update my entities. I am struggling with knowing when to add (insert) entities to my context and when to attach (update) them, because SAP doesn't have knowledge of what my application may or may not have. I need to guard against duplicates, too. For example, should I perform a lookup of each child entity in my parent entity to see if they exist before I apply them to the parent? Then, add / attach the entire parent object to the context or handle each entity separately while still maintaing their relationships?
Yes you must manually test everything to make correct decision what must be inserted, updated or deleted. Depending on the application you can use some more complex queries to reduce number of round trips to the database - for example you can use single query with Contains to load all TeamMembers needed for processed Project or you can load Project with including all related data if you also need to test if project exists.
I did large synchronization application before and I end up with pre-loading all entities at the beginning with few queries and working completely in memory.
Don't forget to use DbSet's Local property or Find method to take advantage of already loaded entities.
You can also use some custom stored procedures to improve performance of this operation.
I am new to MVC. I am developing an web application in asp.net MVC. I have a form through which users can get registered, after registration user redirected to ProfileView.aspx page. till here everything is fine.
Now I want to show the articles headings posted by that user right under his profile.
Right now I m using following code:
public ActionResult ProfileView(int id)
{
Profile profile = profileRepository.GetProfileByID(id);
var articles = articlesRepository.FindArticlesByUserID(id).ToList();
return View("ProfileView", profile);
}
Thanks for helping in advance
Baljit Grewal
I can think of two options:
Use the ViewData dictionary to store the articles.
public ActionResult ProfileView(int id)
{
Profile profile = profileRepository.GetProfileByID(id);
var articles = articlesRepository.FindArticlesByUserID(id).ToList();
ViewData["Articles"] = articles;
return View("ProfileView", profile);
}
Or, if you want to avoid using ViewData, create a ViewModel. A ViewModel is kind of a data transport object. You could create a ProfileViewModel class like this:
public class ProfileViewModel
{
public Profile Profile{ get; set; }
public IList<Article> Articles { get; set; }
}
or just include the Profile properties you are using in your view, this will make binding easier but you'll have to copy values from your Model to your ViewModel in your controller.:
public class ProfileViewModel
{
public int Id{ get; set; }
public string Name { get; set; }
.......
public IList<Article> Articles { get; set; }
}
If you go for this last option take a look at AutoMapper (an object to object mapper).
you will want your page to inherit from ViewPage and then you can use your model inside the .aspx markup like