Ado.Net Entity : Object doesn't display linked members (foreign keys) - c#

I have a simple databasescheme: User, Account. User has 1-to-many relationship with Account.
I have generated a ado.net entity data model, and I can create users and accounts, and even link them together. In the database the account.user_id is correctly filled, so theoretically I should be able to acces User.Account.ToList() in C# through entity.
However, When I try to acces User.Account.ToList() I get zero results.
User user = db.User.First(U => U.id == 1);
List<Account> accounts = user.Account.ToList(); ##count = 0...
When I add the following code before the previous code it suddenly gives me the correct count 2.
Account account1 = db.Account.First(A => A.id == 1);
Account account2 = db.Account.First(A => A.id == 2);
User user = db.User.First(U => U.id == 1);
List<Account> accounts = user.Account.ToList(); ##count = 2...??
What am I missing here??

You should use the ObjectQuery.Include method for this. Your method works also but results in an additional query.
In your example you would get
User user = db.User.Include("Account").First(u => u.id == 1);
You have to figure out whether the string "Account" is correct. Usually it should be prefixed with something like MyEntities. This depends on the namespace of your entities but with a little trial and error you should be able to figure this out.

Yes, that's a common problem when starting to use the Entity framework - neither parent nor child relationships are lazy loaded so you have to load them explicitly. If you are going to share the object context around between classes / methods you might want to make a check to see if the relationship is already loaded:
e.g.
if(!user.Account.IsLoaded)
user.Account.Load();
You can make this easier with a simple extension method:
public static class EntityExtensions
{
public static void EnsureLoaded(this RelatedEnd relatedEnd)
{
if (!relatedEnd.IsLoaded)
relatedEnd.Load();
}
}
using this makes your load call shorter again:
user.Account.EnsureLoaded();
And as it uses a RelatedEnd, which is common to parent and child relationships in the entity framework, you can use this for parent reference relationships too - e.g.
account.UserReference.EnsureLoaded();
As rwwilden says, if you are always going to load the child objects with the parent in this case, you might want to use an Include to make the call more efficient and avoid an extra roundtrip to the database.

I guess my knowledge is a bit small of the framework. :)
You need to explicitly load the related accounts first.
user.Account.Load();
Now It does display correctly.

Related

Entity Framework update the intermediate table of a many-to-many relation

I've got a many-to-many relation between user and project. Like:
class User
{
public ICollection<Project> Projects { get; set; }
}
class Project
{
public ICollection<User> Users { get; set; }
}
Entity Framework automatically generated the intermediate table.
The thing is I want to update the user, along with the entire list of projects. This list could've been modified in any way, projects could've been added and deleted. at the same time before the user object gets updated.
I always get the same error, that Entity Framework tried to add a duplicated entry in the intermediate table.
I've tried numerous things without success (a few listed below).
var tmp = Context.Entry(user); // user being the updated object.
tmp.State = EntityState.Modified;
tmp.Collection(e => e.Projects).IsModified = true;
Context.Users.Update(user);
Context.SaveChanges();
or
var tmp = Context.Users.SingleOrDefault(u => u.Id == user.Id);
if (tmp == null)
return null;
Context.Entry(tmp).CurrentValues.SetValues(user);
Context.SaveChanges();
return user;
or just plain old update:
Context.Users.Update(user);
Context.SaveChanges();
But none of these worked.
The issue sounds like you have a detached User Entity with a set of Projects and you want to pass that into a method, associate with the DbContext to persist the changes.
You are encountering issues with doubling up records because while you attach the user to the DbContext, it will treat each of the Project entities associated with the user as new instances because they don't reference tracked instances themselves.
Updating detached entities with associations is fairly involved, especially where you expect to possibly add or remove associations in an operation.
The recommended approach would be to load the current User and Projects from the DB then leverage Automapper to guard what values you can copy over from the detached entity, and then go through the associations to add/remove any project references that have changed. If it is possible to create a brand new project to associate to the user as part of this operation, you need to handle that as well.
var existingUser = Context.Users.Include(x => x.Projects).Single(x => x.UserId == user.UserId);
Mapper.Map(user, existingUser);
// Where Automapper is configured with a User to User mapping with allowed
// values to copy over, ignoring anything that cannot legally be changed.
var newProjectIds = user.Projects.Select(x => x.ProjectId).ToList();
var existingProjectIds = existingUser.Projects.Select(x => x.ProjectId).ToList();
var projectIdsToAdd = newProjectIds.Except(existingProjectIds).ToList();
var projectIdsToRemove = existingProjectIds.Except(newProjectIds).ToList();
var projectsToAdd = Context.Projects.Where(x => projectIdsToAdd.Contains(x.ProjectId)).ToList();
var projectsToRemove = existingUser.Projects.Where(x => projectIdsToRemove.Contains(x.ProjectId)).ToList();
foreach(var project in projectsToRemove)
existigUser.Projects.Remove(project);
foreach(var project in projectsToAdd)
existingUser.Projects.Add(project);
Context.SaveChanges();
... This example does not cover the possibility of brand new projects. If the updated user can include a brand new project then you need to detect those when looking for projectsToAdd to add any Projects from the passed in project list where the ID is in new project IDs but not found in the DB. Those detached references can be added to the User loaded from the DbContext, however you do need to handle any navigation properties that each Project might have to avoid duplication, substituting each of those with references to tracked entities, including any bi-directional reference back to the User if present.
In general, dealing with detached entities has various considerations that you need to keep in mind and handle very deliberately. It is generally much better to avoid passing detached entities around and instead aim to pass a minimal representation of the data you want to associate, then load and adjust that server-side. Usually the argument to using detached entities is to avoid having to load the data again, however this leads to more code when trying to synchronize these detached instances, and neglects the fact that data state could have changed since the detached instances were taken. The above code for instance should also be looking at entity versioning between the detached entity and the loaded state to detect if anyone else might have made changes since the detached copies were read at the start of the user process for making the changes.

