Entity Framework Code First table relationships - c#

I have a C# app which connects to a Web API which feeds my app some XML data for estate agency listings and agents.
The XML looks something along these lines:
<Snapshot>
<Agents>
<Agent id="838388" firstName="John" surname="Smith"/>
<Agent id="838389" firstName="Jane" surname="Doe"/>
<Agent id="838390" firstName="Mary" surname="Appleton"/>
<Agent id="838391" firstName="Peter" surname="Gill"/>
</Agents>
<Listings>
<Listing id="1737672" officeId="801948" agencyName="Century 21">
<Agents>
<AgentRef id="838388" />
<AgentRef id="838391" />
</Agents>
</Listing>
<Listing id="1737673" officeId="801949" agencyName="Remax">
<Agents>
<AgentRef id="838390" />
<AgentRef id="838389" />
</Agents>
</Listing>
</Listings>
</Snapshot>
I have decided to use using Entity Framework 6.2, code-first approach. So I created these two classes:
public class Agent
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AgentId { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
public virtual ICollection<Listing> Listings { get; set; }
}
and
public class Listing
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ListingId { get; set; }
public int OfficeId { get; set; }
public int AgencyName { get; set; }
public virtual ICollection<Agent> Agents { get; set; }
}
As you can see, it's a many-to-many relationship between Agents and Listings. So one Agent can have zero or more listings associated to him, and one listing can have zero or more agents associated to it.
So, my app reads all the agents in the first tag, and inserts all the agents into the agents table. Then, later, when it reads all the listings, it looks like EF is trying to create those agents again. Obviously, this gives a PRIMARY KEY violation error, as it's trying to add a second agent again with the same ID.
I am using XDocument to parse the XML. This is the bit where I read the AgentRef elements of the listing:
XElement root = xDoc.Root.Elements("Listings").Descendants("Listing");
if (root.Descendants("Agents").Any())
{
List<string> agentRefs = root.Element("Agents").Elements("AgentRef")
.Select(a => a.Attribute("id").Value).ToList();
listing.AgentRefs = agentRefs.Select(int.Parse).ToList();
}
Any ideas how I can tackle this?

If the agent already exists in the DB you must EF tell that by attaching the agent to the context:
using(var myContext = new MyContext)
{
var agent = new Agent() { AgentId = 838388 };
myContext.Agents.Attach(agent);
var listing = new Listing() { ... };
listing.Agents.Add(agent);
myContext.Listings.AddObject(listing);
myContext.SaveChanges();
}

Related

What's the correct way to reference tables using Code First with EF.Core for searching efficiently

Fairly new to EF.Core and I'm having some issues as my tables start getting more complex. Here's an example of what I have defined for my classes. Note ... there are many more columns and tables than what I have defined below. I've paired them down for brevity.
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Active { get; set; }
}
Followed by
public class JournalEntry
{
public int Id { get; set; }
public int UserId { get; set; }
public string Details { get; set; }
public DateTime DateEntered { get; set; }
public virtual User User { get; set; }
}
I want to be able to issue the following query and INCLUDE the User Table so that I can then populate a ViewModel with columns from the User Table without having to do another lookup and also to sort the data while retrieving it:
public IQueryable<JournalEntry> GetByUser(int userId)
{
return _DbContext.JournalEntries.Where(j => j.UserId == userId)
.Include(u => u.User)
.OrderBy(u=> u.User.FirstName)
.ThenBy(j => j.DateEntered);
}
My controller would then have something similar to the following:
public IActionResult List(int userId)
{
var journalEntries = new _dbRepository.GetByUser(userId);
var myViewModel = new MyViewModel();
myViewModel.UserName = ($"{journalEntries.User.FirstName} {journalEntries.User.LastName}");
myViewModel.Entries = journalEntries;
etc ....
return View(myViewModel);
}
I'm loading the user's first and last name in the View Model and whatever other attributes from the various tables that are referenced. The problem that I'm having is that I'm getting errors on the Migration creation "Foreign key constraint may cause cycle or multiple cascade paths." And of course, if I remove the line reading public virtual User User { get; set; } from the JournalEntry class then the problem goes away (as one would expect).
I believe that the way I'm doing the models is incorrect. What would be the recommended way that I should code these models? I've heard of "lazy loading". Is that what I should be moving towards?
Thanks a bunch.
--- Val
Your query returns an IQueryable<JournalEntry> not a JournalEntry.
Change the code to get the user details from the first object:
var myViewModel.UserName = ($"{journalEntries.First().User.FirstName} {journalEntries.First().User.LastName}");
In the line above I'm calling First() on your journal entries collection and that would have a User. Then I can access FirstName and LastName.
Also, don't bother with LazyLoading since you are learning. It could cause select n+1 issues if used incorrectly

