Retrieving data from related tables with EF 6 and C# - c#

I'd like to retrieve data from my DB with two tables (Scientists and Countries).
This is how i programmed my class
public class Scientist
{
public long ScientistID { set; get; }
public string Name { set; get; }
public string Surname { set; get; }
public string BornDate { set; get; }
public string Subject { set; get; }
public long? CountryID { set; get; }
public virtual Country Country { set; get; }
}
public class Country
{
public long CountryID { set; get; }
public string CountryName { set; get; }
public string Zone { set; get; }
public virtual List<Scientist> Scientists { set; get; }
}
public class PeopleOfScienceContext : DbContext
{
public PeopleOfScienceContext() : base("ScientistsConnectionString")
{
Database.SetInitializer<PeopleOfScienceContext>(new CreateDatabaseIfNotExists<PeopleOfScienceContext>());
}
public DbSet<Scientist> Scientists { get; set; }
public DbSet<Country> Countries { get; set; }
}
Is there a "simple" way to download from DB both content since they're correlated?
I mean, with something like:
List<Scientist> scients = ctx.Scientists.ToList(); //ctx was initialized don't worry!
I can download all my data from Scientist table, but i cannot download the "CountryName" info since it is stored in another table and it remains blank.
I'd like to avoid to create a "JOIN" query; since i'm learning a Frame Work i've been told to "write the bare minimum" code. My second idea was to download both tables and merge them "client side", but still seems an useless complication of the task (and i bet this method couldn't scale well with big tables). Am I missing the simplest solution or there isn't such a thing in EF 6?

I believe the concept you are looking for is Eager Loading.
Although you could explicitly join the Scientist and Country tables in a Linq Query, because you already have the navigation property defined, you should be able to simply Include the navigation:
List<Scientist> scientists = ctx.Scientists
.Include(s => s.Country)
.ToList()
... or the async equivalent (since this is I/O bound work)
var scientists = await ctx.Scientists
.Include(s => s.Country)
.ToListAsync();
And you should now be able to dereference the country like so:
scientist.Country.CountryName
In the event of the relationship being optional (i.e. CountryId can be NULL in the database), then you could use the nullsafe dereference operator:
scientist.Country?.CountryName

Related

c# Entity Framework Eager Loading - Not Working for Me