Override DbContext when returning IQueryable

In my database, all my tables have 5 repeating fields (CreateUser, UpdateUser, IsRemoved...)
Now, I call the DbContext normally as:
mydbEntities db = new mydbEntities();
And then when I need to bring up all the students and all classes I call them normally as:
var students = db.students;
var classes = db.classes;
BUT! Since I have to NOT bring the ones that have column IsRemoved == true , I'm rewriting everywhere in my app to:
var students = db.students.Where(m => m.IsRemoved == false);
var classes = db.classes.Where(m => m.IsRemoved == false);
To avoid bugs and errors when replacing existing code, when the scaffolding creates new controllers, and other developers forgetting to add the Where(), makes me wonder if I can overwrite the call db.students so that internally, it ALWAYS adds the Where()
This will also help in the future, so when I call db.students.Include(s => s.classes) that both times the IsRemoved is taken into account and even if the student exists, if the class has been removed, then it won't return it in the list.
This is why I believe that having Repository classes - for the database access layer - is always a good idea. So you never have to access your dbContext object directly, but through a layer of abstraction, eg. the StudentRepositoy.
IStudentRepository studentRepository = new StudentRepository(dbContext);
var students = studentRepository.getStudents();
In this case, you only need to add the Where(m => m.IsRemoved == false) once, inside your getStudents() method.
I hope you don't have those queries repeated in your controllers (I don't know if you are using MVC or some other thing). Check out this tutorial for more detail information.
Although, this does not answer your question, I hope you consider these modifications.
Edit:
It does look like there is a way to override dbcontext from the documentation
Have you considered creating a view in the database that accounts for the IsRemoved flag?
Select * From <tablename> Where IsRemoved = 0
Then add the view to your DBContext.
Just an idea.

How to keep customer data segregated

As a simplified example I have users, products and customers. Users are allowed access to certain products and to certain customers.
I'm using an edmx-file to map my SQL Server to my code and get the data using linq. A typical query might look something like this:
from prod in ctx.Products
join userProduct in ctx.UserProduct
on prod.Id equals userProduct.ProductId
join user in ctx.UserProfile
on userProduct.UserId equals user.Id
where user.UserName == username // <-- username is a method parameter
select new Product
{
Id = prod.Id,
DisplayText = prod.UserFriendlyText
}
Every time I need data from the database I must join towards the access rights table to EXCLUDE data the user does not have access to. This means that if someone (and it will happen eventually) forget to join towards the access table a user will see too much. Is there a way to INCLUDE data instead so that if I forget the access tables nothing is shown?
I've also been thinking about separating the different customers into different databases as their data will never be related to each other and it will be a small disaster if I leak data between customers. Leaking products between users from the same customer is bad but not as critical.
If it matters I'm in a C# MVC4 CQRS architecture with eventual consistency between the read and write side.
I've checked stack overflow for similar questions but all I could find was this unanswered one:
Access rules in CQRS read model
How about using the Repository pattern, and forcing your Dev's to use it to make calls to the Database? This will promote code reuse and improve the maintainability of the app.
Because a method will be called from the repository you can control the code that interacts with the database, and force consistency, that way you can make sure that the access table is always used, and used as you wish.
I have a similar problem in my database. 90% of my entities are "organisation dependent". My approach uses a generic base repository with methods like this:
public virtual T Find(int id)
{
T e = Context.Set<T>().Find(id);
var od = e as OrganisationDependent;
if (od != null && od.OrganisationID != CurrentOrganisationID)
return null;
if (e == null)
return null;
return e;
}
The "All" method was a particular issue. Solved by How to conditionally filter IQueryable
private static readonly PropertyInfo _OrganisationIDProperty = ReflectionAPI.GetProperty<OrganisationDependent, int>(o => o.OrganisationID);
private static Expression<Func<TOrg, bool>> FilterByOrganization<TOrg>(int organizationId)
{
//The FilterByOrganisation method uses the LINQ Expressions API to generate an expression that will filter on organisation id
//This avoids having to cast the set using .AsEnumerable().Cast<OrganisationDependent>().Where(x => x.OrganisationID == CurrentOrganisationID).AsQueryable().Cast<T>();
//https://stackoverflow.com/questions/20052827/how-to-conditionally-filter-iqueryable-by-type-using-generic-repository-pattern
var item = Expression.Parameter(typeof(TOrg), "item");
var propertyValue = Expression.Property(item, _OrganisationIDProperty);
var body = Expression.Equal(propertyValue, Expression.Constant(organizationId));
return Expression.Lambda<Func<TOrg, bool>>(body, item);
}
public virtual IQueryable<T> All
{
get
{
if (typeof(T).IsSubclassOf(typeof(OrganisationDependent)))
return Context.Set<T>().Where(FilterByOrganization<T>(CurrentOrganisationID));
return Context.Set<T>();
}
}
This closes off most of the places that a user could access someone else's data. But it doesn't filter navigational properties. So I have to add code to all navigation properties on non-organisation dependent entities to do that.
I don't want to separate my data into different database, but one day I will find out if it's feasible to create views filtered by organisation in different schemas - with the same name and structure as my tables, then switch schema according to user.....oh and I want to automatically create them for each new organisation and autmatically migrate them using code-first too....
And you could vote to Allow filtering for Include extension method here
If you're using a CQRS style architecture you can think about having one or more viewmodels per user that contains the products/customers that they have access to.
If you see yourself having to implement logic on the query side of CQRS that is a strong indication that you are doing something wrong.