Conditional Include in asp.net core and Entity Framework

I just started my first project using asp.net core and for the first time I'm gonna use the code repository for my project in C# and VS 2019.
I create a new Model and it called Comment. This Table can save all of the comments on the project, That mean is user comments in POSTS, SOCIALMEDIA, and etc Areas saved in this table
[Key]
public int CommentId { get; set; }
public eTable Table { get; set; }
public int ContentId { get; set; }
[StringLength(1500)]
public string Notes { get; set; }
public virtual AnalysedMarket AnalysedMarket { get; set; }
ContentID is my foreign key, And my eTable enum type is like bellow:
public enum eTable
{
AnalysedMarket,
Blog,
News,
Migration,
}
I created a new class for AnalysedMarket as well to save Users data from our social media area.
[Key]
public int AnalysedMarketId { get; set; }
[StringLength(255)]
public string Images { get; set; }
public int Hits { get; set; }
public string Notes { get; set; }``
Now I created a method in my code repository for extract data using EF and LINQ to get list of AnalysedMarket data but I can't Include my result with Comment table and result of my code repository in the comment section is null always.
public async Task<IEnumerable<AnalysedMarket>> List(int? page, int? perPage, eStatus? status, string userId)
{
var query = _db.AnalysedMarkets.Select(a => a);
if (status.HasValue)
query = query.Where(m => m.Status.Equals(status));
if (!string.IsNullOrEmpty(userId))
query.Where(m => m.CreatedBy.Equals(userId));
query.Include(a => a.Comments.Where(c => c.Table.Equals(eTable.AnalysedMarket) && c.ContentId == a.AnalysedMarketId));
if (page.HasValue && perPage.HasValue)
return await query.OrderBy(a => a.AnalysedMarketId).ToPagedListAsync(page.Value, perPage.Value);
else
return await query.OrderBy(a => a.AnalysedMarketId).ToListAsync();
}
Actually my question is how can I get list of AnalysedMarket data included by Comment data.
And it has a condition and it says include comment if ContentId is equal to AnalysedMarketId and eTable is Table.AnalysedMarket.
I read the articles about conditional Include but I didn't get any thing of them.
Example 1
Example 2
You need to add a reference from AnalysedMarket to comment like this in your AnalysedMarket-Class:
ICollection<Comment> Comments { get; set; }
And then include them while querying your AnalysedMarkets like this:
var query = _db.AnalysedMarkets.Include(c => c.Comments);
/Edit:
Regarding your comment - for this you would need kind of an hierarchy/inheritance structure. It seems to be supported by EfCore and something like this should work:
public class CommentableItem {
ICollection<Comment> Comments {get;set;}
}
public class Comment {
CommentableItem CommentableItem {get;set;}
}
public class AnalysedMarket : CommentableItem {
}
Than you should be able to use the include for each item inheriting from CommentableItem. I did not use the inheritance feature yet (as far as I know this is quite new for EF Core), so for further instructions check the documentation

SQLite and Entity Framework Core: why include doesn't work?

I use EntityFrameworkCore.SQLite v1.1.3, VS 2015, make simple WPF application, .Net 4.6
People and Orders tables are related as "many-to-many" through OrdersToPeople table in my database. I've made dbContext classes using SQLite Tools.
I use this code to check loaded data:
var list = myDbContext.People
.Include(t => t.OrdersToPeople);
foreach (var element in list)
{
var c = element.OrdersToPeople.Count;
//c is always 0. Why?
}
When i load OrdersToPeople or Orders tables the same way
var list = myDbContext.OrdersToPeople
or
var list = myDbContext.Orders
, i see the data. When i make SQL-query, it returns me correct data.
Why Include does not load OrdersToPeople?
P.s. The OrdersToPeople field is virtual.
public partial class People
{
//...fields...
public virtual ICollection<OrdersToPeople> OrdersToPeople { get; set; }
public People()
{
OrdersToPeople = new HashSet<OrdersToPeople>();
}
}
public partial class OrdersToPeople
{
public long Id{ get; set; }
public long PeopleId { get; set; }
public long OrderId { get; set; }
public virtual People People { get; set; }
public virtual Orders Orders { get; set; }
}
I think the behaviour is expected. Did you try to invoke ToList on your selection?
It looks like EF is ignoring include without ToList:
Entity Framework Core ignoring .Include(..) without .ToList(..) indirectly
var list = myDbContext.People
.Include(t => t.OrdersToPeople);
.ToList();