not sure what I've done wrong. I cannot, no matter what I do, and what posts I try to follow, get EF Eager loading to work. I don't know If I have done something wrong well before hand... but nothing works. Not any style of include, or disabling lazy loading... i just cannot get it.
I have an Agent model
public class Agent
{
[Key]
public int AgentId { get; set; }
[Required]
[StringLength(50)]
public string Username { get; set; }
[Required]
[StringLength(100)]
public string FirstName { get; set; }
[Required]
[StringLength(100)]
public string LastName { get; set; }
Then a prducer model
public class Producer
{
[Key]
public int ProducerId { get; set; }
[Required]
[StringLength(254)]
public string Name { get; set; }
public int StreetNumber { get; set; }
[StringLength(254)]
public string StreetName { get; set; }
[StringLength(254)]
public string City { get; set; }
public int StateId { get; set; }
public virtual State State { get; set; }
public int CountryId { get; set; }
public virtual Country Country { get; set; }
And then i have another model/table to link the two
public class AgentProducer
{
public int AgentProducerId { get; set; }
public int ProducerId { get; set; }
public int AgentId { get; set; }
public virtual Producer Producer { get; set; }
public virtual Agent Agent { get; set; }
}
The goal here, would be that when i Query thr AgentProducer table, my Producer property would have the entire producer object, and that producer object would have the Country and State objects.
I dont think this matters but the producer does have FK constraints on the countryId and stateId
My setup was from a lesson i was following, where i have a repository wrapper holding everything
public class RepositoryContext : DbContext
{
public RepositoryContext(DbContextOptions<RepositoryContext> options) : base(options)
{
}
public DbSet<Producer> Producers { get; set; }
public DbSet<Agent> Agents { get; set; }
}
The above item is held in a repository wrapper, that has a repository implementing a specific interface for each model type in it.
The problem Im having is whether i query with method syntax or LINQ, i cannot get everything to be included in the query, and end up having to do a bunch of extra steps to get certain info.
So far the closest i got was with LINQ, in which I would get the AgentProducer item to return with the Producer object set - however inside that producer, the COuntry and State were null, and I need that info.
I tried things like:
*** (Worth noting "AgentProducer" here is a DBSet... not sure if thats correct or not? I dont seem to have options like "ThenInclude" that ive seen in other answers.
_repoWrapper.Repo.AgentProducer.Include(i => i.Producer).Where(i => i.AgentId == filter.AgentId);
This gives me absolutely nothing - even the producer is null - before even making it to my country/state problem.
I tried
var res = _repoWrapper.Repo.AgentProducer.Include("Producer").Where(i => i.AgentId == filter.AgentId);
Same null result.
(from ap in _repoWrapper.Repo.AgentProducer.Include(i => i.Producer)
where ap.AgentId == filter.AgentId
select ap);
Same null.
The only thing that has even minorly worked was:
var res = (from ap in _repoWrapper.Repo.AgentProducer
join p in _repoWrapper.Repo.Producers on ap.ProducerId equals p.ProducerId
join a in _repoWrapper.Repo.Agents on ap.AgentId equals a.AgentId
join c in _repoWrapper.Repo.Country on ap.Producer.Country.Name equals c.Name
join s in _repoWrapper.Repo.State on ap.Producer.State.Name equals s.Name
where ap.AgentId == filter.AgentId
orderby p.Name descending
select new AgentProducer
{
AgentProducerId = ap.AgentProducerId,
Producer = p
});
Which fills out the producer, because of the manual join and set on the object. However, A) This isnt really eager loading, and B) using this method I have no idea how i can set the country and state objects on the producer here, as they still show null. And in the object initializer i cant just assign the country and state.
Ive browsed countless approaches and I cannot get a single thing to work... so i feel like I have done something wrong earlier in the process but I have no clue what.
Can anyone help me out?
Looks like it was me being a tool.
I had not grabbed the nuget package for EntityFrameworkCore.Relational, and had not added
using Microsoft.EntityFrameworkCore;
instead I had:
using System.Data.Entity;

Entity Framework returns null for Include properties

I got 3 entities(tables) that have many to many connections:
public class AccUserRole
{
public long Id { get; set; }
public string RoleName { get; set; }
public List<AccAdGroup> Groups { get; set; }
public List<AccScreen> Screens { get; set; }
}
public class AccAdGroup
{
public long Id { get; set; }
public string AdIdent { get; set; }
public List<AccUserRole> Roles { get; set; }
}
public class AccScreen
{
public long Id { get; set; }
public string ScreenIdent { get; set; }
public List<AccUserRole> Roles { get; set; }
}
I wanted to get all Roles(including their screens and groups) that has at least one of specified list of groups(the groups of the current user). So I used this query:
List<AccUserRole> userRoles = (from ur in db.AccUserRoles.Include("Groups").Include("Screens")
from g in ur.Groups
where user.Groups.Contains(g.AdIdent)
select ur).ToList();
It gets the right roles, but the Groups and Screens properties are null. Looks like EF has a problem with using Include and second from.
Any help on how to include the properties or rewrite the query will be appreciated.
Eager Loading
The reason for this is that you have specified only one level of include, while your query is asking for something on the second level.
Your include lets you ask for ur.Groups and ur.Screens.
The next level is from g in ur.Groups, and you haven't included that level. (This is probably unexpected for you, since you already have asked for all AccUserRoles in the first part of the query.)
To make your query run, you could add another .include at the start, going two levels deep:
from ur in db.AccUserRoles
.Include("Groups")
.Include("Groups.Roles")
.Include("Screens")
If you need to go yet another level, you'd just add yet another include:
from ur in db.AccUserRoles
.Include("Groups")
.Include("Groups.Roles")
.Include("Groups.Roles.Groups")
.Include("Screens")
...etc.
This might become cumbersome if you have a whole lot of levels to nest, so an alternative would be to use Lazy Loading instead, as Praval 'Shaun' Tirubeni suggests, by adding the virtual keyword to the collections in your entities.
Move the include before ToList().
select ur).Include("Groups").Include("Screens").ToList();
The subselect can remove the Include effect.
If you are doing eager loading, the virtual keyword is not needed.
By adding virtual, you are using lazy loading, not eager loading.
Try adding the virtual key word to your class properties like so:
public class AccUserRole
{
public long Id { get; set; }
public string RoleName { get; set; }
public virtual List<AccAdGroup> Groups { get; set; }
public virtual List<AccScreen> Screens { get; set; }
}
public class AccAdGroup
{
public long Id { get; set; }
public string AdIdent { get; set; }
public virtual List<AccUserRole> Roles { get; set; }
}
public class AccScreen
{
public long Id { get; set; }
public string ScreenIdent { get; set; }
public virtual List<AccUserRole> Roles { get; set; }
}