While attempting to traverse relationships, my entity framework table objects are null

I have the following tables represented in my Entity Framework diagram (.edmx file)
Users
- UserID
- Username
- UserGroupID
Groups
- GroupID
- GroupName
In my code, I retrieve a valid instance of the User object and I'm trying to traverse the relationship to get to the Groups table, to retrieve the GroupName, however everytime, the Groups object is null. The UserGroupID exists in the Groups table, so i'm not sure why this is.
The Visual Studio intellisense knows the relationship exists and allows me to attempt it, but at runtime, the 'Groups' instance is null.
Users users= (Users)e.Row.DataItem;
string groupName = users.Groups.GroupName;
In that case, Groups is null and i'm not sure why. What are the possible causes?
Thanks
Kevin
Looks like you are trying to access Groups from within a databound event and the Groups data was not loaded before you binded it. You most likely need to "include" Groups like below.
var users = entity.Users.include("Groups").where(x => x.UserID == 20);
You might want to read this, Entity doesn't seem to act the same as Linq-To-SQL when it comes to Lazy Loading
http://www.singingeels.com/Articles/Entity_Framework_and_Lazy_Loading.aspx
How to load related items: http://msdn.microsoft.com/en-us/library/bb896272.aspx
probably the lazy loading is disabled.
add the below line when you initialize the context
context.ContextOptions.LazyLoadingEnabled = true;
anyway you should think about what strategy you want to use to pull date from your DB

Fluent NHibernate and multiple key columns

I have a User entity that has a IList property. The mapping for
this looks like this:
HasMany(x => x.Forms)
.Cascade.None()
.AsBag().KeyColumn("Supervisor")
.Key(key => key.Not.Update())
.PropertyRef("Email");
Now I have a new feature request in that essentially adds another
KeyColumn that should ALSO populate into this property. Essentially,
each form can have a "Supervisor" and an "Alternate", and I need this
property to populate any Forms where the User.Email is equal to either
one (Forms BELONG to a User IF the User.Email == Form.Supervisor OR User.Email == Form.Alternate).
Is there a way to do this? Simply adding another KeyColumn
specification simply overrides the previous one, and adding keys via
the Keys() method seems to not allow for multiple key columns like I
want... I'm looking for a way to essentially tell it that this
relationship should be found with a WHERE Superviser = Email OR Alternate = Email, but it doesn't seem to support that OR clause...
My other option is essentially duplicate this mapping, but for "Alternate" in another property, and then slam the collections together in my consuming code, but I wanted to see if NHibernate was smart enough for this to be possible...
Any suggestions?
To my knowledge, no - NHibernate does not support this unusual type of join. I would approach this exactly like you said. Use two collections - User.SupervisorForms and User.AlternateForms, then combine them later, perhaps using LINQ's Concat method to do so. You could define a property to do this concatenation for you:
public virtual IEnumerable<Form> Forms
{
get { return SupervisorForms.Concat(AlternateForms); }
}
If you wish to load a User with a completely initialized Forms collection in a single round-trip to the database, then you can use this approach from the Improving Performace - Multi Query section of the NHibernate documentation:
User user = s.CreateMultiQuery()
.Add("select u from User u left join fetch u.SupervisorForms where u.Id = :id")
.Add("select u from User u left join fetch u.AlternateForms where u.Id = :id")
.SetInt32("id", 123)
.UniqueResult<User>();
This code uses CreateMultiQuery() and HQL - but the approach should work just as well with your choice of query batching syntax: CreateMultiQuery(), CreateMultiCriteria(), or Future() / FutureValue().

Categories

Resources