Entity framework one to many - c#

I am using Entity Framework and have the following classes
class Student
{
[Key]
public virtual int StudentID {get; set;}
public virtual string StudentName {get; set;}
public virtual ICollection<Note> Notes {get; set;}
}
class Note
{
[Key]
public virtual int NoteID {get; set;}
public virtual int StudentID {get; set;}
public virtual string Message {get; set;}
}
class StudentDBContext:DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Note> Notes { get; set; }
}
So to summarize, I have a class of students who can each have many notes. Now, I want to use Linq to retrieve and display all the notes for a particular student. So I try
using (StudentDBContext a = new StudentDBContext())
{
var b = from c in a.Student
where c.StudentID == 1001
select c;
var currStudent = b.FirstOrDefault();
Console.WriteLine(currStudent.StudentName);
//display all the messages of the current student
foreach (var currNote in currStudent.Notes)
Console.WriteLine(currNote.Message);
}
In the above code, my foreach block always fails because Student.Notes is always null. Am I missing some step in initializing Student.Notes and populating it from the database?

Your class Student and Note should be public.
The following code runs:
class Program {
static void Main(string[] args) {
using ( StudentDBContext efc = new StudentDBContext()) {
foreach (var v in efc.Students) {
Console.WriteLine("{0}", v.StudentName);
foreach (var vv in v.Notes) {
Console.WriteLine(" {0}", vv.Message);
}
}
}
}
}
public class Student {
public Student() {
//Notes = new List<Note>();
}
[Key]
public int StudentID {get; set;}
public virtual string StudentName {get; set;}
public virtual ICollection<Note> Notes {get; set;}
}
public class Note {
[Key]
public int NoteID {get; set;}
public int StudentID {get; set;}
public string Message {get; set;}
}
class StudentDBContext:DbContext {
public DbSet<Student> Students { get; set; }
public DbSet<Note> Notes { get; set; }
}