How can I do an EF Linq query, including a subset of related entities

I have the following classes:
public class Problem
{
public Problem()
{
this.Questions = new HashSet<Question>();
this.Solutions = new HashSet<Solution>();
}
public int ProblemId { get; set; }
public string Title { get; set; }
public string Note { get; set; }
public virtual ICollection<Question> Questions { get; set; }
public virtual ICollection<Solution> Solutions { get; set; }
}
public class Question
{
public int QuestionId { get; set; }
public int ProblemId { get; set; }
public int QuestionStatusId { get; set; }
public string Note { get; set; }
public virtual Problem Problem { get; set; }
}
public class Solution
{
public int SolutionId { get; set; }
public int Number { get; set; }
public int ProblemId { get; set; }
public bool? Correct { get; set; }
public string Text { get; set; }
public string Note { get; set; }
public virtual Problem Problem { get; set; }
}
Can someone help me with LINQ for my EF6,1 SQL Server 2012.
What I would like to do is to get a List that contains only a subset of the data. In this case I would like the Notes properties in Problem, Question and Solution Entities to not be fetched from the database.
Note the Question and Solution tables are connected to the Problem table. I'm not 100% sure of this but I think this means I don't need to add an .Include.
Ideally I would like the selects that EF causes to be issues to not include the Notes column.
You can use table splitting feature of EF. Create a Problem(PK+all fields except for Notes) and a ProblemNotes(PK+Notes) entities. Then querying against Problem should satisfy your needs.
http://msdn.microsoft.com/en-us/data/jj715645.aspx
With Entity Framework table splitting you can separate the properties that might contain very large amount of data into a separate entity and only load it when required.
You might use .Select(...) to make avoid fetching redundantly data from db. The code below illustrates how to fetch list of Problems with only ProblemId and Title fields:
var result = context.Problems.Select(problem => new { ProblemId = problem.ProblemId , Title = proble.Title }).ToList();
Using of .Select above will generate SQL query "SELECT p.ProblemId,p.Title from dbo.Problems as p".
Using of .List will retrieve data (it will not be dependent on context anymore )
Your might cast resulted set to Problem type ,eg:
var newResult = result.Select(x=>new Problem() { ProblemId = x.ProblemId, Title = x.Title } )

Entity Framework 6 - Multiple lookup inserts vs An object with the same key already exists in the ObjectStateManager

