c# Entity Framework Eager Loading - Not Working for Me - c#

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;

Related

Retrieving data from related tables with EF 6 and 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

Linq join with Many to Many

I'm using MVC, C# and EntityFramework.
I've seen different solutions on Many to Many joins and after a lot of tinkering I got it to work in Linqpad. But when I try it in my solution I get an error because one of the tables isn't in my DBContext.
I have two visible tables and one hidden. Items, Recipes & RecipeItems.
All recipes are based on one item and use two or more items to be made.
So I want a list, IEnumerable or similar with the data from both Items and Recipes that specifies this recipe and then I want all the items needed to make the recipe.
The following query works in LinqPad
var t = from r in Recipes
join i in Items on r.ItemId equals i.Id
select new {FinalProduct = r.FinalProduct, Effect= i.Effect,
Description = r.Description, Ingredients = r.RecipeItems.Select(g => g.Item)};
When I do this in my solution I get the error since my DBContext only contains Recipe and Items but no RecipeItems. Entityframework handles this without me I guess.
I tried to make a DbSet<RecipeItems> without any luck. Any of you who have a suggestion of how I can move forward.
Item Class
public class Item
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Effect { get; set; }
public bool Published { get; set; }
public virtual ICollection<Recipe> Recipe { get; set; }
}
Recipe Class
public class Recipe
{
[Key]
public int Id { get; set; }
public int ItemId { get; set; }
[Display(Name = "Final Product")]
public string FinalProduct { get; set; }
public string Description { get; set; }
public RecipeGroup RecipeGroup { get; set; }
public bool Published { get; set; }
public virtual ICollection<Item> Ingredients { get; set; }
}
The ItemId in Recipe is to set the actual Item the Recipe will make.
Try adding this to your Recipe object:
public Recipe()
{
this.Ingredients = new HashSet<Item>();
}
This overrides the default constructor for the class and kind of gives EF a place that initializes the related objects.

Entity Framework 6 and navigational property issue

Im hoping to get your help here. Im very new to EF and am having some troubles. I am using the Database First approach and have a database in Azure that I have to retreive data from.
[DataContract]
[Table("A")]
public class AgencyDC
{
[DataMember]
[Key]
public string AID { get; set; }
public string AName { get; set; }
public string GeneralEmailAddress { get; set; }
public string WebsiteURL { get; set; }
[DataMember]
[ForeignKey("AID")]
[IgnoreDataMember]
public virtual AExtensionDC AExtension { get; set; }
}
[DataContract]
[Table("AExtension")]
public class AExtensionDC
{
[DataMember]
[Key]
public string AID { get; set; }
[DataMember]
public bool? IsActive { get; set; }
public bool? IsOptedOut { get; set; }
public DateTime? LastUpdated { get; set; }
}
I am trying to use EF6 to retreive my records using DBSets in my context..
public List<ADataCcontract> GetAllAs()
{
using (AContext _aCtx = new AContext())
{
var mylist = _aCtx.A.Include("AExtension").ToList();
return mylist;
}
}
Now, I should be getting back 547 records back with only 1 of them having the AExtension navigational property having content within. The other 546 records should contain NULL. However, for some reason, I am only getting what appears to be a record that has a match in both tables. In SQL speak, I kind of just want a left join so that I return ALL rows from AE entity and OPTIONALLY matches in AE.
I hope this makes sense.
If possible, if you have a fix, could you please post an example I could referent? I am really stuck.
I think this SO Answer might get you most of the way? https://stackoverflow.com/a/4299667/78551
Basically Include does a left outer join or left join as 'outer' is actually optional in SQL.
A left join / inner join will be performed by ´.Include´ if your fields have/lack of nullability.
To review your query put a breakpoint and check this value:
var myQuery = _aCtx.A.Include("AExtension").ToTraceString();

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.

MVC - Linq - Populate List<T> with Records in Another Table

I'm prototyping my first MVC application, it's a simple forum. I've done part of the domain model and I'm trying to figure out how to do something that's pretty basic in SQL alone, but I can't figure it out in my application. Here are my Entities:
[Table(Name="Users")]
public class User
{
[Column(IsPrimaryKey=true, IsDbGenerated=true, AutoSync=AutoSync.OnInsert)]
public int Id { get; set; }
[Column] public string Username { get; set; }
[Column] public string Password { get; set; }
[Column] public string PasswordHash { get; set; }
[Column] public string PasswordSalt { get; set; }
[Column] public string FirstName { get; set; }
[Column] public string LastName { get; set; }
public List<Forum> AllowedForums { get; set; }
[Column] public DateTime LastLogin { get; set; }
[Column] public DateTime MemberSince { get; set; }
}
[Table(Name="Forums")]
public class Forum
{
[Column(IsPrimaryKey=true, IsDbGenerated=true, AutoSync=AutoSync.OnInsert)]
public int Id { get; set; }
[Column] public int ParentId { get; set; }
[Column] public string Title { get; set; }
[Column] public string Description { get; set; }
[Column] public bool IsGlobal { get; set; }
[Column] public int DisplayOrder { get; set; }
}
I also have a linking table called AllowedForums that looks like:
userid forumid
1 4
In order to select the forums that a user is allowed to view and forums where IsGlobal == true I'd do this in SQL:
SELECT * FROM Forums
LEFT OUTER JOIN AllowedForums ON Forums.id = AllowedForums.Forumid
WHERE AllowedForums.Userid = 1
OR Forums.IsGlobal = 1
How should I populate the
public List<Forum> AllowedForums
field using C#/Linq to SQL?
Should AllowedForum be a value object with its own table mapping? That seems like overkill but I could easily join on it. I looked briefly at EntitySet but the simple example I saw didn't seem to fit. It feels like there should be an elegant way to get a collection of Forum objects for each User, but I can't come up with any. BTW, I'm new to C# & OO. I should also mention that since these are the early stages of the app, I'm open to changing the structure/relationships of the entities or tables if there's a better approach I'm not seeing.
You should have another Entity class (probably should be internal) that mirrors your AllowedForums table in the database. Now I'm assuming your User table and your Forums table both have PK/FK relationships to this AllowedForums table. Therefore, there should be an internal property on the User class that looks like this:
internal EntitySet<AllowedForums> AllowedForumsRelationships
{
get;set;
}
Or something like that. This should be on both the User and Forums class. Your AllowedForums class will have two properties on it. One for User and one for Forum. (If you use the LINQ to SQL designer, all this will happen for you automatically).
Once you have that, if you want to get all the AllowedForums for a user you can do something like this:
public IList<Forum> AllowedForums
{
get
{
var result = new List<Forum>();
foreach(var relationShip in this.AllowedForumsRelationships)
{
result.Add(relationShip.Forum);
return result;
}
}
}
This is some rough code I just banged out, and I'm not sure it's 100% accurate, but I think you'll get the idea. Basically you're dealing with a many to many relationship which is always a pain.
EDIT: I just messed with this idea with the Northwind Database with these tables:
Orders
OrderDetails
Products
There's a many to many relationship there: An order can have multiple products, and a product can belong to many orders. Now say you want to get all products for an order:
public partial class Order
{
public IList<Product> Products
{
get
{
var list = new List<Product>();
foreach (var item in this.Order_Details)
{
list.Add(item.Product);
}
return list;
}
}
}
That works, so it should work in the scenario you're talking about as well.
Something like:
var AllowedForums = from f in ForumsTable
join af in AllowedForumsTable on f.Id equals af.forumid into temp
from aft in temp.DefaultIfEmpty()
where (f.IsGlobal == 1 || aft.userid == 1)
select f;

Categories