try this:
using (StudentDBContext a = new StudentDBContext())
{
var b = ((from c in a.Student
join b in a.Notes on b.StudentId equals c.StudentId into sNotes
from notes in sNotes.DefaultIfEmpty()
where c.StudentId==1001
select c).SingleOrDefault();
if (b != null )
...
}
Be aware of the fact that Entity Framework will do a lazy load of your data. So if you don't do something to populate it then you will not get it. There are other ways of doing it but among other things this query clearly documents that you are wanting both the student and notes.

This shouldn't be virtual.
public virtual NoteID {get; set;}
It should be an integer:
public int NoteID {get; set;}

Related

One select list consisting of two classes

I have 3 classes
public class A
{
public decimal id {get; set;}
public virtual B? BNavigation {get; set;};
public virtual C? CNavigation {get; set;};
}
public class B
{
public int id {get; set;}
public string title {get; set;}
[...]
}
public class C
{
public int id {get; set;}
public string title {get; set;}
[...]
}
I try example above, string concatenation, tried to do it with LINQ.
I realized that I need to do this through an auxiliary class.
And i want to make SelectList by class A
Example:
ViewData["A"] = new SelectList( A,"Id", "BNavigation.title - CNavigation.title");
How can i do it ?
I want this post to be closed, because i found block of code which decide my problem
ViewBag.PackageId = db.Packages.Where(p => p.status == "A")
.Select(p => new SelectListItem
{
Text = p.u_package_id + "-" + p.package_nme,
Value = p.u_package_id.ToString()
});

Building includes map using EF Core

Hi I have a simple database, and what I am trying to do is build simple include maps as string using eager loading mechanism in EF CORE.
So in other words mu db models looks like:
And models that are supporting them:
public class StartTable
{
public int Id {get; set;}
public ICollection<TableA> TableA {get; set;}
public ICollection<TableB> TableA {get; set;}
}
public class TableA
{
public int Id {get; set;}
public StartTable StartTable {get; set;}
public int StartTableId {get; set;}
public TableAChild TableAChild {get; set;}
public int TableAChildId {get; set;}
public TableAB TableAB {get; set;}
public int TableABId {get; set;}
}
public class TableB
{
public int Id {get; set;}
public StartTable StartTable {get; set;}
public int StartTableId {get; set;}
public TableBChild TableBChild {get; set;}
public int TableBChildId {get; set;}
public TableAB TableAB {get; set;}
public int TableABId {get; set;}
}
public class TablAChild
{
public int Id {get; set;}
public TableA TableA {get; set;}
}
public class TableBChild
{
public int Id {get; set;}
public TableB TableB {get; set;}
}
public class TableAB
{
public int Id {get; set;}
public TableA TableA {get; set;}
public TableB TableB {get; set;}
}
I think relations are readible from models. Now I just want to create a map, that is I want to select start table and with include of all branches so final include path should looks like:
_context.StartTable.Include("StartTable.TableA.TableAChild")
_context.StartTable.Include("StartTable.TableA.TableAB")
_context.StartTable.Include("StartTable.TableB.TableBChild")
_context.StartTable.Include("StartTable.TableB.TableAB")
And If I type this manually it works, but this will grow a lot so I don't want to update this every time something will come up, I tried AutoInclute() in context on main table but it includes only 1 level down.
I thought I can create some sort of map function that looks like:
private static IEnumerable<string> BuildIncludeTree(DbContext context, Type type)
{
var entityAssemblyTypes = Assembly.GetExecutingAssembly().GetReferencedAssemblies().SelectMany(assembly => Assembly.Load(assembly).GetTypes());
void AddAssetByString(ref HashSet<string> navigation, List<string> createdPaths)
{
foreach (var path in createdPaths)
{
var splitPath = path.Split('.');
var relationNavigationNode = splitPath.Last();
var parentNavigationType = entityAssemblyTypes.FirstOrDefault(t => t.Name == relationNavigationNode);
if (parentNavigationType == null)
{
throw new ArgumentException($"Unknown type parent: {relationNavigationNode}");
}
var parentNodesProperties =
parentNavigationType.GetProperties().Where(prop => !prop.PropertyType.IsSimple() && !splitPath.Contains(prop.Name)).ToArray();
if (!parentNodesProperties.Any())
{
navigation.Add(path);
continue;
}
navigation.Add(path);
AddAssetByString(ref navigation, parentNodesProperties.Select(prop => $"{path}.{prop.Name}").ToList());
}
}
IEntityType entityType = context.Model.FindEntityType(type);
if (entityType == null) throw new ArgumentException($"Unknown entity type {type.Name}");
var navigationsByString = new HashSet<string>();
var relationsByString = entityType.GetNavigations().Select(nav => $"{type.Name}.{nav.Name}");
AddAssetByString(ref navigationsByString, relationsByString.ToList());
return new List<string>();
}
But problem here is relation to TableAB, I mean when I get to mapping this part function goes circular and creates map:
StartTable.TableA.TableAChild.TableB.StartTable.TableA ... and so on
Can this be prevented and what am I missing?
Can EF Core detect in some sort of way navigation downwards and upwards?
Or is there any other and simpler way to do that?
You can't Include all .There is already post about that here
Is there a way to Include() all with dbcontext?
But if correctly understand you can use Linq to make you code shorter like:
_context.StartTable.Include(x => x.TableA.TableAChild && x.TableA.TableAB && x. ....)
And in that case you will need to add entities you want to include in your class also you can use [NotMapped] attribute if you working direct with your class instead of dto. So in that way you can access the mapped entities direct from the class like class.TableAChild
+
public virtual TableAChild TableAChild { get; set; }
public virtual TableAB TableAB { get; set; }
Greetings and good luck

Entity framework 6 code first - Add entity with reference to another existing entity

Say I have the following models in my database:
public class LetterEntity
{
public int Id {get; set;}
public string Content {get; set;}
public List<Destination> Destinations {get; set;}
public virtual Folder Folder {get; set;}
public int FolderId {get; set;}
}
Now I want to add a new letter the client has made to my database:
public class SendLetterRequest
{
public string Content {get; set;}
public List<int> DestinationsIds {get; set;}
}
public void SaveLetterToDatabase(SendLetterRequest letter)
{
var letterEntity = new LetterEntity
{
Content = letter.Content;
FolderId = 1;
// How to insert the Destinations Ids in a way that I don't have to load all of those destinations to the context?
}
context.Set<LetterEntity>().Add(letterEntity);
context.SaveChanges();
}
I know that if a LetterEntity only had a single Destination object I could just set it's foreign key value and the insert would work (Just like I do with the FolderId).
How is it done when working with List of entities - how to tell EF that those Ids are already in the database, without fetching all of them to the context, so that it doesn't recreate them?
EDIT:
My Destination model -
public void Destination
{
// Manual key
public int Address {get; set;}
public string DestinationName {get; set;}
public string Information {get; set;}
}
Well, as you probably know, there are two ways to define many-to-many replationship in EF.
(1) Implicit link table
This is what you have used. You create explicitly only the two entitities, define the relation via navigation properties/and or model configuration and let EF maintain the so called "link" table. It's easy, but the downside is that you don't have access to that table, so the only way to add related items is to actually load the entities needed and add them to the navigation property collection.
(2) Explicit link table
Here you define explicitly the link entity and configure 2 one-to-many relations. This way you have access and can add related records w/o having the other entities loaded.
For instance, in your case it could be something like this:
Model:
public class LetterEntity
{
public int Id { get; set; }
// ....
public List<LetterDestinationLink> Links { get; set; }
}
public class Destination
{
public int Id { get; set; }
// ....
public List<LetterDestinationLink> Links { get; set; }
}
public class LetterDestinationLink
{
[Key]
[Column(Order = 0)]
public int LetterId { get; set; }
[Key]
[Column(Order = 1)]
public int DestinationId { get; set; }
public LetterEntity Letter { get; set; }
public Destination Destination { get; set; }
}
Context:
public class YourDbContext : DbContext
{
public DbSet<LetterEntity> LetterEntities { get; set; }
public DbSet<Destination> Destinations { get; set; }
public DbSet<LetterDestinationLink> LetterDestinationLinks { get; set; }
}
Use case:
List<int> destinationIds = ...;
var letterEntity = new LetterEntity { ... };
letterEntity.Links = destinationIds.Select(destinationId =>
new LetterDestinationLink { Letter = letterEntity, DestinationId = destinationId })
.ToList();
context.Set<LetterEntity>().Add(letterEntity);
context.SaveChanges();

C# & EF 4 - Update related object in database

My classes (and tables):
class A {
public int Id {get; set;}
public string Name {get; set;}
public virtual C CField {get; set;}
}
class B {
public int Id {get; set;}
public string Name {get; set;}
public DateTime Date {get; set;}
public virtual C CField {get; set;}
}
class C {
public int Id {get; set;}
public string Name {get; set;}
}
class D {
public int Id {get; set;}
public string Title {get; set;}
public virtual A AField {get; set;}
public virtual B BField {get; set;}
}
I need update my D object. I use dependency injection so I can use repositories in controller:
A aObj = aService.GetAById(id);
B bObj = bService.GetBByName(name);
D dObj = new D()
{
Title = "MyTitle",
AField = aObj,
BField = bObj
};
dService.Update(dObj);
In each class repository I just create context as private field:
private MyContext db = new MyContext();
and I use it in every method like:
var model = db.A.Where(x=>x.Id == id);
return model;
But it can't work because both classes A and B has field with C class so I still got excepted: An entity object cannot be referenced by multiple instances of IEntityChangeTracker when I call dService.Update(dObj) (second listing).
I found that I should detach context in every method in each repositroy like this:
var model = db.A.Where(x=>x.Id == id);
db.Entry(model).State = System.Data.Entity.EntityState.Detached;
return model;
Exception was gone but now CField in aObj and bObj is always null and dObj.BField is not updated.
How can I fix that and what I'm doing wrong? I lost a few days for finding out what I should do: I even try remove private context field in repositories and in every method just use using(var db = new MyContext()) but then exception "An entity object cannot be referenced by multiple instances of IEntityChangeTracker" is back.
You can't mix entities from different contexts in EF. Use ids instead.
Update models:
class A {
public int Id {get; set;}
public string Name {get; set;}
public int CFieldId {get; set;}
public virtual C CField {get; set;}
}
class B {
public int Id {get; set;}
public string Name {get; set;}
public DateTime Date {get; set;}
public int CFieldId {get; set;}
public virtual C CField {get; set;}
}
class C {
public int Id {get; set;}
public string Name {get; set;}
}
class D {
public int Id {get; set;}
public string Title {get; set;}
public int AFieldId {get; set;}
public int BFieldId {get; set;}
public virtual A AField {get; set;}
public virtual B BField {get; set;}
}
And in controller:
A aObj = aService.GetAById(id);
B bObj = bService.GetBByName(name);
D dObj = new D()
{
Title = "MyTitle",
AFieldId = aObj.Id,
BFieldId = bObj.Id
};
dService.Update(dObj);

lambda expression path through Collection Property

I need to get all Students which have Registration on specyfic Realisation.
I was thinking it would be s => s.Registrations.RealisationId == realisationId but it doesn't work :). I'm trying to make this like in the example code but I'm getting:A lambda expression with a statement body cannot be converted to an expression tree. I have no idea how can I write this expression correctly, can anyone help me with this?
I couldn't figure out how to title this question better, sorry.
Database:
public class Student : BaseEntity {
public int StudentId {get; set;}
public virtual ICollection<Registration> Registrations {get; set;}
}
public class Registration : BaseEntity {
public int RegistrationId {get; set;}
public int StudentId {get; set;}
public int RealisationId {get; set;}
public Student Student {get; set;}
public Realisation Realisation {get; set;}
}
public class Realisation : BaseEntity {
public int RealisationId {get; set;}
public virtual ICollection<Registration> Registrations {get; set;}
}
My try:
public IEnumerable<Student> GetByRealisationId(int realisationId) {
return Context.Set<Student>().Where(s => {
foreach(Registration r in s.Registrations) {
if (r.RealisationId == realisationId)
return true;
}
return false;
});
}
You'll need to select the ID's out and use Contains:
return Context.Set<Student>()
.Where(s => s.Registrations
.Select(r => r.RealisationId)
.Contains(realisationId));
Generally this is converted to a WHERE IN clause.

Categories

Resources