I am apparently having a real devil of a time understanding Entity Framework 6 which I am using with ASP.NET MVC 5.
The core of the matter is that I have a really quite simple data model that is typical of any real world situation where I have various business objects that have other business objects as properties (and of course they child objects may in turn have other child business objects) and also various types of lookup/type data (Country, State/Province, LanguageType, StatusType etc.) and I cannot figure out how to save/update it properly.
I keep going back and forth between two error states:
1) I either run into the situation where saving a parent business object results in unwanted duplicate values being inserted into my lookup/type tables (for example saving a business object that has been assigned an existing LanguageType of 'English' will result in another LanguageType for 'English' being inserted into the LanguageType table), or
2) I use some of the suggestions I've seen here and elsewhere on the net (e.g. Saving Entity causes duplicate insert into lookup data, Prevent Entity Framework to Insert Values for Navigational Properties ) to solve issue 1 and then find myself fighting against this same issue: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key .
I will now provide a few code snippets to help build the picture of what I am trying to do and what I am using to do it. First, an example of the entities involved:
public class Customer : BaseEntity
{
public string Name { get; set; }
[LocalizedDisplayName("Contacts")]
public virtual List Contacts { get; set; }
}
public class Contact : BaseEntity
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public int? LanguageTypeID { get; set; }
[Required]
[ForeignKey("LanguageTypeID")]
public virtual LanguageType Language { get; set; }
}
public class LanguageType : Lookup
{
[LocalizedDisplayName("CultureName")]
public string CultureName { get; set; }
}
public class Lookup : BaseEntity
{
public string DisplayName { get; set; }
public int DisplayOrder { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class BaseEntity
{
public int ID { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public DateTime? DeletedOn { get; set; }
public bool Deleted { get; set; }
public bool Active { get; set; }
public ApplicationUser CreatedByUser { get; set; }
public ApplicationUser UpdatedByUser { get; set; }
}
In my controller, I have some code like the following:
foreach(Contact contact in lstContacts)
{
customer.Contacts.Add(contact);
}
if (ModelState.IsValid)
{
repository.Add(customer);
}
Let us suppose that each of the contacts has the same LanguageType of 'English' assigned (and in this example it is the fact that I am trying to save multiple contacts that have the same LanguageType that triggers the ObjectStateManager error). Initially, the repository.Add() code just did a context.SaveChanges() which did not work as expected, so now it looks something like this (Entity variable is a Customer):
try
{
if(Entity.Contacts != null)
{
foreach(Contact contact in Entity.Contacts)
{
var entry = this.context.Entry(contact.Language);
var key = contact.Language.ID;
if (entry.State == EntityState.Detached)
{
var currentEntry = this.context.LanguageTypes.Local.SingleOrDefault(l => l.ID == key);
if (currentEntry != null)
{
var attachedEntry = this.context.Entry(currentEntry);
//attachedEntry.CurrentValues.SetValues(entityToUpdate);
attachedEntry.State = EntityState.Unchanged;
}
else
{
this.context.LanguageTypes.Attach(contact.Language);
entry.State = EntityState.Unchanged;
}
}
}
}
context.Customers.Add(Entity);
context.SaveChanges();
}
catch(Exception ex)
{
throw;
}
Is it fundamentally wrong to expect this to have worked? How am I supposed to save and example like this? I have similar problems saving similar object graphs. When I look at tutorials and examples for EF, they are all simple and they all just call SaveChanges() after doing something very similar to what I am doing here.
I've just recently been using the ORM capabilities of ColdFusion (which is hibernate under the covers) and there are would simply load the LanguageType entity, assign it to the Contact entity, save the Contact entity, assign it to the Customer and then save the Customer.
In my mind, this is the most basic of situations and I cannot believe that it has caused me so much pain - I hate to say it, but using plain old ADO.NET (or heaven forbid, ColdFusion which I really don't enjoy) would have been MUCH simpler. So I am missing SOMETHING. I apparently have a key flaw in my understanding/approach to EF and If somebody could help me to make this work as expected and help me to figure out just where my misunderstanding lies, I would greatly appreciate it. I have spend too many hours and hours on this and it is a waste of time - I have/will have countless examples just like this one in the code I am building so I need to adjust my thinking with respect to EF right now so I can be productive and do approach things in the expected way.
Your help will mean so much and I thank you for it!
Let's consider the following object graph in which a teacher instance is the root object,
Teacher --[has many]--> courses
Teacher --[Has One]--> Department
In entity framework's DbContext, each instance of an object has a State indicating whether the object is Added, Modified, Removed or Unchanged. What happens apparently is the following :
Creating the root object for the first time
In this case, in addition to the newly created root object Teacher, ALL the child objects in the graph will have the State Added as well even if they're already created. The solution for this problem is to include the foreign key property for each child element and use it instead, i.e. Teacher.DepartmentId = 3 for example.
Updating the root object and one of its child elements' properties
Suppose you fetch a teacher object from the db, and you change the Teacher.Name property as well as the Teacher.Department.Name property; in this case, only the teacher root object will have the State marked as Modified, the department's State on the other hand remains Unchanged and the modification won't be persisted into DB; Silently without any warning.
EDIT 1
I used your classes as follows and I don't have a problem with persisting the objects :
public class Customer : BaseEntity
{
public string Name { get; set; }
public virtual List<Contact> Contacts { get; set; }
}
public class Contact : BaseEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int? LanguageTypeID { get; set; }
public Customer Customer { get; set; }
[ForeignKey("LanguageTypeID")]
public LanguageType Language { get; set; }
}
public class LanguageType : Lookup
{
public string CultureName { get; set; }
}
public class Lookup : BaseEntity
{
public string DisplayName { get; set; }
public int DisplayOrder { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class BaseEntity
{
public int ID { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public DateTime? DeletedOn { get; set; }
public bool Deleted { get; set; }
public bool Active { get; set; }
public ApplicationUser CreatedByUser { get; set; }
public ApplicationUser UpdatedByUser { get; set; }
}
public class ApplicationUser
{
public int ID { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
And used the following Context :
public class Context : DbContext
{
public Context() : base("name=CS") { }
public DbSet<Customer> Customers { get; set; }
public DbSet<Contact> Contacts { get; set; }
public DbSet<LanguageType> LanguageTypes { get; set; }
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//I'm generating the database using those entities you defined;
//Here we're demanding not add 's' to the end of table names
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Then I created a unit tests class with the following :
[TestMethod]
public void TestMethod1()
{
//our context
var ctx = new Infrastructure.EF.Context();
//our language types
var languageType1 = new LanguageType { ID = 1, Name = "French" };
var languageType2 = new LanguageType { ID = 2, Name = "English" };
ctx.LanguageTypes.AddRange(new LanguageType[] { languageType1, languageType2 });
//persist our language types into db before we continue.
ctx.SaveChanges();
//now we're about to start a new unit of work
var customer = new Customer
{
ID = 1,
Name = "C1",
Contacts = new List<Contact>() //To avoid null exception
};
//notice that we're assigning the id of the language type and not
//an object.
var Contacts = new List<Contact>(new Contact[] {
new Contact{ID=1, Customer = customer, LanguageTypeID=1},
new Contact{ID=2, Customer = customer, LanguageTypeID=2}
});
customer.Contacts.AddRange(Contacts);
//adding the customer here will mark the whole object graph as 'Added'
ctx.Customers.Add(customer);
//The customer & contacts are persisted, and in the DB, the language
//types are not redundant.
ctx.SaveChanges();
}
It all worked smoothly without any problems.
As far as i know there is no build in support for reattaching modified graphs (like the SaveOrUpdate method of nHibernate). Perhaps this or this can help you.

How do I use EF6 with Database First and existing views?

I'm an EF noob (any version) and my Google-foo has failed me on finding out how to do this. Which makes me think I must be doing this wrong, but here is the situation:
I'm definitely in an environment that is database first and the schema won't be updated by us coders. I'm also not a fan of 'automatic' code generation, so I've stayed away from the designer or the EF powertools (though I did run through them just to see them work).
To learn I imported the Northwind DB into my LocalDB to have something to play with while creating some simple Web API 2 endpoints. This all went well as I created slimmed down models of the Employees, Shippers, & Region tables in Northwind. Region was particularly interesting as it wasn't plural and EF had issues with that. Anyway, I got by that.
My trouble now is; I want to use a view instead of a table as my source and whatever I'm doing just doesn't seem to work. What I tried was setting it up just like I did the tables. But that produces a ModelValidationException error. I tried looking at the auto-generated code from the designer, but got no insight.
My models:
//-- employee, shipper, & region work as expected
public class employee {
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
public class shipper {
public int ShipperID { get; set; }
public string CompanyName { get; set; }
public string Phone { get; set; }
}
public class region {
public int RegionID { get; set; }
public string RegionDescription { get; set; }
}
//-- invoice is a view (actual viewname is 'Invoices')
//-- so i followed the same rules as i did for employee & shipper
//-- i have tried uppercase 'I' as well as a plural version of the model
public class invoice {
public string CustomerID { get; set; }
public string CustomerName { get; set; }
public string Salesperson { get; set; }
public int OrderID { get; set; }
public int ProductID { get; set; }
public string ProductName { get; set; }
}
My Context looks like this:
public class NorthwindDBContext : DbContext {
public DbSet<Employee> Employees { get; set; }
public DbSet<shipper> Shippers { get; set; }
public DbSet<region> Regions { get; set; }
public DbSet<Invoice> Invoices { get; set; } //-- offending line of code
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//--- fix for Region being singular instead of plural
modelBuilder.Entity<region>().ToTable("Region");
}
}
If I comment out the public DbSet<Invoice> Invoices { get; set; } line in the context everything works. Just by having the line present (even if i don't reference the Invoices property) I receive the ModelValidationException error when using the context in anyway.
Can anybody tell me what I'm doing wrong here?
Thanks.
Update: I tried this in one of my controllers, but I am too noob'ish to know if this is the right path either, though it worked as far as getting records.
using (var dbContext = new NorthwindDBContext()) {
return dbContext.Database.SqlQuery<Invoice>("select * from invoices").ToList();
}
Code-first conventions will look for an ID or InvoiceID property to use as a key. Your Invoice model has neither, while the others do. This is the specific reason your code is failing.
The less-specific one is that you can't have entities in EF which lack a unique key. If you can, have the view define a key. Otherwise, you may still be able to work around the issue.

Categories