Entity Framework using Load with Select - is it possible?

I'm trying to optimize my EF queries and I'm stuck with this one.
Let's say I have a model like this:
public class House
{
public int ID { get; set; }
public ICollection<Window> Windows { get; set; }
}
public class Window
{
public int ID { get; set; }
public string Color { get; set; }
public WindowKind Kind { get; set; }
}
public class WindowKind
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
What I would like to do is to explicitly load all windows and to specify what should be populated in WindowKind property.
I know I can do it with .Include() like this:
var house = Context.Houses.Single(h => h.ID == id);
var windows = Context.Entry(house).Collection(h => h.Windows).Query().Include(w => w.Kind).Load();
However, this will create a query that will load all WindowKind properties and I need only Name, for example. I was hoping something like this would work but it does not, Windows collection is empty, although the generated query looks good.
var house = Context.Houses.Single(h => h.ID = id);
var windows = Context.Entry(house).Collection(h => h.Windows).Query().Select(w => { new w.Color, w.Kind.Name }).Load();
Is it possible to have fine grained control when loading child collections?
you can't load only a part of the scalar (int, string,...) properties of an entity by loading the entity.
In you case, something like the following should do:
from
w in Context.Windows
where
w.House.ID == id // here a navigation property is missing, but (imho) more clear for the sample
select new {
windows = w,
kName = w.Kind.Name
}
But in this case you will not get context attached entities.

Troublesome LINQ query/join

Ok I'm not even sure where to begin on this. I have four main tables.
IPACS_Departments (one to many) -> IPACS_Functions (one to many) -> IPACS_Processes (one to many) -> IPACS_Procedures
I have an IPACS_Documents table with a primary key for docID.
I have 4 look up tables.
IPACS_DepartmentDocs -> IPACS_FunctionDocs -> IPACS_ProcesseDocs -> IPACS_ProcedureDocs
Each of those tables have a FK to the IPACS_Document table docID
They also have a FK to my first four tables mentioned on departmentID, functionID, processID, procedureID.
I need to somehow wire these together though a LINQ statement.
For my department view page. I need to show every single document that is in the current department.
For example we have a computer department. That has 2 functions within that department, that has 13 processes within those functions and 41 procedures within those processes.
So on my department view page I need to show all of the documents for that department and it's functions and it's processes and it's procedures.
On my department view page I have access to the departmentID.
Where I am 100% confused is how do I get all of the associated documents using these 9 different tables?
I hope that made sense because my brain is friend trying to think through this.
I'm not sure if I have your model down right, but I think it follows this pattern (assuming Entity Framework, with the descendant entities having mapping properties to allow the heirarchy to be walked):
public class Department
{
[Key]
public int Id { get; set; }
public virtual ICollection<Function> Functions { get; set; }
public virtual ICollection<DepartmentDocument> DepartmentDocuments { get; set; }
}
public class DepartmentDocument
{
[Key]
public int Id { get; set; }
[ForeignKey("Department")]
public int DeptId { get; set; }
[ForeignKey("Document")]
public int DocId { get; set; }
public virtual Department Department { get; set; }
public virtual Document Document { get; set; }
}
public class Document
{
[Key]
public int Id { get; set; }
public virtual DepartmentDocument DepartmentDocument { get; set; }
public virtual FunctionDocument FunctionDocument { get; set; }
}
Assuming a model like this, then you can write the following - I've only included traversing two levels, but the extras just need some extra lines with SelectMany() for the child elements:
public List<Document> GetDocumentsForDepartment(List<Department> departments)
{
var docs = new List<Document>();
foreach (var department in departments)
{
foreach (var ddoc in department.DepartmentDocuments)
{
docs.Add(ddoc.Document);
}
foreach (var fx in department.Functions)
{
foreach (var fdoc in fx.FunctionDocuments)
{
docs.Add(fdoc.Document);
}
}
}
return docs;
}
Which simplifies to:
public List<Document> GetDocumentsForDepartment2(List<Department> departments)
{
var docs = new List<Document>();
foreach (var department in departments)
{
docs.AddRange(department.DepartmentDocuments.Select(ddoc => ddoc.Document));
docs.AddRange(department.Functions.SelectMany(fx => fx.FunctionDocuments, (fx, fdoc) => fdoc.Document));
}
return docs;
}
This might be OK, the scheme uses more than one DB call (if you are using EF and not Linq to Objects). If that sucks, then maybe you need to put a view in the DB.
I couldn't think how to write this as a single linq query, so maybe this is just a starting point for further work.
This is a pretty simple follow-up to your earlier question though. When you're looking to aggregate child data, you need SelectMany().

Categories